코틀린에서는 산술 연산자와 마찬가지로 원시 타입 값뿐 아니라 모든 객체에 대한 비교 연산을 수행할 수 있다. equals나 compareTo를 호출해야 하는 자바와 달리 코틀린에서는 == 비교 연산자를 직접 사용할 수 있다.
== 연산자 호출은 equals 메소드 호출로 컴파일되며, != 연산자를 사용하는 식도 equals 호출로 컴파일된다.
==와 !=는 내부에서 인자가 널인지 검사하므로 다른 연산과 달리 널이 될 수 있는 값에도 적용할 수 있다.
7.1절에서 사용한 Point 클래스의 경우 data라는 표시가 붙어있으므로 컴파일러가 자동으로 equals를 생성해준다. 직접 equals를 구현한다면 다음과 비슷한 코드가 된다.
class Point(val x: Int, val y: Int) {
override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other !is Point) return false
return other.x == x && other.y == y
}
}
식별자 비교(identify equals) 연산자 ===를 사용해 equals의 파라미터가 수신 객체와 같은지 살펴본다. 식별자 비교 연산자는 자바 == 연산자와 같다. 따라서 ===는 자신의 두 피 연산자가 서로 같은 객체를 가리키는지(원시 타입인 경우 두 값이 같은지) 비교한다. equals를 구현할 때는 ===를 사용해 자기 자신과의 비교를 최적화하는 경우가 많다. ===를 오버로딩할 수는 없다.
Kotlin | Java |
---|---|
== | equals |
=== | == |
equals는 Any에 정의된 메소드이므로 override가 붙어있다. Any의 equals에는 operator가 붙어있지만 그 메소드를 오버라이드하는 하위 클래스 메소드 앞에는 operator 변경자를 붙이지 않아도 자동으로 상위 클래스의 operator 지정이 적용된다.
또한 Any에서 상속 받은 equals가 확장 함수보다 우선순위가 높기 때문에 equals를 확장 함수로 정의할 수 없다.
또한 컴파일러는 != 호출에 대해 equals의 반환 값을 반전시켜 돌려준다.
자바에서 값을 비교해야 하는 알고리즘에 사용할 클래스는 Comparable 인터페이스를 구현해야 한다. Comprable에 들어있는 compareTo 메소드는 한 객체와 다른 객체의 크기를 비교해 정수로 나타내준다. 하지만 자바에는 이 메소드를 짧게 호출할 수 있는 방법이 없다. <나 > 등의 연산자로는 원시 타입의 값만 비교할 수 있다. 다른 모든 타입의 값에는 element1.compareTo(element2)를 명시적으로 사용해야 한다.
코틀린도 똑같은 Comparable 인터페이스를 지원한다. 코틀린은 Comparable 인터페이스 안에 있는 compareTo 메소드를 호출하는 관례를 제공한다. 비교 연산자 (<, >, <=, >=)는 compareTo 호출로 컴파일된다. compareTo가 반환하는 값은 Int다. p1 < p2
는 p1.compareTo(p2) < 0
과 같다.
Person 클래스를 예시로, 사람을 비교할 때 주소록 순서(성을 비교하여 성이 같으면 이름을 비교)를 사용해보자.
class Person(
val firstName: String,
val lastName: String,
) : Comparable<Person> {
override fun compareTo(other: Person): Int {
return compareValuesBy(this, other, Person::lastName, Person::firstName)
}
}
fun main() {
val p1 = Person("Alice", "Smith")
val p2 = Person("Bob", "Johnson")
println(p1 < p2) // false
}
equals와 마찬가지로 Comparable의 compareTo에도 operator 변경자가 붙어있으므로 하위 클래스의 오버라이딩 함수에 operator를 붙일 필요가 없다.