# 7장 연산자 오버로딩과 기타 관례

### 이번장에서는...

#### 배운점, 느낀점

- 연산자 오버로딩
- 관례: 여러 연산을 지원하기 위해 특별한 이름이 붙은 메서드
- 위임 프로퍼티

---

- 표준 라이브러리와 밀접하게 연관된 언어 기능
    - for ...in 루프 : java.lang.Iterable
    - try문(try-with-resource statement) : java.lang.AutoCloseable

- 어떤 언어 기능과 미리 정해진 이름의 함수를 연결해주는 기법을 코틀린에서는 관례라고 부른다
    - 코틀린은 어떤 클래스 안에 plus라는 이름의 특별한 메서드를 정의하면 그 클래스 인스턴스에 대해 + 연산자를 사용할 수 있다.
    - 언어 기능을 타입에 의존하는 자바와 달리 코틀린은(함수 이름을 통한)관례에 의존한다.

#### 산술 연산자 오버로딩

- 코틀린에서 관례를 사용하는 가장 단순한 예는 산술 연산자다.

#### 이항 산술 연산 오버로딩

```kotlin
data class Point(val x: Int, val y: Int) {
//    operator fun plus(other: Point): Point { // "plus"라는 이름의 연산자 함수를 정의한다.
//        return Point(x + other.x, y + other.y) // 좌표를 성분별로 더한 새로운 점을 반환한다.
//    }
}

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

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

operator fun Point.unaryMinus(): Point { // 단항 Minus(음수) 함수는 파라미터가 없다.
    return Point(-x, -y) // 좌표에서 각 성분의 음수를 취한 새점을 반환한다.
}

fun main(args: Array<String>) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)

    println(p1 + p2) // + 로 계산하면 "plus" 함수가 호출된다.

    val p = Point(10, 20)
    println(p * 1.5)

    println(-p)
}

연산자를 확장 함수로 정의하기
operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}
오버로딩 가능한 이항 산술 연산자
함수 이름
a * b times
a / b div
a % b mod(1.1 부터 rem)
a + b plus
a - b minus
결과 타입이 피연산자 타입과 다른 연산자 정의하
operator fun Char.times(count: Int): String {
    return toString().repeat(count)
}

fun main(args: Array<String>) {
    println('a' * 3)
}

복합 대입 연산자 오버로딩

val number = ArrayList<Int>()
numbre += 42
println(number[0])

// 42
operator fun <T> MutableCollection<T>.plusAssign(element: T) {
    this.add(element)
}
val list = arrayListOf(1, 2)
list += 3 // += 는 list를 변경한다.
val newList = list + listOf(4, 5) // +는 두 리스트의 모든 원소를 포함하는 새로운 리스트를 반환한다.
println(list)

// [1, 2, 3]

println(newList)
// [1, 2, 3, 4, 5]

단항 연산자 오버로딩

operator fun Point.unaryMinus(): Point { // 단항 Minus(음수) 함수는 파라미터가 없다.
    return Point(-x, -y) // 좌표에서 각 성분의 음수를 취한 새점을 반환한다.
}
함수 이름
+a unaryPlus
-a unaryMinus
!a not
++a , a++ inc
--a, a-- dec
operator fun BigDecimal.inc() = this + BigDecimal.ONE

fun main(args: Array<String>) {
    var bd = BigDecimal.ZERO
    println(bd++) // 후위 증가 연산자는 println이 실행된 다음에 값을 증가시킨다

    println(++bd) // 전위 층가 연산자는 println이 실행되기 전에 값을 증가시킨다.
}

비교 연산자 오버로딩

동등성 연산자: equals

class Point(val x: Int, val y: Int) {
    override fun equals(obj: Any?): Boolean { // Any에 정의된 메서드를 오버리딩 한다.
        if (obj === this) return true // 최적화: 파라미터가 this와 같은 객체인지 살펴본다
        if (obj !is Point) return false // 파라미터 타입을 검사한다
        return obj.x == x && obj.y == y // Point로 스마트 캐스트해서 x와 y 프로퍼티에 접근한다.
    }
}

순서 연산자: compareTo

class Person(
    val firstName: String, val lastName: String
) : Comparable<Person> {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other, Person::lastName, Person::firstName) // 인자를 받은 함수를 차례로 호출하면서 값을 비교한다.
    }
}

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

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

mutableMap[key] = newValue
data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: int) { // set이라는 연산자 함수를 정의한다
    when (index) {
        0 -> x = value
        1 -> y = value
        else ->
            throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array<String>) {
    val p = MutablePoint(10, 20)
    p[1] = 42 println (p)
}

in 관례

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

operator fun Ractangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x && // 범위를 만들고 x 좌표가 그 범위 안에 있는지 검사한다.
            p.y in upperLeft.y until lowerRight.y // until 함수를 사용해 열린 범위를 만든다
}

fun main(args: Array<String>) {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    println(Point(20, 30) in rect)
    println(Point(5, 5) in rect)
}

rangeTo 관례

operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
fun main(args: Array<String>) {
    val now = LocalDate.now()
    val vacation = now..now.plusDays(10) // now(오늘) 부터 시작해 10일짜리 범위를 만든다.
    println(now.plusWeeks(1) in vacation)  // 특정 날짜가 날짜 범위 안에 들어가는지 검사한다.
}
fun main(args: Array<String>) {
    val n = 9
    println(0..(n + 1)) // 0..n + 1 이라고 써도 되지만 괄호를 치면 더 뜻이 명확해진다.
}
fun main(args: Array<String>) {
    (0..n).forEach { print(it) } // 컴파일 x, 범위의 메서드를 호출하려면 범위를 괄호로 둘러싸라
}

for 루프를 위한 iterator 관례

operator fun CharSequense.iterator(): CharIterator // 이 라이브러리 함수는 문자열을 이터레이션할 수 있게 해준다

for (c in "abc") {
} // 이 라이브러리 함수는 문자열을 이터레이션할 수 있게 해준다,

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) // 현재 날짜를 1일 뒤로 변경한다.
        }
    }

fun main(args: Array<String>) {
    val newYear = LocalDate.ofYearDay(2017, 1)
    val daysOff = newYear.minusDays(1)..newYear
    for (dayOff in daysOff) {
        println(dayOff)
    }
}

구조 분해 선언과 component 함수

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

operator fun component1() = x
operator fun component2() = y
data class NameComponents(val name: String, val extension: String) // 값을 저장하기 위한 데이터 클래스를 선언한다

fun splitFileName(fullName: String): NameComponents {
    val result = fullName.split('.', limit = 2)
    return NameComponents(result[0], result[1]) // 함수에서 데이터 클래스의 인스턴스를 반환한다
}

fun main(args: Array<String>) {
    val (name, ext) = splitFileName("example.kt") // 구조 분해 선언 구문을 사용해 데이터 클래스를 푼다
    println(name)
    println(ext)
}

구조 분해 선언과 루프

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) {
        println("$key -> $value") // 루프 변수에 구조 분해 선언을 사용힌다.
    }
}

fun main(args: Array<String>) {
    val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
}
printEntries(map)

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


class Foo {
    private val delegate = Delegate() // 컴파일러가 생성한 도우미 프로퍼티다
    var p: Type
        set(value: Type) = delegate.setValue(..., value) // p 프로퍼티를 위해 컴파일러가 생성한 접근자는 delegate의 getValue와 setValue 메서드를 호출한다. 
    get() = delegate.getValeu(...)
}
class Delegate {
    operator fun getValue(...) {  // getValue는 게터를 구현하는 로직을 담는다
        ...
    }
    operator fun setValue(...) { // setValue 메서드는 세터를 구현하는 로직을 담는다
        ...
    }
}

class Foo {
    val p: Type by Delegate() // by 키워드는 프로퍼티와 위임 객체를 연결한다.
}

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

class Email {/*...*/ }

fun loadEmails(person: Person): List<Email> {
    println("${person.name}의 이메일을 가져옴")
    return listOf(/*...*/)
}
지연 초기화를 뒷받침하는 프로퍼티를 통해 구현하기
class Person(val name: String) {
    private var _emails: List<Email>? = nill // 데이터를 저장하고 emails 의 위임 객체 역할을 하는 _email 프로퍼티
    val emails: List<Email>
        get() {
            if (_emails == null) {
                _emails = loadEmails(this) // 최초 접근 시 이메일을 가져온다
            }
            return _emails!! // 저장해 둔 데이터가 있으면 그 데이터를 반환한다
        }
}

fun main(args: Array<String>) {
    val p = Person("Alice")
    println(p.emails) // 최초로 emails를 읽을 때 단 한 번만 이메일을 가져온다,
}
class Person(val name: String) {
    val emails by lazy { loadEmail }
}

프로퍼티 값을 맵에 저장

class Person {
    // 추가 정보
    private val _attribute = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attribute[attrName] = value
    }

    // 필수 정보
    val name: String
        get() = _attribute["name"]!! // 수동으로 맵에서 정보를 꺼낸다
}
class Person {
    // 추가 정보
    private val _attribute = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attribute[attrName] = value
    }
    val name: String by _attribute // 위임 프로퍼티로 맵을 사용한다
}