컬렉션을 다룰 때 가장 많이 사용하는 연산은 인덱스를 사용해 원소를 읽거나 쓰는 연산과 어떤 값이 컬렉션에 속해있는지 검사하는 연산이다. 인덱스를 사용해 원소를 설정하거나 가져오고 싶다면 a [b]라는 식을 사용하고, 원소가 컬렉션이나 범위에 속하는지 검사하는 연산자로는 in 연산자를 사용한다.
get과 set(인덱스로 원소에 접근)
코틀리에서 맵의 원소에 접근할 때나 자바에서 배열 원소에 접근할 때나 "[", "]"를 사용한다. 이 연산자를 사용해 변경 가능 맵에 키/값 쌍을 넣거나 이미 맵에 들어있는 키/값 연관 관계를 변경할 수 있다.
multableMap[key] = newValue
- 코틀린에서는 인덱스 연산자도 관례를 따른다.
- 인덱스 연산자를 사용해 원소를 읽는 연산은 get 연산자 메서드로 변환되고 원소를 쓰는 연산은 set 연산자 메서드로 변환된다.
- Map, MultableMap 인터페이스에는 두 메서드가 들어있다.
operator fun Point.get(index: Int): Int { //get 연산자 함수를 정의
return when(index){
//주어진 인덱스에 해당하는 좌표를 찾는다
0 -> x
1 -> y
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
fun main(args: Array<String>) {
val p = Point(10, 20)
println(p[1])
}
- get이라는 메서드를 만들고 operator 변경자를 붙이면 된다
- p[1]이라는 식은 p가 Point 타입일 경우 방금 정의한 get 메서드로 변환된다
- get 메서드의 파라미터로 Int가 아닌 타입도 사용할 수 있다는 점을 기억하자
- 맵 인덱스 연산 같은 경우 get의 파라미터 타입은 맵의 키 타입과 같은 임의의 타입일 수 있다
- 또한 여러 파라미터를 사용하는 get을 정의할 수도 있다(operator fun get(rowIdx:Int, colIdx: Int){}, matrix[row, col])
- 컬렉션 클래스가 다양한 키 타입을 지원해야 한다면 다양한 파라미터 타입에 대해 오버로딩한 get메서드를 여럿 정의할 수 있다
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int){ //set 연산자 함수를 정의
//주어진 인덱스에 해당하는 좌표를 변경
when(index){
0 -> x = value
1 -> y = value
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
fun main(args: Array<String>) {
val p = MutablePoint(10, 20)
p[1] = 42
println(p)
}
- 대입에 인덱스 연산자를 사용하려면 set이라는 이름의 함수를 정의하면 된다.
- set이 받는 마지막 파라미터 값은 대입문의 우항에 들어가고, 나머지 파라미터 값은 인덱스 연산자에 들어간다
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 && //범위를 만들고 x 좌표가 그 범위 안에 있는지 검사
p.y in upperLeft.y until lowerRight.y //util함수를 통해 열린 범위 생성
}
fun main(args: Array<String>) {
val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect)
println(Point(5, 5) in rect)
}
- in의 우항에 있는 객체는 contains 메서드의 수신 객체가 되고, in의 좌항에 있는 객체는 contains 메서드에 인자로 전달된다.
- 열린 범위는 끝 값을 포함하지 않는 범위를 말하고, util을 사용해 열린 범위를 생성할 수 있다
rangeTo 관례
범위를 만들려면 .. 구문을 사용해야 한다. ".." 연산자는 rangeTo 함수를 간략하게 표현하는 방법이다.
rangeTo 함수는 범위를 반환한다. 이 연산자를 아무 클래스에나 정의할 수 있지만, 어떤 클래스가 Comparable 인터페이스를 구현하면 rangeTo를 정의할 필요가 없다.
코틀린 표준 라이브러리를 통해 비교 가능한 원소로 이뤄진 범위를 쉽게 만들 수 있으며, 모든 Comparable 객체에 대해 적용 가능한 rangeTo 함수가 들어있다.
operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
- 이 함수는 범위를 반환하며, 어떤 원소가 그 범위 안에 들어있는지 in을 통해 검사할 수 있다.
fun main(args: Array<String>) {
val now = LocalDate.now()
val vacation = now..now.plusDays(10) //오늘 부터 10일짜리 범위 생성
println(now.plusWeeks(1) in vacation) //특정 날짜가 날짜 범위 안에 들어가는지 검사
}
- now..now.plusDays(10) 이라는 식은 컴파일러에 의해 now.rangeTo(now.plusDays(10))으로 변환된다.
- rangeTo 함수는 Comparable에 대한 확장 함수다.
fun main(args: Array<String>) {
val n = 9
println(0..(n+1)) //괄호를 처서 명확하게 하자
(0..n).forEach{ print(it) } //범위의 메서드를 호출하기 위해서는 범위를 괄호로 둘러싸라
}
- rangeTo 연산자는 다른 산술 연산자 보다 우선순위가 낮다
- 혼동을 피하기 위해 괄호로 인자를 감싸주자
- 0.. n.forEach()와 같은 식은 컴파일 할 수 없다.
- 범위 연산자의 우선순위가 낮아 범위의 메서드를 호출하기 위해서는 범위를 괄호로 둘러싸야 한다.
for 루프를 위한 iterator 관례
코틀린의 for 루프는 범위 검사와 똑같이 in 연산자를 사용하지만, in의 의미는 다르다.
for(x in list){}와 같은 문장은 list.iterator()를 호출해 이터레이터를 얻은 뒤 자바와 마찬가지로 해당 이터레이터에 대해 hasNext와 next 호출을 반복하는 식으로 변환된다.
코틀린에서는 이 또한 관례로서 iterator 메서드를 확장 함수로 정의할 수 있다.
operator fun CharSequence.iterator(): CharIterator //문자열에 대한 이터레이션할 수 있게 한다
- String의 상위 클래스인 CharSequence에 대한 iterator 확장 함수를 제공한다.
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
object : Iterator<LocalDate> {
//이 객체는 LocalDate 원소에 대한 Iterator를 구한다
var current = start
override fun hasNext(): Boolean =
current <= endInclusive //compareTo 관례를 사용해 날짜를 비교한다
//현재 날짜를 저장한 다음 날짜를 변경한다, 그 후 변경한 날짜를 반환한다
override fun next(): LocalDate = current.apply {
current = plusDays(1) //현재 날짜를 1일 뒤로 변경한다
}
}
fun main(args: Array<String>) {
val newYear = LocalDate.ofYearDay(2017, 1)
val daysOff = newYear.minusDays(1)..newYear
for (dayOff in daysOff){ //daysOff에 대응하는 iterator 함수가 있으면 daysOff에 대해 이터레이션한다
println(dayOff)
}
}
- rangeTo 라이브러리 함수는 ClosedRange의 인스턴스를 반환한다.
- 코드에서 ClosedRange<LocalDate>에 대한 확장 함수 iterator를 정의했기 때문에 LocalDate의 범위 객체를 for 루프에 사용할 수 있다.
'KotlinInAction > 연산자 오버로딩과 기타 관례' 카테고리의 다른 글
| 프로퍼티 접근자 로직 재활용: 위임 프로퍼티 (0) | 2022.08.09 |
|---|---|
| 구조 분해 선언과 component 함수 (0) | 2022.08.09 |
| 비교 연산자 오버로딩 (0) | 2022.08.08 |
| 산술 연산자 오버로딩 (0) | 2022.08.08 |