728x90
반응형
✅ 컬렉션과 범위에 대해 쓸 수 있는 관례
- 컬렉션을 다룰 때 가장 많이 쓰는 연산은 인덱스를 사용해 원소를 읽거나 쓰는 연산과 어떤 값이 컬렉션에 속해있는지 검사하는 연산이다.
- 이러한 연산 모두 연산자 구문으로 사용할 수 있다.
- 그렇다면 이런 연산을 지원하기 위해 어떤 관례를 사용하는지 알아보자.
📌 인덱스로 원소에 접근 : get과 set
- 우리는 이미 코틀린에서 맵의 원소에 접근할 때나 자바에서 배열 원소에 접근할 때 모두 각괄호( [ ] )를 사용한다는 사실을 알고 있다.
- 같은 연산자를 사용해 변경 가능 맵에 키 / 값 쌍을 넣거나 이미 맵에 들어있는 키 / 값 관계를 변경할 수 있다.
mutableMap[key] = newValue
- 이제 이 코드가 어떻게 동작하는지 살펴보면 코틀린에서는 인덱스 연산자도 관례를 따른다.
- 인덱스 연산자를 사용해 원소를 읽는 연산은 get 연산자 메소드로 변환되고, 원소를 쓰는 연산은 set 연산자 메소드로 변환된다.
- 다음 예제들은 이런 연산을 구현하고 사용하는 방법을 보여준다.
/* get 관례 구현하기 */
operator fun Point.get(index: Int) : Int { // "get" 연산자 함수를 정의한다.
return when(index) {
0 -> x // 5 ~ 7번째 줄은 주어진 인덱스에 해당하는 좌표를 찾는다.
1 -> y
else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
val p = Point(10, 20)
println(p[1])
// 20
- 위의 코드와 같이 get이라는 메소드를 만들고 operator 변경자를 붙이기만 하면 인덱스에 해당하는 컬렉션 원소를 읽을 수 있다.
- p[1]이라는 식은 p가 Point 타입인 경우 방금 정의한 get 메소드로 변환된다.
- 인덱스에 해당하는 컬렉션 원소를 쓰고 싶을 때는 set이라는 이름의 함수를 정의하면 도니다.
- 마찬가지로 앞에 operator 변경자를 붙여 사용하면 된다.
/* 관례를 따르는 set 구현하기 */
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int) { // "set"이라는 연산자 함수를 정의
when(index) {
0 -> x = value // 7 ~ 9번째 줄은 주어진 인덱스에 해당하는 좌표를 변경한다.
1 -> y = value
else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
val p = MutablePoint(10, 20)
p[1] = 42
println(p)
// MutablePoint(x=10, y=42)
📌 in 관례
- 컬렉션이 지원하는 다른 연산자로는 in이 있다.
- in은 객체가 컬렉션에 들어있는지 검사(멤버십 검사; Membership Test)한다.
- 이때, in 연산자와 대응하는 함수는 contains다.
/* in 관례 구현하기 */
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator frun Rectangle.contains(p: Point) : Boolean {
return p.x in upperLeft.x until lowerRight.x && // 범위를 만들고 "x" 좌표가 그 범위 안에 있는지 검사한다.
p.y in upperRight.y until lowerRight.y // "until" 함수를 사용해 열린 범위를 만든다.
}
val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect)
// true
println(Point(5, 5) in rect)
// false
- in의 우항에 있는 객체는 contains 메소드의 수신 객체가 되고, in의 좌항에 있는 객체는 contains 메소드에 인자로 전달된다.
📌 rangeTo 관례
- 범위를 만들기 위해선 .. 구문을 사용해야 한다.
- 예를 들어 1..10은 1부터 10까지 모든 수가 들어있는 범위이다.
- .. 구문 대신 범위를 만드는 관례로는 rangeTo() 함수가 있다.
- rangeTo 함수는 범위를 반환한다.
- 이 연산자를 아무 클래스에나 정의할 수 있다.
- 하지만 어떤 클래스가 Comparable 인터페이스를 구현하면 rangeTo를 정의할 필요가 없다.
- 코틀린 표준 라이브러리에는 모든 Comparable 객체에 대해 적용 가능한 rangeTo 함수가 들어있다.
- 이 rangeTo 함수는 범위를 반환하며, 어떤 원소가 그 범위 안에 들어있는지 in을 통해 검사할 수 있다.
operator fun<T: Comparable<T>> T.rangeTo(that: T) : ClosedRange<T>
- 이제 예제를 통해 rangeTo 관례에 대해 살펴보자.
/* 날짜의 범위 다루기 */
val now = LocalDate.now()
val vacation = now..now.plusDays(10) // now(오늘)부터 시작해 10일짜리 범위를 만든다.
println(now.plusWeeks(1) in vacation) // 특정 날짜가 날짜 범위 안에 들어가는지 검사한다.
// true
- 위의 예제에서 now..now.plusDays(10)이라는 식은 컴파일러에 의해 now.rangeTo(now.plusDays(10)) 으로 변환된다.
- rangeTo 연산자는 다른 산술 연산자보다 우선순위가 낮지만 혼동을 피하기 위해 괄호로 묶어주는 것이 좋다.
val n = 9
println(0 .. (n + 1)) // 0..n + 1이라고 써도 되지만 괄호를 치면 더 뜻이 명확해진다.
// 0..10
- 또한 0..n.forEach {} 와 같은 식은 컴파일할 수 없다.
- 범위 연산자의 우선순위가 낮기 때문에 범위의 메소드를 호출하기 위해서는 (0..n).forEach {} 와 같이 범위를 괄호로 둘러싸야 한다.
val n = 9
(0..n).forEach { print(it) } // 범위의 메소드를 호출하려면 범위를 괄호로 둘러싸야 한다.
// 0123456789
📌 for 루프를 위한 iterator 관례
- 코틀린에서는 iterator 메소드를 확장 함수로 정의하여 일반 자바 문자열에 대한 for 루프가 가능하다.
- 클래스 안에 직접 iterator 메소드를 구현하는 예제를 살펴보자.
/* 날짜 범위에 대한 이터레이터 구현하기 */
operator fun ClosedRange<LocalDate>.iterator() : Iterator<LocalDate> =
object : Iterator<LocalData> { // 이 객체는 LocalDate 원소에 대한 Iterator를 구현한다.
var current = start
override fun hasNext() = current <= endInclusive // compareTo 관례를 사용해 날짜를 비교한다.
override fun next() = current.apply { // 현재 날짜를 저장한 다음 날짜를 변경한다. 그 후 저장해둔 날짜를 반환한다.
current = plusDays(1) // 현재 날짜를 1일 뒤로 변경한다.
}
}
val newYear = LocalDate.ofYearDay(2017, 1)
val daysOff = newYear.minusDays(1) .. newYear
for (dayOff in daysOff) { println(dayOff) } // daysOff에 대응하는 iterator 함수가 있으면 daysOff에 대해 이터레이션한다.
// 2016-12-31
// 2017-01-01
- 위 코드에서 closedRange<LocalDate>에 대한 확장 함수 iterator를 정의했기 때문에 LocalDate의 범위 객체를 for 루프에 사용할 수 있게 된다.
728x90
반응형
'Studying > Kotlin' 카테고리의 다른 글
[Kotlin] 위임 패턴(Delegation Pattern)과 위임(Delegation) 그리고 상속(Inheritance) (2) | 2023.06.05 |
---|---|
[Kotlin In Action] 7장. 연산자 오버로딩과 기타 관례(4) - 구조 분해 선언과 component 함수 (0) | 2023.06.05 |
[Kotlin In Action] 7장. 연산자 오버로딩과 기타 관례(2) - 비교 연산자 오버로딩 (0) | 2023.06.02 |
[Kotlin In Action] 7장. 연산자 오버로딩과 기타 관례(1) - 산술 연산자 오버로딩 (0) | 2023.06.02 |
[Kotlin In Action] 6장. 코틀린 타입 시스템(4) - 컬렉션과 배열 (0) | 2023.06.01 |