코틀린의 Any에는 잘 설정된 규약들을 가진 메서드들이 있다.
이 메서드들은 자바 때부터 정의되어 있던 메서드라 코틀린에서 중요한 위치에 있고 중요한 내용이므로 equals부터 하나씩 다뤄본다.
코틀린에는 두 가지 동등성이 있다.
구조적 동등성: equals 메서드와 이를 기반으로 만들어진 ==
연산자로 확인한다 (!=
포함)
a가 nullable이 아니라면 a == b
는 a.equals(b)
로 변환되고 a가 nullable이라면 a?.equals(b) ?: (b === null)
로 변환된다.
레퍼런스적 동등성: ===
연산자 (!==
포함)로 확인하는 동등성이다. 두 피연산자가 같은 객체를 가리키면, true를 리턴한다.
euqals
는 모든 클래스의 슈퍼클래스인 Any
에 구현되어 있어서 모든 객체에서 사용할 수 있다. 다만 연산자를 사용해서 다른 타입의 두 객체를 비교하는 것은 허용하지 않는다.
물론 상속 관계를 갖고 있으면 비교 가능하다
open class Animal
class Book
Animal() == Book() // or === also error
class Cat: Animal()
Animal() == Cat() // possible
Animal() === Cat() // also possible
다른 타입의 두 객체를 비교하는 것은 의미가 없으므로 이렇게 구현되어 있다.
Any 클래스속 equals 메서드는 디폴트로 ===
처럼 두 인스턴스가 완전히 같은지 비교한다. 이는 모든객체는 디폴트로 유일한 객체라는 것을 의미한다.
class Name(val name: String)
val name1 = Name("a")
val name2 = Name("a")
val name1Ref = name1
name1 == name1 // true
name1 == name2 // false
name1 == name1Ref // true
name1 === name1 // true
name1 === name2 // false
name1 === name1Ref // true
여기서 data
한정자를 붙여서 데이터 클래스로 정의하면 내부의 값들을 비교하며 동등성으로 동작한다.
data class Name(val name: String, val surname: String)
val name1 = Name("a", "Mosaka")
val name2 = Name("a", "Mosaka")
val name3 = Name("b", "Mosaka")
name1 == name1 // true
name1 == name2 // ture, 데이터가 같다
name1 == name3 // true
name1 === name1 // true
name1 === name2 // false
name1 === name3 // false
그래서 일반적으로 데이터 모델을 표현할 때는 data 한정자를 붙인다.
equals를 직접 구현해야 한다면 다음과 같다.
기본적으로 제공되는 동작과 다른 동작을 해야 하는 경우
일부 프로퍼티만으로 비교해야 하는 경우
data 한정자를 붙이는 것을 원하지 않거나, 비교해야 하는 프로퍼티가 기본 생성자에 없는 경우
코틀린 1.4.31을 기준으로 equals에는 아래와 같은 주석이 달려 있다
구현은 반드시 요구 사항을 충족해야 한다.
추가로 공식 문서엔 없는 규약이지만 equals
, toString
, hashCode
의 동작은 빨라야 한다.
이 요구 사항들은 자바 때부터 정의되었으며, 코틀린에서도 처음부터 정의된 내용입니다. 따라서 수많은 객체가 이러한 동작에 의존해서 만들어졌습니다.
equals를 굉장히 잘못 설계한 예로를 java.net.URL
이 있다.
객체를 비교해서 동일한 IP 주소로 해석되면 true, 아니면 false인데 문제는 이 결과가 네트워크 상태에 따라 달라진다.
import java.net.URL
fun main() {
val enWiki = URL("https://en.wikipedia.org/")
val wiki = URL("https://wikipedia.org/")
println(enWiki == wiki)
}
ip 주소가 같으면 일반적으로 true가 출력되는데 인터넷 연결이 끊겨 있으면 false를 출력한다.
이처럼 동등성이 네트워크 상태에 의존한다는 것은 잘못된 것이다.
equals
, hashCode
처리는 빠를거라 예상하는 네트워크 처리는 생각보다 느리게 동작한다.그래서 안드로이드는 안드로이드 4.0부터 이러한 내용이 수정되었다.
코틀린/JVM 또는 다른 플랫폼을 사용할 때는 java.net.URL
이 아니라 java.net.URI
를 사용하자
특별한 이유가 없는 이상, 직접 equals를 구현하는 것은 좋지 않다.
기본적으로 제공되는 것을 그대로 쓰거나 데이터 클래스로 만들어서 사용하는 것이 좋다
그래도 직접 구현해야 한다면 반사적, 대칭적, 연속적, 일관적 동작을 하는지 꼭 확인하고 final
로 선언하자.
만약 상속을 한다면 서브클래스에서 equals가 작동하는 방식을 변경하면 안된다는 것을 기억하자.
참고로 데이터 클래스는 언제나 final이다.