1. 동등성 연산자: equals
- 코틀린은 관례에 따라 == 연산자 호출을 equals 메소드 호출로 컴파일한다. != 연산자를 사용하는 식도 equals 호출로 컴파일된다. 이 연산자의 경우 내부에서 인자가 널인지 검사해서 널이 아닌 경우에만 equals를 호출한다.
a == b -> a?.equals(b) ?: (b==null)
class Point2(val x:Int, val y:Int){
override fun equals(obj: Any?): Boolean{
if(obj === this) return true //최적화: 파라미터가 this와 같은 객체인지
if(obj !is Point) return false
return obj.x==x && obj.y==y //Point로 스마트 캐스트 후 접근
}
}
식별자 비교 연산자(===)를 사용해 equals의 파라미터가 수신 객체와 같은지 비교함으로써 최적화 한다. 다른 연산자 오버로딩 관례와 달리 equals는 Any에 정의된 메소드이므로 override가 필요하다. (동등성 비교를 모든 코틀린 객체에 적용) 상위 클래스에서 정의 된 메소드를 오버라이드하는 것이므로 앞에 따로 operator를 붙이지는 않아도 자동으로 상위 클래스의 operator 지정이 적용된다. 또한 Any에서 상속 받은 equals가 확장 함수보다 우선순위가 높기 때문에 equals를 확장함수로 정의할 수 없다.
2. 순서 연산자: compareTo
- 자바에서는 <나 > 등의 연산자로는 원시 타입의 값만 비교 가능했다. 그러나 코틀린은 Comparable 인터페이스 안에 있는 compareTo 메소드를 호출하는 관례를 제공한다. 따라서 비교 연산자는 compareTo 호출로 컴파일된다.
a >= b -> a.compareTo(b) >= 0
class Person(val firstName:String, val lastName:String): Comparable<Person>{
override fun compareTo(other: Person): Int {
return compareValuesBy(this, other, Person::lastName, Person::firstName)
}
}
compareValuesBy 함수는 두 객체와 여러 비교 함수를 인자로 받는다. 첫번째 비교 함수에 두 객체를 넘겨서 두 객체가 같지 않다는 결과가 나오면 그 결과 값을 즉시 반환하고, 두 객체가 같다는 결과가 나오면 두 번째 비교 함수를 통해 두 객체를 비교한다.
3. 인덱스로 원소에 접근: get과 set
- 인덱스 연산자도 관례를 따른다. 인덱스 연산자를 사용해 원소를 읽는 연산은 get 연산자 메소드로 변환되고, 원소를 쓰는 연산은 set 연산자 메소드로 변환된다.
operator fun Point.get(index: Int): Int{
return when(index){
0 -> x
1 -> y //주어진 인덱스에 해당하는 좌표를 찾는다.
else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
>>>val p = Point(10, 20)
>>>println(p[1])
20
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")
}
}
>>>val p2 = MutablePoint(10, 20)
>>>p2[1] = 42
>>>println(p2)
MutablePoint(x=10, y=42)
4. 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
}
>>>val rect = Rectangle(Point(10, 20), Point(50, 50))
>>>println(Point(20,30) in rect)
true
5. rangeTo 관례
- .. 연산자는 rangeTo 함수를 간략하게 표현하는 방법이다. rangeTo 함수는 범위를 반환한다.
>>>val now = LocalDate.now()
>>>val vacation = now..now.plusDays(10) //10일짜리 범위
>>>println(now.plusWeeks(1) in vacation)
true
now..now.plusDays(10)은 컴파일러에 의해 now.rangeTo(now.plusDays(10))으로 변환된다. 이때 rangeTo 함수는 Comparable에 대한 확장함수이다.
operator fun <T: Comparable<T>> T.rangeTo(that:T): ClosedRange<T>
6. for 루프를 위한 iterator 관례
- for 루프는 범위 검사와 똑같이 in 연산자를 사용하지만 이 경우 쓰이는 in의 의미는 다르다. for(x in list)의 경우 list.iterator()를 호출한 후 이터레이터에 대한 hasNext와 next 호출을 반복하는 식으로 변환된다.
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
object:Iterator<LocalDate>{
var current = start
override fun hasNext() = current <= endInclusive //compareTo 관례를 사용해 날짜를 비교
override fun next() = current.apply{
current = plusDays(1) //현재 날짜를 1일 뒤로 변경
}
}
>>>val newYear = LocalDate.ofYearDay(2022, 1)
>>>val daysOff = newYear.minusDays(1)..newYear //LocalDate 범위 객체 -> ClosedRange<LocalDate> 반환
>>>for(dayOff in daysOff){
... println(dayOff)
... }
2021-12-31
2022-01-01
rangeTo 함수는 ClosedRange의 인스턴스를 반환한다. 따라서 코드에서 ClosedRange<LocalDate>에 대한 확장 함수 iterator를 정의했기 때문에 LocalDate의 범위 객체를 for 루프에 사용할 수 있다.
'3-1기 스터디 > Kotlin' 카테고리의 다른 글
[3-2, 5주차] 연산자 오버로딩과 기타 관례(4) (0) | 2022.05.31 |
---|---|
[3-2, 3주차] 연산자 오버로딩과 기타 관례(3) (0) | 2022.05.17 |
[3-2, 1주차] 연산자 오버로딩과 기타 관례(1) (0) | 2022.04.06 |
[12주차] 코틀린 타입 시스템(2) (0) | 2022.01.30 |
[11주차] 코틀린 타입 시스템(1) (0) | 2022.01.21 |
댓글