본문 바로가기

개발/Kotlin

Item 42: Respect the contract of equals

Kotlin에서는 java의 object와 같이 모든 객체는 Any를 상속받는다.

이 Any에는 몇가지 잘 설계된 contract들이 있다.

 

- equals

- hashCode

- toString

 

Equality

Kotlin에서는 2가지의 Equality가 있다.

 

- Structural equality

equals method나 == 연산자를 통해 체크가 된다. a == b는 a.equals(b)와 같다. (nullable이면 a?.equals(b) ?: (b === null)

 

- Referential equality

=== 연산자를 통해 체크된다. 양 쪽이 같은 오브젝트를 가리킬 때만 true를 return한다.

 

equals는 Any에 구현되어 있기 때문에 모든 객체에서 사용가능하지만 다른 타입의 객체를 비교할 때는 사용이 불가능하다.

open class Animal
class Book

Animal() == Book() // error
Animal() === Book() // error

class Cat: Animal()

Animal() == Cat() // OK
Animal() === Cat() // OK

 

Equals가 필요한 이유

default로 구현되어 있는 것은 referential equality(===)같이 다른 오브젝트가 정확하게 같은 인스턴스인지를 체크하는 것이다.

class Name(val name:String)
val name1 = Name("Marcin")
val name2 = Name("Marcin")
val name1Ref = name1

name1 == name1 // true
name1 == name2 // false
name1 == name1Ref // true

name1 === name1 // true
name1 === name2 // false
name1 === name1Ref // true

 

data 클래스에서는 생성자에 포함된 모든 property들이 같으면 true를 반환한다.

data class FullName(val name: String, val surname: String)
val name1 = FullName("Marcin", "Moskała")
val name2 = FullName("Marcin", "Moskała")
val name3 = FullName("Maja", "Moskała")

name1 == name1 // true
name1 == name2 // true, because data are the same
name1 == name3 // false

name1 === name1 // true
name1 === name2 // false
name1 === name3 // false

 

모든 property를 비교하고 싶지 않을 때는 다음과 같이 구현하면 된다.

data class로 하면 비교하고 싶지 않은 property는 생성자에 넣지 않으면 된다.

이 경우, copy할 때 기본 생성자에 포함되지 않은 property들은 복사되지 않는다.

 

따라서, 우리가 equals를 직접 구현해야할 때는 다음과 같다.

 

- 로직이 default 값과 달라야할 때

- property의 subset만 비교하고 싶을 때

- 오브젝트를 data class로 만들고 싶지 않거나 기본 생성자에 포함되지 않은 값을 비교해야할 때

 

equals를 직접 구현할 때는 다음과 같은 성질을 만족해야한다.

 

- Reflexive: non-null value x에 대해 x.equals(x)는 항상 성립

- Symmetric: non-null value x, y에 대해 x.equals(y)가 참이면 y.equals(x)도 참이어야 한다.

- Transitive: non-null value x, y, z에 대해 x.equals(y), y.equals(z)가 참이면 x.equals(z)도 참이어야한다.

- Consistent: non-null value x, y에 대해 x.equals(y)를 여러 번 불러도 데이터가 바뀌지 않았다면 결과가 일관적이어야 한다.

- Never equal to null: non-null value x는 null과 항상 같지 않다. x.equals(null)은 false.

 

추가적으로 equals, toString, hashCode는 빨라야한다.

 

Symmetric 성질을 위반하는 equals

 

이 성질이 깨지면 다른 오브젝트에서 비교를 할 때, x to y로 비교할지 y to x로 비교할지 모르기 때문에 정상적으로 동작하지 않을 수 있다.

 

java.net.URL

이건 true일까 false 일까?

더보기

인터넷이 켜져 있으면 True, 꺼져있으면 False 라고 한다..!

 

안드로이드에서는 4.0부터 수정이 되었다.

이러한 문제 때문에 Kotlin/JVM을 사용하면 java.net.URL 대신 java.net.URI를 사용하도록 권장한다.