Chapter7. 연산자 오버로딩과 기타 관례 (1)

김신영·2022년 10월 22일
0

kotlin-in-action

목록 보기
8/11
post-thumbnail

연산자 오버로딩

  • operator 키워드를 붙여야 한다.
  • 연산자를 멤버함수로 정의할 수 있다.
data class Point(val x: Int, val y: Int) {  
    operator fun plus(other: Point): Point {  
        return Point(x + other.x, y + other.y)  
    }  
}  
  
fun main(args: Array<String>) {  
    val p1 = Point(10, 20)  
    val p2 = Point(30, 40)  
    println(p1 + p2)  
    println(p1.plus(p2))  
}
  • 연산자를 확장함수로 정의할 수 있다.
  • 연산자를 정의할 때는 관례(Convention)을 따르는 이름의 확장 함수로 구현하는게 일반적인 패턴이다.
data class Point(val x: Int, val y: Int)  
  
operator fun Point.plus(other: Point): Point {  
    return Point(x + other.x, y + other.y)  
}  
  
fun main(args: Array<String>) {  
    val p1 = Point(10, 20)  
    val p2 = Point(30, 40)  
    println(p1 + p2)  
    println(p1.plus(p2))  
}

오버로딩 가능한 이항 산술 연산자 (Binary Operator)

함수 이름
a * btimes
a / bdiv
a % bmod (1.1부터 rem)
a + bplus
a - bminus
  • 연산자 우선순위는 언제나 표준 숫자 타입에 대한 연산자 우선순위가 같다.

두 피연산자가 같은 타입일 필요는 없다.

data class Point(val x: Int, val y: Int)  
  
operator fun Point.times(scale: Double): Point {  
    return Point((x * scale).toInt(), (y * scale).toInt())  
}  
  
fun main(args: Array<String>) {  
    val p = Point(10, 20)  
    println(p * 1.5)  // Point(x=15, y=30)
}

연산자 함수의 반환 타입이 두 피연산자의 타입과 달라도 된다.

operator fun Char.times(count: Int): String {  
    return toString().repeat(count)  
}  
  
fun main(args: Array<String>) {  
    println('a' * 3)  // aaa
}
  • 일반 함수와 마찬가지로 operatpor 함수도 오버로딩할 수 있다.

비트 연산자에 대해 특별한 연산자 함수를 지원하지 않는다.

Kotlin 중위 연산자Java 연산자
shl왼쪽 시프트 <<
shr오른쪽 시프트 >>
ushr오른쪽 시프트 >>>
and비트 곱 &
or비트 합 `
xor비트 배타 합 ^
inv비트 반전 ~
fun main(args: Array<String>) {  
    println(0x0F and 0xF0)  
    println(0x0F or 0xF0)  
    println(0x1 shl 4)  
    println(0b101010.inv())  
}

// 0
// 255
// 16
// -43

복합 대입 연산자 오버로딩

Compound Assignment Operator

a += b

// 1. plus
a = a.plus(b)

// 2. plusAssign
a.plusAssign(b)


- `+=`는 `plus`와 `plusAssign` 양쪽으로 컴파일할 수 있다.
- `plus`, `plusAssign` 동시에 정의하지 말라.

```kotlin
fun main(args: Array<String>) {  
    val list = arrayListOf(1, 2)  
    list += 3  //  plusAssign
    val newList = list + listOf(4, 5)  
    println(list)  
    println(newList)  
}

단항 연산 오버로딩 (Unary Operator)

함수 이름
+aunaryPlus
-aunaryMinus
!anot
++a , a++inc
--a , a--dec
data class Point(val x: Int, val y: Int)  
  
operator fun Point.unaryMinus(): Point {  
    return Point(-x, -y)  
}  
  
fun main(args: Array<String>) {  
    val p = Point(10, 20)  
    println(-p)  // Point(x=-10, y=-20)
}
operator fun BigDecimal.inc() = this + BigDecimal.ONE  
  
fun main(args: Array<String>) {  
    var bd = BigDecimal.ZERO  
    println(bd++)  // 0
    println(++bd)  // 2
}

비교 연산자 오버로딩

동등성 연산자 equals

  • a == b --> a?.equals(b) ?: (b == null)
  • 식별자 비교 연산자 === 의 경우 오버로딩 할 수 없다.
  • Any 에 이미 operator로 정의된 메서드
  • override 만 명시하면 된다.
class Point(val x: Int, val y: Int) {  
    override fun equals(obj: Any?): Boolean {  
        if (obj === this) return true  
        if (obj !is Point) return false  
        return obj.x == x && obj.y == y  
    }  
}  
  
fun main(args: Array<String>) {  
    println(Point(10, 20) == Point(10, 20))  // true
    println(Point(10, 20) != Point(5, 5))    // true
    println(null == Point(1, 2))             // false
}

순서 연산자 compareTo

  • a >= b --> a.compareTo(b) >= 0
    - Comparable 인터페이스에 operator로 정의 된 메서드
    - override 만 명시하면 된다.
class Person(  
        val firstName: String, val lastName: String  
) : Comparable<Person> {  
  
    override fun compareTo(other: Person): Int {  
        return compareValuesBy(this, other, 
	        Person::lastName, Person::firstName)  
    }  
}  
  
fun main(args: Array<String>) {  
    val p1 = Person("Alice", "Smith")  
    val p2 = Person("Bob", "Johnson")  
    println(p1 < p2)  // false
}

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

Convention함수 이름
x[a, b]x.get(a, b)
x[a, b] = cx.set(a, b, c)
a in listlist.contains(a)
start..endInclusivestart.rangeTo(endIclusive)
for (x in a..b)(a..b).iterator()

인덱스로 원소에 접근 get , set

  • x[a, b] --> x.get(a, b)
  • x[a, b] = c --> x.set(a, b, c)
data class Point(val x: Int, val y: Int)  
  
operator fun Point.get(index: Int): Int {  
    return when(index) {  
        0 -> x  
        1 -> y  
        else ->  
            throw IndexOutOfBoundsException("Invalid coordinate $index")  
    }  
}  
  
fun main(args: Array<String>) {  
    val p = Point(10, 20)  
    println("${p[0]}, ${p[1]}")  // 10, 20
    println(p[2])  // IndexOutOfBoundsException: Invalid coordinate 2
}
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")  
    }  
}  
  
fun main(args: Array<String>) {  
    val p = MutablePoint(10, 20)  
    p[1] = 42  
    println(p)  // MutablePoint(x=10, y=42)
}

in 관례

  • a in c --> c.contains(a)
data class Point(val x: Int, val y: Int)  
  
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  
}  
  
fun main(args: Array<String>) {  
    val rect = Rectangle(Point(10, 20), Point(50, 50))  
    println(Point(20, 30) in rect)  // true
    println(Point(5, 5) in rect)    // false
    println(Point(15, 50) in rect)  // false
}

rangeTo 관례

  • start..end --> start.rangeTo(end)
  • Comparable 인터페이스를 구현했다면, rangeTo 를 정의할 필요가 없다.
  • Kotlin 표준 라이브러리에는 모든 Comparable객체에 대해 rangeTo함수가 정의되어 있다.
    - Comparable에 대한 확장함수로 정의되어 있다.
// Kotlin Library
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
  • rangeTo 연산자 ..는 다른 산술 연산자보다 우선순위가 낮다.
    - 하지만 혼동을 피하기 위해 과라호로 인자를 감싸주는걸 추천한다.

for loop를 위한 iterator 관례

for (x in a..b) {
   // ...
}

// After Kotlin Compiler 
val it = (a..b).iterator()
while (it.hasNext()) {
   val x = it.next()
   // ...
}
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =  
        object : Iterator<LocalDate> {  
            var current = start  
  
            override fun hasNext() =  
                current <= endInclusive  
  
            override fun next() = current.apply {  
                current = plusDays(1)  
            }  
        }  
  
fun main(args: Array<String>) {  
    val newYear = LocalDate.ofYearDay(2017, 1)  
    val daysOff = newYear.minusDays(1)..newYear  
    for (dayOff in daysOff) { println(dayOff) }  
}

// 2016-12-31
// 2017-01-01
profile
Hello velog!

0개의 댓글