[Kotlin] 7장 연산자 오버라이딩 및 관례 이해(사용)하기

13 분 소요

코틀린은 인스턴스에 대해 + 연산자를 정의해서 사용할 수 있다. 이것을 코틀린에서는 관례라고 부른다. 언어 기능을 타입에 의존하는 자바와 달리 코틀린은 함수 이름을 통해 관례에 의존한다. 함수 이름을 통한 관례를 채택한 이유는 기존 자바 클래스가 구현하는 인터페이스는 이미 고정이 되어있어서 코틀린 쪽에서 자바 클래스가 새로운 인터페이스를 구현하게 만들수 가 없기 때문이다.

하지만, 확장 함수를 사용하면 기존 클래스에 새로운 메소드를 추가할 수 있다. 즉, 기존 자바 코드를 바꾸지 않아도 새로운 기능을 쉽게 부여할 수 있다.

7.1 산술 연산자 오버로딩

자바에서는 원시 타입에 대해서만 산술 연산자를 사용할 수 있다. 추가로 String에 대해 + 연산자를 사용할 수 있다.

int a = 1 + 1;
String b = "Hello" + "World!";

다른 클래스에서도 산술 연산자가 유용한 경우가 존재한다.

  • 예를 들면 BigInteger 클래스도 add 메소드 보다는 + 연산을 사용하는 편이 낫다.
  • 컬렉션 원소를 추가하는 경우에도 += 연산자를 사용할 수 있으면 좋다.

7.1.1 이항 산술 연산 오버로딩

data class Point(val x: Int, val y:Int) {
	operator fun plus(other: Point): Point {
		return Point(x + other.x, y + other.y)
	}
}

+ 연산자를 오버로딩 하고 싶으면 plus 함수 앞에 operator 키워드를 붙여야 한다. 연산자를 오버로딩 하기 위해서는 operator를 붙임으로서 관례를 따르는 함수임을 명확하게 할 수 있다. operator가 없는 실수로 관례에서 사용하는 함수 이름을 사용하는 경우에는 "operator modifier is required ..." 오류가 발생한다.

위 처럼 정의하고 나면 Point 클래스끼리 + 연산이 가능하다. 물론, + 연산자는 plus 함수 호출로 컴파일한다. 클래스 내부 함수로 정의할 수도 있고, 확장 함수로 정의할 수도 있다.

operator fun Point.plus(other: Point): Point {
	return Point(x + other.x, y + other.y) 
}

|식 |함수이름 | |–|–| |a * b |times | |a / b |dic | |a % b |mod | |a + b |plus | |a - b |minus |

연산자 우선순위는 표준 숫자 타입에 대한 연산자 우선순위와 같다. 즉, a + b * c의 경우 언제나 곱셈이 먼저 수행된다. *, /, %은 우선순위가 같고, +, - 보다 우선순위가 높다.

연산자를 정의할 때 두 피연산자 (a + b 일 때, ab)가 같은 타입일 필요는 없다.

operator fun Point.times(other: Int): Point {
	return Point((x * other).toInt(), (y * other).toInt())
}

operator fun Double.times(p: Point): Point = 
	Point((x * other).toInt(), (y * other).toInt())

val p = Point(10, 20)
println(p * 2) // Point(x=20, y=40)
println(p.times(2)) // 실제 컴파일되어 실행되는 코드
println(2.times(p)) // 교환 법칙이 성립되지 않는다.

하지만, 연산자가 자동으로 교환 법칙을 지원하지는 않는다. 2 * p는 불가하다. 저렇게 적고 싶다면 대응하는 operator fun Double.times(p: Point): Point를 정의해야한다.

반환타입 또한 두 피연산자 중 하나일 필요는 없다.

println('a' * 3) /// char * Int => String

>>> aaa

7.1.2 복합 대입 연산자 오버로딩

plus와 같은 연산자를 오버로딩하면 그와 관련 있는 연산자인 +=도 자동으로 함께 지원한다. +=, -= 등의 연산자를 복합 대입 연산자라고 부른다. point += Point(3, 4)point = point + Point(3, 4)와 같다. 물론, 변수는 변경이 가능해야 한다.

꼭 참조 변경이 아닌 내부 상태를 변경하는 것도 좋다.

val numbers = ArrayList<Int>()
numbers += 42
numbers.add(42) // 컴파일 시 에는 이렇게 표현.

반환타입이 Unit으로 하려면 plusAssign을 사용하면 된다. plusplusAssign을 동시에 정의하지 마라.

7.1.3 단항 연산자 오버로딩

단항 연산자도 이항 연산자랑 동일하게 정해진 이름으로 함수를 정의하면 된다.

-a

!a

++a, a++

–a, a— |식 |함수이름 | |–|–| |+a|unaryPlus | |-a|unaryMinus | |!a|not | |++a, a++|inc | |–a, a–|dec |

inc, dec 를 정의하는 경우, 전위와 후위 증가 / 감소 연산자와 같은 의미를 제공한다. 후위 ++ 연산은 먼저 현재의 값을 반환한 다음에 값을 증가시킨다 .전위는 수행한다음 값을 반환한다. 전위와 후위를 위해서 별다른 처리를 해주지 않아도 정상 동작한다.

7.2 비교 연산자 오버로딩

코틀린에서는 모든 객체에 대해서 비교 연산을 수행할 수 있다. 자바에서의 equals, compareTo를 코틀린에서는 ==로 사용할 수 있다. 위에서 말한 관례를 적용한 것에 불가하다.

!= 식도 equals 함수로 컴파일된다. a가 null인지 판단해서 null이 아닌 경우에만 비교한다. 만약, a, b 둘다 null이라면 true를 반환한다.

a == b -------> a?.equals(b) ?: (b == null)

자바에서처럼 식별자 비교 연산자(===)를 통해 객체 참조 주소가 같은지 볼 수 있다. ===는 오버로딩 할 수 없다는 것을 기억해라.

다른 연산자 오버로딩과는 다르게 equals의 경우 override를 붙여야 한다. 다른 연산자 관례와는 달리 equalsAny에 정의된 메소드이므로 override가 필요하다. Any의 equals에는 operator가 붙어있다. operator를 오버라이드하는 경우는 또, operator를 붙이지 않아도 된다. 마지막으로 != 를 위해서 따로 처리해야 할 것은 없다.

7.2.2 순서 연산자: compareTo

자바에서 정렬, 최댓값, 최솟값을 비교하는 알고리즘에 comparable 인터페이스를 구현해야 한다. Comparable에 들어있는 compareTo 메소드는 한 객체와 다른 객체의 크기를 비교해 정수로 나타낸다. 자바에서는 짧게 호출할 수 있는 방법이 없다. 하지만, 코틀린에서는 관례를 제공한다.

p1 < p2 --------> p1.compareTo(p2) < 0  같다.
a >= b ---------> a.compareTo(b) >=0  같다.

구현 예시를 살펴보면,

class Person { val firstName: String, val lastName: String): Compareable<Person> {
	override fun compareTo(other: Person): Int {
		return compareValueBy(this, other, Person::lastName, Person::firstName)
	}
}

val p1 = Person("Alice", "Smith")
val p2 = Person("Bob", "Johnson")
println(p1 < p2)
>>> false

equals와 마찬가지로 operator를 붙일 필요가 없다. 상위 클래스 operator를 오버라이드 하는 것이기 때문에 override만 붙이면 된다. 위의 코드는 compareValuesBy 함수를 이용해 compareTo를 쉽고 간결하게 정의할 수 있다. compareValueBy 함 수는 두 객체와 여러 비교 함수를 인자로 받는다. 첫 번째 비교를 통해 같지 않다면 바로 반환을 하고, 같다면 다음 비교 인자로 비교를 이어나간다. 모든 비교 인자가 0을 반환하면 0을 반환한다. 물론 성능은 더 떨어짐을 기억하라. 처음에는 간결하게 짜고 성능에 문제 있다면 추후에 개선하라.

Compare 인터페이스를 구현하는 모든 자바 클래스를 코틀린에서는 간결한 연산자 구문으로 비교할 수 있다. 비교 연산자를 자바 클래스에 대해서 사용하기 위해 따로 확장함수를 만들 필요는 없다.

코틀린 클래스에 대해서는 compareTo를 구현해야 하지만 자바 클래스에 대해서는 코틀린에서 이미 compareTo 가 구현되있다는 의미로 받아드려졌는데, 맞는지는 모르겠다.

7.3 컬렉션과 범위에 대해 쓸 수 있는 관례

컬렉션을 쓸 때 가장 많이 쓰는 행동은 3가지이다.

  1. 원소를 읽거나 (get)
  2. 원소를 쓰거나 (set)
  3. 특정 값이 컬렉션에 속해있는 지 (in)
    1. 속해 있는지
    2. 이터레이션

원소를 인덱스를 통해 설정하거나 사용할 때는 a[b]라는 식(인덱스 연산자라고 부른다.)으로 사용한다. in 연산자는 원소가 컬렉션에 속하는 지 검사하거나 컬렉션에 있는 원소를 이터레이션 할 때 사용한다.

7.3.1 인덱스로 원소에 접근: get과 set

코틀린에서 mutableMap[key] = newValue 즉, 인덱스 연산자는 자동으로 set 연산자로 변환된다.

operator fun Point.get(index: Int): Int {
	return when(index) {
		0 -> x
		1 -> y
		else -> 
			throw IndexOutOfBoundsException("Invalid coordinate $index")
	}
}

>> val p = Point(10, 20)
>> println(p[1])
20
// get이 오버라이드된 메소드가 반환된다.

get 또는 set 메소드를 만들고 operator 변경자를 붙이기만 하면 된다.

위 예시에는 Int를 파라미터로 받고 있지만,

  1. 어느 타입이 와도 가능하고
  2. 여러 파라미터를 사용하는 get을 정의할 수 도 있다.
  3. 다양한 key 타입을 제공해야 한다면 다양한 파라미터 타입에 대해 오버로딩한 get 메소드를 제공할 수 있다.

한번 set 메소드에 대한 오버라이드 예시를 살펴보자.

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
	when(index) {
		0 -> x = value
		1 -> y = value
		else ->
			throw IndexOutOfBoundsException("Invalid coordinate $index")
	}
}
>>> val p = MutablePoint(10, 20)
>>> p[1] = 42
>>> println(p)
MutablePoint(x=10, y=42)

get과 마찬가지로 단순하다.

대신, 특징이 하나 있는데

  • set이 받는 마지막 파라미터 값은 대입문의 우항에 들어가고, 나머지 파라미터 값은 인덱스 연산자에 들어간다. 예시) *x[a, b] = c → x.set(a, b, c)*

7.3.2 in 관례

in은 객체가 컬렉션에 들어있는지 검사(멤버십 검사 membership test)한다. 이때, 재정의할 때 정의해야하는 함수는 contains다. 예시) a in c → c.contains(a)

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
	return p.x in upperLeft.x. until lowerRight.x 
					&& p.y in upperLeft.y until lowerRight.y
}
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect) // 10 ~ 49, 20 ~ 49 범위안에 존재하는지
true
>>> println(Point(5, 5) in rect))
false

until 함수를 이용해 열린 범위를 만든다. 열린 범위는 끝 값을 포함하지 않는 범위를 말한다. 예를 들어, 10..20 이라는 식을 사용해 일반적인 닫힌 범위를 만들면 10 이상 20이하(20포함)인 범위가 생긴다. 10 until 20은 10 이상 19 이하인 범위며, 20은 범위에 포함되지 않는다.

7.3.3 rangeTo 관례

범위를 만드려면 .. 구문을 사용해야 한다. 예시) start..end → start.rangeTo(end) rangeTo 함수는 범위를 반환한다.

>>> val now = LocalDate.now()
>>> val vacation = now..now.plusDays(10)
>>> println(now.plusWeeks(1) in vacation)
true

now..now.plusDays(10) → now.rangeTo(now.plusDays(10))으로 변환된다. rangeTo 연산자는 다른 산술연산자보다 우선순위가 낮다. 하지만 혼동을 피하기 위해서 괄호를 써주면 좋다. 예시) 0..(n + 1)

  1. rangeTo 연산자는 아무 클래스에나 정의할 수 있다.
  2. 하지만, Compareable 인터페이스를 구현하면 rangeT를 정의할 필요가 없다.
    1. operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T> 위의 LocalDate의 rangeTo 함수도 해당 클래스의 멤버가 아니며, Comparable의 확장 함수 이다.

7.3.4 for 루프를 위한 iterator 관례

검사에서도 in 연산자를 사용했지만 for 문 안에서 사용됐을 경우 그 의미는 다르다. for (x in list) { ... } → list.iterator()를 호출해서 자바와 마찬가지로 hasNext와 next 호출을 반복하는 식이다.

이 또한 코틀린의 관례이므로 iterator() 메소드를 확장함수로 정의할 수 있다.

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> = 
	object: Iterator<LocalDate> { // <- 이 객체는 LocalDate안에 Iterator를 구현한다.
		var current = start
		override fun hasNext() = 
			current <= endInclusive // <- compareTo 관례를 사용해 날짜 비교
		override fun next()	= current.apply { 
					current = plusDays(1) // 현재 날짜를 저장 -> 날짜 변경 -> 저장한 날짜 반환
			} // also 도 가능했을 거 같음. apply 라서 더 헷갈리는 듯
	}
}

>>> val newYear = LocalDate.ofYeatDay(2017, 1)
>>> val daysOff = newYear.minusDays(1)..newYear // 2016-12-31 ~ 2017-01-01
>>> for (dayOff in daysOff) { println(dayOff) } // 2016-12-31 -> 2017-01-01
2016-12-31
2017-01-01

ClosedRange에 대한 확장 함수 Iterator를 정의했기 때문에 LocalDate의 범위 객체를 for 루프에 사용할 수 있었다.

7.4 구조 분해 선언과 component 함수

구조 분해 선언(destructuring declaration) 관례를 이용한 마지막 특성이다.

복합적인 값을 분해해서 여러 다른 변수를 한번에 초기화 할 수 있다.

val p = Point(10, 20)
val (x, y) = p
println(x)
println(y)
// 10
// 20

일반 변수 선언과 비슷해 보이지만 좌변에 여러 변수를 괄호로 묶었다는 점이 다르다.

내부에서 구조 분해선언은 관례를 사용한다. componentN이라는 함수를 호출한다. val (a, b) = p → val a = p.component1() val b = p.component2()

data type class의 경우 컴파일러가 componentN 함수를 만들어준다. data type class가 아닌 경우는 아래와 같이 정의하면 된다.

class Point(val x: Int, val y: Int) {
	operator fun component1() = x
	operator fun component2() = y
}

사용 예시)

data class NamaComponents(val name: String, val extension: String) 

fun splitFileName(fullName: String): NameComponents {
	val result = fullName.split('.', limit = 2)
	return NameComponents(result[0]. result[1])
}
>>> val (name, ext) = splitFilename("example.kt")
>>> println(name)
// example
  • 코틀린 표준 라이브러리에서는 맨 앞 다섯 원소에 대한 componentN을 제공한다.
  • Pair, Triple 클래스를 사용하면 함수에서 여러 값을 더 간단하게 반환할 수 있다.
    • 대신, 가독성은 더 떨어질 수 있다.

7.4.1 구조 분해 선언과 루프

루프 안에서의 구조 분해 선언을 살펴본다.

fun printEntries(map: Map<String, String>) {
	for ((key, value) in map) {
		println("$key -> $value")
	}
}

val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
printEntries(map)
// Oracle -> Java
// JetBrains -> Kotlin

a to b의 형태로 Pair로 mapOf로 넘겨주고 있고, map 이터레이션 원소로 Entry가 반환될 텐데 구조 분해 선언을 통해서 각 key, value형태로 받아서 유용하게 사용할 수 있다. 아래 코드 참조

for (entry in map.entries) {
	val key = entry.compoent1()
	val value = entry.compoent2()
  // ...
}

7.5 프로퍼티 접근자 로직 재활용: 위임 프로퍼티

위임은 객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 그 작업을 처리하게 맡기는 디자인 패턴을 말한다. 여기서 작업을 처리하는 도우미 객체를 위임 객체라고 부른다. 이를 관례인 위임 프로퍼티를 통해 제공할 수 있다.

7.5.1 위임 프로퍼티 소개

문법은 다음과 같이 쓴다.

class Foo {
	var p: Type by Delegate()
}

p 프로퍼티의 접근자 로직을 다른 객체에 위임한다. 실제로 컴파일 된 코드는 대략 다음과 같다.

class Foo {
	private val delegate: Delegate()
	var p: Type
	set(value: Type) = delegate.setValue(...)
	get() = delegate.getValue(...)
}

프로퍼티 위임 관례를 따르는 Delegate 클래스는 getValue, setValue 메소드를 제공해야 한다. setVlaue는 변경가능한 프로퍼티만 제공한다.

Delegate 클래스를 살펴보면 다른 관례와 마찬가지로 operator 가 붙어있다. 단순화하여 코드로 나타낸 것이다. Delegate라는 클래스명도 예시일 뿐이다.

by 키워드를 통해 위임 객체를 연결한다.

class Delegate {
	operator fun getValue(...) {...}
	operator fun setValue(...) {...}
}

위임된 객체의 사용은 다른 프로퍼티와 동일하게 사용할 수 있다. 일반 프로퍼티처럼 사용 가능하지만 실제 호출은 위임 객체의 메소드를 호출한다.

>>> val foo = Foo()
>>> val oldValue = foo.p
>>> foo.p = newValue

7.5.2 위임 프로퍼티 사용: by lazy()를 사용한 프포퍼티 초기화 지연

지연 초기화(lazy initialization)는 객체의 일부분을 초기화하지 않고 남겨뒀다가 실제로 필요한 경우에 초기화하는 패턴이다.

사용 예시) person 클래스가 존재하고 해당 객체는 자신이 주고 받은 이메일 목록을 가지고 있다고 할 때 이메일을 데이터 베이스에서 가져오려면 시간이 오래걸린다. 최초로 사용할 때 단 한번만 이메일을 데이터 베이스에서 가져오고 싶을 때 lazy 를 사용하면 된다.

7.5.2.1 지연 초기화 직접 구현 시

class Email {...}
fun loadEmail (person: Person): List<Email> {...}

class Person(val name: String) {
	private var _emails: List<Email>? = null // 초기값은 가져오기 전까지 없음
	val emails: List<Email>
		get() {
			if(_emails == null) _emails = loadEmails(this)
			return _emails!!
		}
}

person.emails의 get메소드를 수정하여 초기에 null일 경우, 로드해오고 존재한다면 그 값을 반환하도록 짠다. 이럴 경우 _emails 프로퍼티 추가로 필요하고 ?를 통해 null이 가능해야 한다. *뒷받침하는 프로퍼티라는 기법이다.*

구현은 가능하지만 생각보다 성가시다. by lazy 키워드를 통해 구현해보자.

7.4.2.2 by lazy를 통해 구현 시

class Person(val name: String) {
	val emails by lazy { loadEmails(this) }
}

lazy 함수의 인자는 값을 초기화할 때 호출할 람다다. lazy 함수는 기본적으로 스레드 safe하다. 필요에 따라 동기화에 사용할 락을 lazy 함수에 전달할 수도 있고, 다중 스레드 환경에서 사용하지 않을 프로퍼티를 위해 lazy 함수가 동기화를 하지 못하게 막을 수도 있다.

lateinitby lazy는 유사한 점이 많다. 값을 나중에 초기화 한다는 점이 가장 유사하지만 가장 큰 차이점은 lateinitvar, by lazyval로 초기화 한다는 점이다.

7.5.3 위임 프로퍼티 구현

예제를 통해 설명해본다. 어떤 객체의 프로퍼티가 바뀔 때마다 리스너에게 변경 통지를 보내려고 한다. 이런 기능은 특정 객체를 UI에 표시하는 경우 객체가 바뀌면 자동으로 UI도 바꿔줘야 한다. 이 기능을 코드로 살펴보자.

PropertyChangeSupport 클래스는 리스너의 목록을 관리하고 PropertyChangeEnvent 이벤트가 들어모녕 목록의 모든 리스터에게 이벤트를 통지한다.

7.5.3.1 그냥 구현을 통한 리스너 기능 구현

open class PropertyChangeAware {
	protected val changeSupport = ProPertyChangeSupport(this)
	// ... 리스터 추가, 삭제 메소드들 추가
}

class Person {
	val name: String, age: Int, salay: Int
} : PropertyChangeAware() {
	var age: Int = age
		set(newVlaue) {
			val oldValue = field
			field = newValue
			changeSupport.firePropertyChange("age", oldValue, newValue) // 통보
	}
	var salary: Int = salary
		set(newVlaue) {
			val oldValue = field
			field = newValue
			changeSupport.firePropertyChange("salary", oldValue, newValue) // 통보
	}

>>> val p = Person("Dmitry", 34, 2000)
>>> p.addPropertyChagneListener(...) // 통보받고 싶은 리스너 추가
>>> p.age = 35
// 리스터에게 통보간다.
>>> p.salary = 21000
// 리스너에게 통보간다.

field 키워드를 통해 각 프로퍼티에 뒷받짐하는 필드에 접근을 한다. 세터 코드를 보면 중복이 많이 보인다.

7.5.3.2 도우미 클래스를 통해서 프로퍼티 변경 통보

이 방법은 위임을 사용하지 않지만, 위임이 실제 동작하는 방식과 유사하다.

먼저, 변경 시 통보하는 코드가 중복되니 이를 자동으로 변경 통지해주는 클래스를 생성한다.

class ObservableProperty {
	val propName: String, var propValue: Int,
	val changeSupport: PropertyChangeSupport
} {
		fun getValue(): Int = propVlaue
		fun setValue(newValue: Int) {
			val oldValue = propValue
			propValue = newValue
			changeSupprot.firePropertyChange(propName, oldVlaue, newValue)
	}
}

class Person {
	val name: String, age: Int, salay: Int
} : PropertyChangeAware() {
	val _age = ObservableProperty("age", age, changeSupport)
	var age: Int
		get() = _age.getValue()
		set(value) { _age.setValue(value) }

	val _salary = ObservableProperty("salary", salary, changeSupport)
	var salary: Int
		get() = salary.getValue()
		set(value) { salary.setValue(value) }

로직의 중복을 상당부분 제거했지만, 위임의 준비코드가 필요하기도하고 중복되는 패턴을 보인다. 이를 코틀린의 위임 프로퍼티 기능을 활용해서 줄여보자.

7.5.3.3 위임 프로퍼티 기능을 사용하여 구현

물론, ObservableProperty 클래스를 위임가능하도록 수정해야한다.

class ObservableProperty {
	var propValue: Int, val changeSupprot: PropertyChangeSupport
} {
	// getValue, setValue 모두 프로퍼티의 객체(p), 해당 프로퍼티(prop)을 인자로 받는다.
	// name을 KProperty에서 가져올 수 있으므로 주 생성자에서 받지 않는다.
	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, salay: Int
} : PropertyChangeAware() {
	var age: Int by ObservableProperty(age, changeSupprot)
	var salary: Int by ObservableProperty(salary, changeSupprot)
}

// 이미 ObservalbeProrperty와 비슷한 클래스가 존재한다. 
class Person {
	val name: String, age: Int, salay: Int
} : PropertyChangeAware() {

	private val observer = {
		prop: KProperty<*>, oldValue: Int, newValue: Int ->
			changeSupport.firePropertyChange(prop.name, oldValue, neweValue)
	}
	var age: Int by Delegates.observable(age, observer)
	var salary: Int by Delegates.observable(salary, observer)
}

by 우항에 항상 새 인스턴스일 필요는 없다. 함수 호출, 다른 프로퍼티, 다른 식이 올 수 있지만 getValue, setValue를 반드시 제공해야 한다.

7.5.4 위임 프로퍼티 컴파일 규칙

class C {
	val prop: Type by MyDelegate()
}
val c = C()

// 컴파일 코드
class C {
	private val <delegate> = MyDelegate()
	val prop: Type
	get() = <delegate>.getValue(this, <property>)
	set(value: Type) = <delegate>.setValue(this, <property>, value)

위 코드를 보면 컴파일러는 모든 프로퍼티 접근자 안에 getValue, setValue호출코드를 생성해준다. 이 방식을 잘 활용하면 저장될 장소를 변경(데이터 베이스, 세션 쿠키 등)할 수 있고, 프로퍼티를 읽/쓰기 할 때 일어날 일을 변경할 수 있다. 이를 간결하게 표현할 수 있기 때문에 잘 활용할 방법을 생각해보자.

출처

  • Kotlin In Action

댓글남기기