본문으로 바로가기
반응형

이 글은 Kotlin In Action을 참고 하였습니다.

더욱 자세한 설명이나 예제는 직접 책을 구매하여 확인 하시기 바랍니다

코틀린에서는 특정 연산자의 역할을 함수로 정의할 수 있습니다. 이를 convention이라고 합니다.

7.1.1 이항 산술 연산자 오버로딩

+, - 같은 연산자를 코틀린에서는 overloading해서 사용할 수 있습니다.
객체끼리 더하거나 뺄때 원하는 동작을 함수로 구현하면 연산자를 이용해 이를 표현할 수 있습니다.
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) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
}

operator라는 키워드로 함수 앞에 붙이고 연산자에 지정된 함수명인 plus를 사용하면 + 연산의 동작을 정의하게 됩니다.

operator 없이 plus()란 함수를 선언하고 만약 해당 기능을 이용한다면 operator가 필요하다는 에러를 뱉습니다.


보통 operator의 선언은 확장함수를 많이 사용합니다.

오버로딩이 가능한 연산자는 아래와 같습니다.

 연산자

 함수명

a+b 

plus 

a-b

minus 

a*b 

times 

a/b 

div 

a%b 

v1.1 미만: mod, v1.1 이상: rem

연산자 우선순위는 산술 연산자의 우선순위를 그대로 따릅니다.


자바에서 코틀린의 오버로딩된 함수를 당연히 호출할 수 있으며, 코틀린에서 자바의 함수를 호출할때 해당 이름과 파라미터 개수만 맞다면 연산자가 오버로딩 된것처럼 사용할 수 있습니다. (자바에는 operator 연산자를 붙일 수 없으므로)


연산자의 타입이 다르고 return값역시 다르게 설정해도 상관없이 동작합니다.

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

fun main(args: Array) {
    val p = Point(10, 20)
    println(p * 1.5)
}

또한 교환법칙이 성립하지 않으므로 a * 1.51.5 * a는 같지 않습니다.

같게 하려면 operator fun Double.times(p: Point): Point {..}를 추가적으로 선언해야 합니다.


operator 함수도 오버로딩이 가능하기 때문에 같은 이름에 파라미터 타입이 서로 다른 연산자 함수를 여러개 만들 수 있습니다.


단 비트연산자는 오버로딩이 불가 하고, 대신 중위 함수를 제공합니다.

 자바 bit operator

코틀린 함수 

 <<

shl 

 >>

shr 

 >>>

ushr 

 &

and 

 |

or 

 ^

xor 

 ~ 

inv 

 println(0x0F and 0xF0)
 println(0x0F or 0xF0)
 println(0x1 shl 4)


7.1.2 복합 연산자

코틀린은 + 대응 함수인 plus를 overloading하면 += 로 자동으로 구현해 줍니다.
만약 =+의 동작을 따로 구현하고 싶다면 plusAssign 함수를 overloading하면 됩니다.
당연히 minusAssign, timesAssign, divAssign등의 함수도 지원합니다.

plusplusAssign 두개를 동시 구현하면 컴파일 오류가 발생합니다. (+ 에 대한 동작을 어떤걸 해야할지 모르기 때문이죠.)

코틀린은 컬렉션에도 해당 연산지를 제공합니다. 단 아래 규칙에 따릅니다.

  • +, - 는 항상 새로운 collection을 반환한다
  • mutable collection에서 +=, -=는 collection을 원소를 변경한다.(새로운 collection을 생성하지 않음)
  • 불변 collection에서 +=, -=는 새로운 collection을 반환한다. 따라서 이를 받는 변수는 var로 선언되어야 한다
val list = arrayListOf(1,2)
list += 3 // 기존 list에 3 추가
val newList = list + listof(3,4)


7.1.3 단항 연산자

++, --처럼 단항 연산자 역시 overloading이 가능합니다.
단항 연산자 함수는 인자가 없습니다.
operator fun Point.unaryMinus(): Point {
    return Point(-x, -y)
}

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

fun main(args: Array) {
    val p = Point(10, 20)
    println(-p)

    var bd = BigDecimal.ZERO
    println(bd++)
    println(++bd)
}

 표현

함수명 

 +a

unaryPlus 

 -a

unaryMinus 

 !a

not 

 ++a, a++ 

inc 

 --a, a-- 

dec 

++ 이나 --의 경우 inc(), dec()만 구현하면 알아서 전위와 후위 연산을 해줍니다.


7.2.1 equals

앞서 객체의 ==는 equals()로 치환된다고 언급했습니다. != 역시 equals()로 치환되어 동작합니다.
실제 내부적으로는 아래와 같이 판단되어 사용되므로 우항이나 인자가 null이어도 정상 동작하도록 되어있습니다.
a == b -> a?.equals(b) ?: (b==null)

또한 위 형태에 따라 a와 b가 둘다 null이면 true를 반환한다는 점을 주의해야 합니다.
equals()는 Any에 정의된 함수 이므로 override keyword를 붙여서 재구현 할 수도 있습니다.

equals는 Any안에 operator 키워드가 붙어서 구현되어 있기 때문에 하위 클래스에서는 override keyword를 사용해서 == 와 치환할 수 있습니다.
또한 이런 특이점 때문에 equals는 extension function으로 구현할 수 없습니다.

7.2.2 compareTo


코틀린에서 비교 연산자인 <, >, <=, >= 는 Comparable의 compareTo 함수를 호출하도록 되어 있습니다.

a >= b -> a.compareTo(b) >= 0 과 같습니다.

 

class Person(val firstName: String, val lastName: String) : Comparable {
    override fun compareTo(other: Person): Int {
        return compareValuesBy(this, other,
            Person::lastName, Person::firstName)
    }
}

fun main(args: Array) {
    val p1 = Person("Alice", "Smith")
    val p2 = Person("Bob", "Johnson")
    println(p1 < p2)
}

Comparable 내부에 compareTo() 역시 operator 키워드가 붙어있기 떄문에 override할때 operator 키워드를 붙여줄 필요는 없습니다.

compareValueBy(객체1, 객체2, 비교조건1, 비교조건2) 함수는 두개의 조건을 우선순위 비교조건에 따라 처리하는 함수 입니다.

1. 두객체 비교 equals -> 0

2. 비교조건1 사용 -> 0 이 안나올때까지 비교

3. 만약 비교조건1이 모두 0이라면 비교조건2 사용 -> 0이 안나올때까지 비교


compareValueBy()를 사용하기 보단 필요한 필드만 따로 비교하도록 만드는게 더 간결하고 좋을수도 있습니다.


반응형