본문 바로가기

책 요약하기/Do it! 코틀린 프로그래밍

#07-3. 연산자 오버로딩 2021-05-16

07-3 연산자 오버로딩

코틀린에서 특정 연산자의 역할을 함수로 정의하고 있습니다. 이를 일종의 협약(Convension)이라고 하는데 수학에서 곱하기 연산이 더하기 연산보다 우선하듯이 연산자 역시 우선순위(Precedence)를 갖고 있습니다.

우선순위 분류 심볼
높음























낮음
접미사(Postfix) ++, --, ., ?., ?
접두사(Prefix) -, ++, --, !, 라벨선언(이름@)
오른쪽 형식(Type RHS)` :, as, as?
배수(Multiplicative) *, /, %
첨가(Additive) +, -
범위(Range) ..
중위 함수(Infix Function) SimpleName
엘비스(Elvis) ?:
이름 검사(Name Checks) in, !in, is, !is
비교(Comparison) >, <, >= , <= 
동등성(Equality) ==, !=
결합(Conjunction) &&
분리(Disjunction) ||
할당(Assignment) =, +=, -=, *=, /=, %=

 

연산자의 작동 방식

연산자를 사용하면 관련된 멤버 메서드를 호출하는 것과 같습니다. :) 예를 들여 a + b는 a.plus(b)라는 함수가 내부적으로 호출되는 것입니다.

코틀린 표준 라이브러리에서 Primitives.kt를 살펴보면 operator 키워드를 사용해 plus()함수가 다양한 자료형으로 선언되어 있는 것을 할 수 있습니다. 

+ 연산자의 오버로딩

package chapter_07.chapter_07_3

class Point(var x: Int = 0, var y: Int = 10) {
    // plus() 함수의 연산자 오버로딩
    operator fun plus(p: Point) : Point {
        return Point(x + p.x, y + p.y)
    }
}

fun main() {
    val p1 = Point(3, -8)
    val p2 = Point(2, 9)

    var point = Point()
    point = p1 + p2 // Point 객체의 +  연산이 가능하게 됨.
    println("point = (${point.x}, ${point.y})")
}
/*
    결과 :
    point = (5, 1)
    point = (4, 0)
 */

Point 클래스를 만들고 +연산자를 오버로딩했습니다. 그 객체를 서로 + 연산자를 이용해 좌표 값에 x, y를 더해 새로운 객체에 할당했습니다.

 

증감 연산자

package chapter_07.chapter_07_3

class Point2(var x: Int = 0, var y: Int = 10) {

    // 증감연산자 오버로딩
    operator fun dec() = Point2(--x, --y)

}

fun main() {
    var point = Point2()
    --point // --연산자
    println("point = (${point.x}, ${point.y})")

}
/*
    결과 :
    point = (-1, 9)
 */

--감소 연산자가 정의되었습니다.

 

호출 연산자

class Point3(var x: Int = 0, var y: Int = 10) {
    // 호출 연산자 오버로딩
    operator fun invoke(value: String) = println(value)
}

fun main() {
    var point = Point3()
    // invoke는 생략 할 수 있음.
    point.invoke("invoke : Do something for me!")
    point("Skip invoke : Do something for me!")
}
/*
    결과 :
    invoke : Do something for me!
    Skip invoke : Do something for me!
 */

호출 연산자는 함수 호출을 돕는데 사용합니다. 소스 코드를 보면 Point3클래스를 통해 새엇앟ㄴ 객체 point라는 이름만으로 접근해 사용할 수 있습니다. 원래는 point.invoke("...") 형태로 호출되어야 하지만 invoke를 생략하고 객체 이름만 작성해서 코드를 읽기 쉬워집니다.

val sum = { a, b -> a + b }
sum.invoke(3, 7)
sum(3, 7) // 위와 동일 식

 

인덱스 접근 연산자

package chapter_07.chapter_07_3

class Point4(var x: Int = 0, var y: Int = 10) {
    val a = arrayOf(1,2,3,4)
}

fun main() {
    var point = Point4()

    // setter -> [ ]연산자로 입력.
    // 1, 2, 3, 4... -> 1, 2, 3, 13
    point.a.set(3, 13)
    point.a[3] = 13

    // getter -> [ ]연산자로 출력.
    println("${point.a.get(3)}")
    println("${point.a[3]}")
}
/*
    결과 :
    13
    13
 */

인덱스 접근 연산자는 게터/세터를 다루기 위한 대괄호([]) 연산자를 제공합니다.

 

단일 연산자

package chapter_07.chapter_07_3

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

// 단일 연산자 오버로딩
operator fun Point5.unaryMinus() = Point5(-x, -y)


fun main() {
    var point = Point5(10, 20)
    println(-point)
}
/*
    결과 :
    Point5(x=-10, y=-20)
 */

확장 함수와 같은 기법으로 Point클래스에 연산자 메서드를 정의했습니다.

 

범위 연산자

package chapter_07.chapter_07_3

fun main() {
    val arr = arrayOf(1,2,3,4,5,6,7,8,9,0)

    val i = 1
    if(i in 1..10){ // 1 <= i && i >= 1과 동일
        println(i)
    }
    
    println(1 in 1..10) // 1은 범위에 있음.
    println(arr.contains(10)) // 10은 범위에 없음

}
/*
    결과 :
    1
    true
    false
 */

in 연산자는 특정 객체를 반복하기 위해 반복문에 사용하거나 범위 연산자와 함께 포함 여부를 판단할 수 있습니다:)
따라서 이 연산자를 오버로딩하려면 contains()메서드를 이용할 수 있습니다. !in 형식은 반대의 경우로 범위에 없는 경우를 가리킵니다. -> ex) (a !in b)식과 (!b.contains(a))은 같은식!

 

대입 연산자

package chapter_07.chapter_07_3

class Point7(var x: Int = 0, var y: Int = 10) {
}

fun main() {
    val point = Point7()
    var result: Int = 0

    var t: Int = 1
    result += point.y
    // 결과 : 10 = 0 + 10을 result에 대입

    //>>>
}

 

동등성 연산자

package chapter_07.chapter_07_3

class Point8(var x: Int = 0, var y: Int = 10) {
}

fun main() {
    val point = Point()

    val a = 10

    // a와 10을 비교한 결과 출력 true
    println(a == point.y)

    // 같은식
    var result: Int? = null
    println(a?.equals(point.y))

}
/*
    결과 :
    true
    true
 */

동등성 연산자는 두 객체의 값의 동등성을 판별합니다. ==, !=는 둘 다 equals()로 변경되어 동작하는데 위와 같은 판단문이 사용되면 인자가 null이어도 동작하도록 되어 있습니다. 따라서 a와 b가 둘 다 null이면 true를 반환합니다. euqals는 Any안에 operator 키워드가 붙어서 구현되어 있기 때문에 하위 클래스에서는 override 키워드를 사용해서 ==와 치환할 수 있습니다. 또한 이런 특이점 때문에 equals는 확장 함수로 구현할 수 없습니다.
(===, !==와 같은 값, 자료형을 비교하는 연산자는 오버로딩 할 수 없습니다)

 

비교 연산자

package chapter_07.chapter_07_3

class Point9(var x: Int = 0, var y: Int = 10) {
}

fun main() {
    val point = Point9()

    val a = 10
    // y와 a가 같으므로 false
    println(a < point.y)

    // 같은식
    println(a.compareTo(point.y))
}
/*
    결과 :
    false
    false
 */

 

마무리 문제

    01. 위임을 통해 메서드에 접근하는 코드가 있습니다.
    interface A {
        fun functionA() { }
    }
    interface B {
        fun functionB() { }
    }
    class DelegatedC(a: A, b: B): ________ {
        fun functionC() {
            functionA()
            functionB()
        }
    }

    정답 : A by a, B by b


    02. 데이터 전달을 위해 사용하는 클래스는 ____ 키워드를 사용하여 정의하면
    toString()이나 equals()와 같은 메서드가 자동으로 생성됩니다.

    정답 : data

//    03. 다음은 내부의 클래스에서 바깥 클래스의 멤버에 접근하는 코드입니다.
    class Outer(val name: String) {
        private val origin = "hello"
        ________class MyClass(val from: String) {
            fun getInfo() = "${name} says $origin from ${from}"
        }
    }

    정답 : Inner

이상 마무리하겠습니다. :)