오늘도 더 나은 코드를 작성하였습니까?

연산자 오버로딩과 기타 관례(컬렉션과 범위에 대해 쓸 수 있는 관례) 본문

Kotlin in Action/코틀린 답게 사용하기

연산자 오버로딩과 기타 관례(컬렉션과 범위에 대해 쓸 수 있는 관례)

hik14 2020. 8. 24. 21:33

컬렉션을 다룰 때 가장 많이 쓰는 연산은 인덱스를 사용해 원소를 읽거나 쓰는 연산과 어떤 값이 컬렉션에 포함되어 있는지 확인하는 연산이다.

 

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

코틀린에서는 인덱스 연산자도 관례를 따른다.

인덱스 연산자를 사용해 원소를 읽는 연산은 get으로 변환되고 원소를 쓰는 연산은 set으로 변환된다.

 

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

p [0]  ====> p.get(0)으로 변환된다.

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[0] = 20
    p[1] = 30
    println(p)
}

set의 경우 받는 마지막 파라미터 값은 대입문의 우항에 들어가고 나머지 파라미터 값은 [ ] 인덱스 연산자로 들어간다.

 

p [0] = 20    ====> p.set(0, 20)

p [1] = 30    ====> p.set(1, 30)

in (포함) 관례

컬렉션이 지원하는 다른 연산자로는 in이 있다.

in은 컬렉션에 객체 들어있는지 확인한다. 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
}


fun main() {

    val rectangle = Rectangle(Point(10, 20),Point(50,50))

    println(Point(20,30) in rectangle)
    println(Point(5,5) in rectangle)
}

until 함수를 사용하여 열린 범위를 만든다.(참고 10.. 20은 10과 20을 포함하지만 10 until 20 은 20은 포함하지 않는다)

 

a in c  ====> c. contains( a )

rangeTo(범위) 관례

범위를 만들려면  .. 구문을 사용한다. 

.. 연산자는 rangeTo에  대응한다.

 

rangeTo 함수는 범위를 반환한다. 이 연산자를 어떤 클래스에도 정의할 수 있다.

하지만 클래스가 comparable 인터페이스를 구현한다면  rangeTo를 정의할 필요가 없다.

코틀린 표준 라이브러리를 통해 비교 가능한 객체로 이뤄진 범위를 쉽게 생성할 수 있다.

코틀린 표준 라이브러리에는 모든 comparable 객체에 대해 적용 가능한 rangeTo함수가 들어있다.

 

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

 

예제

fun main() {
    val now = LocalDate.now()
    val vacation = now..now.plusDays(10)
    println(now.plusWeeks(1) in vacation)

    val n = 9
    println(0..(n+1))  // 산술연산자보다 우선순위가 낮다

    (0..n).forEach { println(it) } // 우선순위가 낮아서 메서드 호출시 괄호를 써주는게 좋다.
}

now.. now.plusDays  ====>  now.rangeTo(now.plusDays(10))

 

for 루프를 위한 iterator 관례

코틀린의 for 루프는 범위 검사와 똑같이 in 연산자를 사용한다.

하지만 이 경우의 in의 의미는 다르다.

 

for (x in list) {...}는 list.iterator() 객체를 얻은 후에 hasNext와 next호출을 반복하는 식으로 변환된다.

 

String의 상위 클래스인 CharSequence에 대한 iterator 확장 함수를 제공한다.

 

operator fun CharSequence.iterator(): CharIterator

 

예제

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
        object : Iterator<LocalDate> {

            var current = start

            override fun hasNext(): Boolean =
                current <= endInclusive

            override fun next(): LocalDate = current.apply {
                current = plusDays(1)
            }
        }


fun main() {

    val newYear = LocalDate.ofYearDay(2020, 1)
    val daysOff = newYear.minusDays(2)..newYear

    for (day in daysOff)
        println(day)
}

 

rangeTo 라이브러리 함수는 ClosedRange 인스턴스를 반환한다.

ClosedRange <LocalDate>에 대한 확장 함수에 iterator를 정의했기 때문에 LocalDate의 객체 범위를 for 루프에서 사용 가능한 것이다.