-
코틀린 - 연산자 오버로딩과 기타 관례Kotiln 2019. 7. 23. 10:54
어떤 언어 기능과 미리 정해진 이름의 함수를 연결해주는 기법을 관례 라고 부른다
이항 산술 연산 오버로딩
연산자를 오버로딩하는 함수 앞에는 꼭
operator
키워드를 붙여야 한다.operator
키워드를 붙임으로써 어떤 함수가 관례를 따르는 함수임을 명확히 할 수 있다.
코틀린에서는 직접 연산자를 만들어 사용할 수 없고 언어에서 미리 정해둔 연산자만 오버로딩할 수 있으며 관례에 따르기 위해 클래스에서 정의해야 하는 이름이 연산자별로 정해져 있다.식 함수 이름 a * b times a / b div a % b mod (1.1 부터 rem) a + b plus a - b minus 코틀린은 표준 숫자 타입에 대해 비트 연산자를 정의하지 않는다. 따라서 커스텀 타입에서 비트 연산자를 정의할 수도 없다. 대신 중위 연산자 표기법을 지원하는 일반 함수를 사용해 비트 연산을 수행한다. 커스텀 타입에서도 그와 비슷한 함수를 정의해 사용할 수 있다.
* shl - 왼쪽 시프트
* shr - 오른쪽 시프트 (부호 비트 유지)
* ushr - 오른쪽 시프트 (0으로 부호 비트 설정)
* and - 비트 곱
* or - 비트 합
* xor - 비트 베타 합
* inv - 비트 반전복합 대입 연산자 오버로딩
plus
와 같은 연산자를 오버로딩 하면 그와 관련 있는 연산자인+=
도 자동으로 지원한다.
반환 타입이Unit
인plusAssign
함수를 정의하면 코틀린은 += 연산자에 그 함수를 사용한다.operator fun <T> MutableCollection<T>.plusAssign(element: T){ this.add(element) }
단항 연산자 오버로딩
식 함수 이름 +a unaryPlus -a unaryMinus !a not ++a, a++ inc --a, a-- dec equals
동등성 연산자a == b
라는 비교를 처리할 때 코틀린은 a 가 null 인지 판단해서 null이 아닐 경우에만a.equals(b)
를 호출한다.compareTo
순서 연산자비교연산자(<, >, <=, >=) 는
compareTo
호출로 컴파일 된다.get
과set
인덱스로 원소에 접근인덱스 연산자 (
[ ]
)를 사용해 원소를 읽는 연산은get
연산자 메소드로 변환되고 원소를 쓰는 연산은set
연산자 메소드로 변환된다.in
관례in
은 객체가 컬렉션에 들어있는지 검사(멤버십 검사) 한다. 그런 경우in
연산자와 대응하는 함수는contains
다.rangeTo
관례..
연산자는rangeTo
함수를 간략하게 표현하는 방법이다.
코틀린 표준 라이브러리에는 모든Comparable
객체에 대해 적용 가능한rangeTo
함수가 들어있다.for
루프를 위한iterator
관례for
루프와 같이in
연산자를 이용하지만 여기서는 관례이기 때문에iterator
메소드를 확장 함 수로 정의할 수 있다.
코틀린 표준 라이브러리는String
의 상위 클래스인CharSequence
에 대한iterator
확장 함수를 제공한다.구조 분해 선언과
component
함수구조 분해를 사용하면 복합적인 값을 분해해서 여러 다른 변수를 한꺼번에 초기화할 수 있다.
val p = Point(10, 20) val (x, y) = p println(x) > 10 println(y) > 20
내부에서 구조 분해 선언은 다시 관례를 사용한다. 구조 분해 선언의 각 변수를 초기화하기 위해
componentN
이라는 함수를 호출한다. (N은 구조 분해 선언에 있는 변수 위치에 따라 붙는 번호다.)
위의 코드val (a, b) = p
는val a = p.component1() val b = p.component2()
로 컴파일 된다.data
클래스의 주 생성자에 들어있는 프로퍼티에 대해서는 컴파일러 자동으로componentN
함수를 만들어준다.class Point(val x: Int, val y: Int){ operator fun component1() = x operator fun compenent2() = y }
구조 분해 선언은 함수에서 여러 값을 반환할 때 유용하다.
구조 분해 선언과 루프
함수 본문 내의 선언문뿐 아니라 변수 선언이 들어갈 수 있는 장소라면 어디든 구조 분해 선언을 사용할 수 있다.
fun printEntries(map: Map<String, String>){ for((key,value) in map){ println("$key -> $value") } }
위의 코드는 객체를 이터레이션 하는 관례와 구조 분해 선언을 활용한다.
위임 프로퍼티 : 프로퍼티 접근자 로직 재활용
위임 프로퍼티를 사용하면 값을 뒷받침하는 필드에 단순히 저장하는 것보다 더 복잡한 방식으로 작동하는 프로터피를 쉽게 구현할 수 있다.
class Foo { var p: Type by Delegate() }
위임 프로퍼티의 일반적인 문법이다. p 프로퍼티는 접근자 로직을 다른 객체에게 위임한다. 여기서
Delegate
클래스의 인스턴스를 위임 객체로 사용한다.컴파일러는 숨겨진 도우미 프로퍼티를 만들고 그 프로퍼티를 위임 객체의 인스턴스로 초기화한다.
class Foo { private val delegate = Delegate() var p: Type set(value: Type) = delegate.setValue(..., value) get() = delegate.getValue(...) }
프로퍼티 위임 관례를 따르는
Delegate
클래스는getValue
와setValue
메소드를 제공해야 한다.Delegate
클래스를 단순화하면 다음과 같다.class Delegate{ operator fun getValue(...){...} operator fun setValue(..., value: Type){...} } class Foo{ var p: Type by Delegate() } > val foo = Foo() > val oldValue = foo.p > foo.p = newValue
위임 프로퍼티 사용 :
by laze()
를 사용한 프로퍼티 초기화 지연
지연 초기화lazy initalization 는 객체의 일부분을 초기화하지 않고 남겨뒀다가 실제로 그 부분의 값이 필요할 경우 초기화 할 때 흔히 쓰이는 패턴이다.
class Email(val name:String){ private var _emails: List<Email>> = null val emails: List<Email> get() { if(_emails == null){ _emails = loadEmails(this) } return _emails!! } } fun loadEmails(person: Person): List<Email>{ println("${person.name}의 이메일을 가져옴") return listOf(/*...*/) }
loadEmails
함수는 데이터베이스에서 이메일을 가져온다.Email
클래스는 이메일을 불러오기 전에는 null을 저장하고 불러온 다음에는 이메일 리스트를 저장하는_emails
프로퍼티를 추가해서 지연 초기화를 구현한 클래스이다.뒷받침하는 프로퍼티backing property 기법을 사용했다.
_emails
프로퍼티는 값을 저장하고 다른 프로퍼티인emails
은_emails
라는 프로퍼티에 대한 읽기 연산을 제공한다._emails
는 null이 될 수 있는 타입인 반면emails
은 null 이 될 수 없는 타입이므로 프로퍼티를 두 개 사용해야 한다.위임 프로퍼티를 사용하면 더 간단해진다. 위임 프로퍼티는 데이터를 저장할 대 쓰이는 뒷받침하는 프로퍼티와 값이 오직 한 번만 초기화됨을 보장하는 게터 로직을 함께 캡슐화해준다.
위임 객체를 반환하는 표준 라이브러리 함수가lazy
이다.class Persion(val name:String){ val emails by lazy { loadEmails(this)} }
lazy
함수는getValue
메소드가 들어있는 객체를 반환하기 때문에by
키워드와 함께 사용해 위임 프로퍼티를 만들 수 있다.위임 프로퍼티 구현
/* PropertyChangeSupport를 사용하기 위한 도우미 클래스 */ open class PropertyChangeAware { protected val changeSupport = PropertyChangeSupport(this) fun addPropertyChangeListener(listener: PropertyChangeListener) { changeSupport.addPropertyChangeListener(listener) } fun removePropertyChangeListener(listener: PropertyChangeListener) { changeSupport.removePropertyChangeListener(listener) } } /* 프로퍼티 위임에 사용할 수 있게 작성 KProperty 인자를 통해 프로퍼티 이름을 전달 받는다. */ class ObservableProperty( var propValue: Int, val changeSupport: PropertyChangeSupport ){ operator fun getValue(p:Person, prop: KProperty<*>): Int = propValue operator fun setValue(p:Person, prop: KProperty<*>, newValue: Int){ val oldValue = propValue propValue = newValue changeSupport.firePropertyChange(prop.name, oldValue, newValue) } } /* 위임 프로퍼티를 통해 프로퍼티 변경 통지 받기 */ class Person( val name: String, age: Int, salary: Int ): PropertyChangeAware(){ var age: Int by ObservableProperty(age, changeSupport) var salary: Int by ObservableProperty(salary, changeSupport) } fun main(args: Array<String>){ val p = Person("Dmitry", 35, 2000) p.addPropertyChangeListener( PropertyChangeListener { event -> println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}") } ) p.age = 34 p.salary = 10000 } > Property age changed from 35 to 34 > Property salary changed from 2000 to 10000
by
키워드 오른쪽에 오는 객체를 위임 객체delegate 라고 부르며 코틀린은 위임 객체를 감춰진 프로퍼티에 저장하고 주 객체의 프로퍼티를 읽거나 쓸 때마다 위임 객체의getValue
와setValue
를 호출해준다.코틀린 표준 라이브러리에 있는
Delegates.observable
을 사용하면 다음처럼 작성할 수 있다.class Person( val name: String, age: Int, salary: Int ): PropertyChangeAware(){ private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int -> changeSupport.firePropertyChange(prop.name, oldValue, newValue) } var age: Int by Delegates.observable(age, observer) var salary: Int by Delegates.observable(salary, observer) }
프로퍼티 값을 맵에 저장
자신의 프로퍼티를 동적으로 정의할 수 있는 객체를 만들 때 위임 프로퍼티를 활용하는 경우가 있다. 그런 객체를 확장 가능한 객체expando object 라고 한다.
class Person{ private val _attributes = hashMapOf<String, String>() fun setAttribute(attrName: String, value: String){ _attributes[attrName] = value } val name: String by _attributes } fun main(args: Array<String>){ val p = Person() val data = mapOf("name" to "Dmitry", "company" to "JeTBrains") for((attrName, value) in data) p.setAttribute(attrName, value) println(p.name) } > Dmitry
참고 도서 : Kotlin in Action(2017)
반응형'Kotiln' 카테고리의 다른 글
코틀린 - 함수의 정의와 호출 (0) 2019.07.24 코틀린 기초 (0) 2019.07.23 코틀린 타입 시스템 - 컬렉션과 배열 (0) 2019.07.22 코틀린 타입 시스템 - 원시 타입 (0) 2019.07.19 코틀린 타입 시스템 - null 관련 (0) 2019.07.19