본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin Tutorial] Operator overload 와 convention #1 - Chap7. Operator overloading and other conventions

by 돼지왕 왕돼지 2017. 8. 24.
반응형

 [Kotlin Tutorial] Operator overload 와 convention #1 - Chap7. Operator overloading and other conventions


참조 : Kotlin in action

!=, +, + operator, +=, ==, ===, === overload, accessing elements by index, and, any class, arithmetic operator, autocloseable, biginteger +, binary opreator, bitwise operator, Callback, callback 비교대상, charsequence, collection, commutativity, comparable interface, comparevaluesby, conciseness, contains, contains operator, convention, convention extension function, Conventions used for collections and ranges, COPY, dec, div, equality operator, equals, equals extension, extension function operator, for in, for loop, function call, Get, hasnext, immutable, in convention, inc, infix call, infix call syntax, inv, iterable, iterator convention, Java, java convention, java operator, kotlin basics, kotlin tutorial, kotlin 강좌, kotlin 기초 강좌, LAMBDA, member reference, MINUS, mod, Mutable, mutable collection, next, Not, nullable operands, numeric type, object, operand, operator, operator function overload, operator function override, or, Ordering operators: compareTo, Overloading arithmetic operators, Overloading binary arithmetic operations, Overloading comparison operators, Overloading compound assignment operators, Overloading unary operators, override, Performance, Plus, plusAssign, Primitive type, property reference, return type, return type unit, Set, shl, shr, specific name, specific type, The rangeTo convention, The “iterator” convention for the “for” loop, Times, try-with-resources, unary operator, unaryMinus, unaryPlus, ushr, Val, XOR, [Kotlin Tutorial] Operator overload 와 convention #1 - Chap7. Operator overloading and other conventions, 개별 element, 성능, 우선순위, 코틀린, 코틀린 강좌



-

Java 에는 특정 class 에 결속되어 있는 언어적 기능이 있다.

예를 들어 Iterable 를 구현하면 for loop 에서 쓸 수 있고, AutoCloseable 을 구현하면 try-with-resources 에서 사용할 수 있다.


Kotlin 도 비슷한 것들이 있다.

그러나 specific type 에 결속된 것이 아니라 specific name 에 결속되는 기능들이 있다.

예를 들어 plus 라는 이름으로 class 에 function 을 추가하면, + operator 를 해당 class 간에 사용할 수 있다.

이것을 Kotlin 에서는 “convention” 이라고 부른다.



-

Kotlin 이 convention 을 사용하는 이유는 이미 존재하는 Java class 들도 Kotlin language feature 에 적용하기 위함이다.

Java 코드를 변화시킬 수 없는 경우에는 Kotlin 만의 특성을 적용시키기 어렵기 때문이다. ( 추가 interface 를 구현시킬 수 없다. )

그러나 extension function 을 이용해 기능을 추가할 수는 있다.

extension method 를 통해 convention method 들을 추가하면 Java 코드 수정 없이 Kotlin 의 convention 기능을 사용할 수 있다.




7.1. Overloading arithmetic operators


-

Java 에서 arithmetic operation 은 primitive type 과 String 에만 사용할 수 있었다.

그러나 BigInteger 등에서도 + 가 쓰이면 편리할 것이다.

Collection 에서 += operator 들이 쓰이면 또 편리할 것이다.

Kotlin 은 “convention” 을 사용해 이를 수행할 수 있다.




7.1.1. Overloading binary arithmetic operations


-

data class Point(val x: Int, val y: Int){

    operator fun plus(other: Point): Point{

        return Point(x + other.x, y + other.y )

    }

}


val p1 = Point(10, 20)

val p2 = Point(30, 40)

val sumPoint = p1 + p2 // 40, 60 값을 갖는 Point, p1.plus(p2) 코드가 수행된다고 보면 된다


operator keyword 를 사용해 plus function 을 정의하면 + 를 쓸 수 있다.



-

extension function 으로도 정의 가능하다.

external library class 에 convention 을 추가할 때는 이렇게 extension function 으로 정의한다.

operator fun Point.plus(other: Point): Point{

    return Point(x + other.x, y+other.y)

}



-

직접 operator 를 정의할 수는 없고, 정해진 몇 개의 set 에 대해서만 overload 가능하다.


Expression / Function name

a * b / times

a / b / div

a % b / mod

a + b / plus

a - b / minus



-

operator 를 overload 해도 기본 numeric type 우선순위는 보장된다.

즉 a + b * c 가 있으면 b * c 가 먼저 수행되고, 그 다음 a 가 더해진다.



-

Java 에서 Kotlin 의 operator 는 그냥 function call 을 하면 된다.

Java 는 operator overload 가 없기 때문에 그냥 convention 함수들이 정의되어 있으면 operator 들을 쓸 수 있다.



-

operator 를 정의할 때 operand 를 꼭 같은 type 으로 지정할 필요는 없다.

operator fun Point.times(scale: Double): Point{

    return Point(x * scale).toInt(), (y * scale).toInt())

}



-

Kotlin 의 operator 는 commutativity ( left, right 를 operator 를 사이에 두고 swap 해도 결과가 같음 ) 를 지원하지 않는다.

예를 들어 위의 times 의 경우 1.5 * p 는 p * 1.5 와 다르다.

1.5 * p 를 사용하려면 Double.times(p : Point) : Point 가 정의되어 있어야 한다.



-

operator 의 return type 도 type 이 달라도 된다.

operator fun Char.times(count: Int): String{

    return toString().repeat(count)

}



-

operator function 도 일반 function 처럼 자유롭게 overload 할 수도 있다.



-

Kotlin 은 number type 에 대해 bitwise operator 를 정의하지 않는다.

따라서 해당 operator overload 도 불가능하다.

대신 infix call syntax 를 이용해서 일반적인 function 으로 대체하였다.


shl - Signed shift left

shr - Signed shift right

ushr - Unsigned shift right

and - Bitwie and

or - Bitwise or

xor - Bitwise xor

inv - Bitwise inversion


println(0x0F and 0xF0) // 0

println(0x0F or 0xF0) // 255

println(0x1 shl 4) // 16




7.1.2. Overloading compound assignment operators


-

plus operator 를 정의하면 + 뿐만 아니라 자동으로 += 도 지원된다.



-

+= 를 새롭게 정의하고 싶다면 plusAssign 를 정의해주면 된다. ( 기존 operator 함수에 Assign 을 postfix 로 )

이 때 return type 을 Unit 으로 준다면 새롭게 Object 를 만드는 것이 아니라, 기존 object 를 modify 한다고 보면 된다.

( 이 경우는 array 에 대한 operation 을 생각하면 쉽다. array1 += array2 )



-

plus 와 plusAssign 이 모두 정의가 된 상황에서 += 가 사용되면 plus 와 plusAssign 모두가 불릴 수 있다.

이 경우에는 compiler 가 error 를 내뿜는다.

a += b 

    a = a.plus(b)

    a.plusAssign(b)


이를 해결하려면 operator 가 아닌 function call 을 해야 한다.

혹은 val 로 선언해서 compiler 가 plusAssign 이 호출되도록 유도할 수 있다.


가장 좋은 것은 plus 와 plusAssign 을 둘 다 정의하지 않는 것이다.

class 가 immutable 이면 plus 만 정의하고,

mutable 인 경우에는 plusAssign 만 제공하는 것이 좋다.



-

Kotlin 의 collection 에서는 다음과 같이 작동한다.

+, - operator 는 항상 새로운 collection 을 return 한다.

+= 와 -= operator 는 mutable collection 에서는 object 를 modify 한다. 

+=, -= 가 read-only 에서는 modified copy 를 return 한다. ( collection 참조가 var 이어야만 한다  )

그리고 operands 로는 collection 이 될 수도 있고, 개별 element 가 될 수도 있다.

val list = arrayListOf(1, 2)

list += 3 // [1, 2, 3]

val newList = list + listOf(4,5) // [1, 2, 3, 4, 5]




7.1.3. Overloading unary operators


-

operator fun Point.unaryMinus(): Point{

    return Point(-x, -y)

}


Unary operator 는 param 이 없다.



-

Expression / Function name

+a / unaryPlus

-a / unaryMinus

!a / not

++a, a++ / inc

—a, a— / dec


inc 와 dec 는 기존 동작과 동일하게 작동한다. ( 순서적으로 )

operator fun BigDecimal.inc() = this + BigDecimal.ONE


var bd = BigDecimal.ZERO

println(bd++) // 0

println(++bd) // 2







7.2. Overloading comparison operators


7.2.1. Equality operators: “equals”


-

Kotlin 에서 == 는 equals method 를 호출해 동일함을 비교한다.

!= 도 마찬가지로 equals 를 이용한다.

다른 operator 들과는 다르게 == 와 != 는 nullable operands 와도 사용될 수 있다.

a == b 를 사용하면 a 가 null 이 아닌지 보고, null 이 아니면 a.equals(b) 를 호출한다. a 가 null 이면 b 도 null 일 때만 true 이다.

a == b // a?.equals(b) ?: (b== null)



-

data class 로 define 된 class 는 property 들을 이용해서 자동으로 equals 를 만든다.

그러나 그 외에는 직접 equals 를 구현해줘야 한다.

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

    }

}



-

=== 는 overload 될 수 없다.

equals 는 extension 으로 정의될 수 없다. Any class 를 상속한 경우 extension 보다 Any class 의 것이 항상 우선되기 때문이다.




7.2.2. Ordering operators: compareTo


-

Java 와 동일하게 Kotlin 에서도 Comparable interface 를 사용해야 object 간 비교가 가능하다.

그러나 Java 와는 다르게 모든 Comparable interface 를 구현한 클래스들에 대해 <, >, <=, >= 를 사용할 수 있다.

Kotlin 에서 저 operator 들이 호출되면 Comparable interface 의 compareTo 가 자동으로 불린다.

a >= b // a.compareTo(b) >= 0



-

class Person(val firstName: String, val lastName: String) : Comparable<Person>{

    override fun compareTo(other: Person): Int{

        return compareValuesBy(this, other, Person::lastName, Person::firstName)

    }

}


compareValuesBy 는 Kotlin 기본 lib 에 들어 있는 것으로 compareTo 를 쉽고 간단하게 구현할 수 있게 도와준다.

param 으로 비교할 object 들과 callback 들을 받는데, callback 들은 비교대상을 이야기한다.

callback 은 lambda 일 수도 있고, property reference 일 수도 있다.


물론 빠르기로 따진다면 직접 구현한 것이 더 빠르다.

그래서 compareValuesBy 를 사용할 때는 conciseness 와 performance 를 고려해봐야 한다.



-

Java 에서 Comparable interface 를 구현한 것은 Kotlin 에서 operator 를 사용해 비교될 수 있다.





7.3. Conventions used for collections and ranges


7.3.1. Accessing elements by index: “get” and “set”


-

Kotlin 에서 index operator 는 또 하나의 convention 이다.


val value = map[key]

mutableMap[key] = newValue


read 는 get 으로 치환되고, write 는 set 으로 치환된다.


operator fun Point.get(index: Int): Int{

    return when(index){

        0 -> x

        1 -> y

        else -> throw IndexOutOfBoundsException(“Invalid coordinate $index”)

    }

}



-

x[a, b] // x.get( a, b )


위와 같이 get 으로 치환되는 형태이기 때문에 parameter 는 Int 가 아닌 다른 것이 되어도 된다.

예를 들어 map 의 경우는 이미 key 가 object (Any) 형태이다


여러 버전의 get 함수를 overload 해도 된다.

two dimensional array 의 경우는 row, column 으로 해당 element 에 접근하는 신박한 녀석을 만들 수도 있다.


set 의 경우도 비슷하다.

x[a, b] = c // x.set(a, b, c)




7.3.2. The “in” convention


-

in 의 경우는 contains 로 매핑된다.

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

}


a in c // c.contains(a)




7.3.3. The rangeTo convention


-

.. 는 rangeTo function 에 mapping 된다.

rangeTo 는 Range class 를 return 한다.

start..end // start.rangeTo(end)



-

만약 class 가 Comparable interface 를 구현했다면 Kotlin standard lib 을 사용해서 range 를 create 할 수 있다.

이 떄 생성되는 Range 는 in 을 check 하기 위한 용도이지 모든 element 가 다 들어간 것은 아니다.

operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>


val now = LocalDate.now()

val vacation = now..now.plusDays(10)

println(now.plusWeeks(1) in vacation) // true


LocalDate 는 rangeTo 를 구현하지 않았지만, Comparable interface 를 구현했기 때문에 extension function 에 의해 .. 을 사용할 수 있다.



-

.. 은 arithmetic operator 보다 우선순위가 낮지만, 가독성을 위해 그래도 괄호를 쓰는 것이 좋다.


0..n+1 보다 0..(n+1) 이 선호된다.




7.3.4. The “iterator” convention for the “for” loop


-

for 문 안의 in 은 iterator() 함수를 호출한다.

iterator 는 hasNext 와 next 가 있고, 이 녀석들이 호출되면서 for 문을 도는 것이다.


Kotlin 에서는 iterator method 도 convention 이다.



-

Kotlin 에서는 CharSequence 에 extension 으로 iterator 를 구현했다. 그래서 for 문 안에서 돌 수 있다.



-

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) }

}





반응형

댓글