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

Map의 원소에 접근할 때나 배열 원소에 접근할 때 각괄호([ ])를 사용한다. 이것이 어떻게 동작하는지 살펴보자.

val value = map[key]

코틀린에서는 인덱스 연산자도 관례를 따른다. 인덱스 연산자를 사용해 원소를 읽는 연산은 get 연산자 메소드로 변환되고, 원소를 쓰는 연산은 set 연산자 메소드로 변환된다. Map과 MutableMap 인터페이스에는 그 두 메소드가 이미 들어있다.

직접 만든 클래스에 get, set 연산자 메소드를 추가하면 각괄호를 사용할 수 있게 된다.

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

operator fun Point.get(index: Int): Int {
    return when(index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main() {
    val p = Point(10, 20)
    println(p[1]) // 20
}

get 메소드의 파라미터로 Int가 아닌 타입도 사용할 수 있다. 예를 들어 맵 인덱스 연산의 경우 get의 파라미터 타입은 맵의 키 타입과 같은 임의의 타입이 될 수 있다. 또한 여러 파라미터를 사용하는 get을 정의할 수도 있다. 컬렉션 클래스가 다양한 키 타입을 지원해야 한다면 다양한 파리미터 타입에 대해 오버로딩한 get 메소드를 여럿 정의할 수도 있다.

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() {
    val p = MutablePoint(10, 20)
    p[1] = 42
    println(p)
}

in 관례

in은 객체가 컬렉션에 들어있는지 검사한다. 그런 경우 in 연산자와 대응하는 함수는 contains다.

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

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 lowerRight.y until upperLeft.y
}

fun main() {
    val rect = Rectangle(Point(10, 50), Point(50, 20))
    println(Point(20, 30) in rect) // true
    println(Point(5, 5) in rect) // false
}

in의 우항에 있는 객체는 contains 메소드의 수신 객체가 되고, in의 좌항에 있는 객체는 contains 메소드에 인자로 전달된다.

열린 범위와 닫힌 범위

열린 범위는 끝 값을 포함하지 않는 범위를 말한다. 예를 들어 10..20이라는 식을 사용해 일반적인 (닫힌) 범위를 만들면 10 이상 20 이하인 범위가 생긴다. 10 until 20으로 만드는 열린 범위는 10 이상 19이하인 범위며, 20은 범위 안에 포함되지 않는다. 사각형을 표현하는 Rectangle 클래스의 경우 오른쪽과 아래쪽 좌표는 사각형 안에 포함시키기 않는 경우가 많다. 따라서 이 경우 열린 범위를 사용하는 편이 더 낫다.

rangeTo 관례

범위를 만들려면 .. 구문을 사용해야 한다. .. 연산자는 rangeTo 함수를 간략하게 표현하는 방법이다.

rangeTo 함수는 범위를 반환한다. 이 연산자를 아무 클래스에나 정의할 수 있다. 하지만 어떤 클래스가 Comparable 인터페이스를 구현하면 rangeTo를 정의할 필요가 없다. 코틀린 표준 라이브러리에는 모든 Comparable 객체에 대해 적용 가능한 rangeTo 함수가 들어있다.

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

이 함수는 범위를 반환하며, 어떤 원소가 그 범위 안에 들어있는지 in을 통해 검사할 수 있다.