반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item40. equals의 규약을 지켜라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item40. equals의 규약을 지켜라

어리둥절범고래 2024. 1. 13. 17:58
반응형

코틀린에는 두 가지 종류의 동등성(equality)이 있습니다.

 

 

구조적 동등성(structural equality)

 

equals 메서드와 이를 기반으로 만들어진 == 연산자(!= 포함)로 확인하는 동등성입니다.

a == b 는 a가 nullable이 아니라면 a.eqauls(b), a가 nullable이라면 a?.equals(b) ?: (b === null)로 변환됩니다.

 

레퍼런스적 동등성(referential equality)

 

=== 연산자(!== 포함)로 확인하는 동등성입니다.

두 피연산자가 같은 객체를 가리키면, true를 리턴합니다.

 

 

equals는 모든 클래스의 슈퍼클래스인 Any에 구현되어 있으므로, 모든 객체에서 사용할 수 있습니다.

서로 다른 타입의 두 객체를 비교하는 것은 허용되지 않으며, 같은 타입을 비교하거나 둘이 상속 관게를 갖는 경우에는 비교할 수 있습니다.

 

 

 

equals가 필요한 이유

 

equals의 기본 동작은 다음과 같습니다.

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 한정자를 붙여서 데이터 클래스로 정의하면, 자동으로 동등성은 기본 생성자의 프로퍼티만 비교하게 됩니다.

데이터 클래스는 내부에 어떤 값을 갖고 있는지가 중요하므로, 이와 같이 동작하는 것이 좋습니다.

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

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

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

이는 일부 프로퍼티만 비교해야할 경우에도 유용합니다.

data class DateTime(
    private var millis: Long = 0L,
    private var timeZone: TimeZone? = null
) {
    private var asStringCache = ""
    private var changed = false
    
    //...
}

참고로, 기본 생성자에 선언되지 않은 프로퍼티는 copy로 복사되지 않습니다.

상황에 따라 equals를 직접 구현해야 하는 경우가 있을 수도 있습니다.

class User(
    val id: Int,
    val name: String,
    val surname: String
) {
    override fun equals(other: Any?): Boolean = 
        other is User && other.id == id
        
    override fun hashCode(): Int = id
}

equals를 직접 구현해야 하는 경우를 정리해 보면, 다음과 같습니다.

 

  • 기본적으로 제공되는 동작과 다른 동작을 해야 하는 경우
  • 일부 프로퍼티만으로 비교해야 하는 경우
  • data 한정자를 붙이는 것을 원하지 않거나, 비교애햐 하는 프로퍼티가 기본 생성자에 없는 경우

 

 

equals의 규약

 

어떤 다른 객체가 이 객체와 같은지 확인할 때 사용하며, 구현은 반드시 다음과 같은 요구 사항을 충족해야 합니다.

 

  • 반사적(reflexive) 동작: x가 널(null)이 아닌 값이라면, x.equals(x)는 true를 리턴해야 한다.
  • 대칭적(symmetric) 동작: x와 y가 널이 아닌 값이라면, x.equals(y)는 y.equals(x)와 같은 결과를 출력해야 한다.
  • 연속적(transitive) 동작: x, y, z가 널이 아닌 값이고 x.equals(y)와 y.equals(z)가 true라면, x.equals(z)도 true여야 한다.
  • 일관적(consistent) 동작: x와 y가 널이 아닌 값이라면, x.equals(y)는 여러 번 실행하더라도 항상 같은 결과를 리턴해야 한다.
  • 널과 관련된 동작: x가 널이 아닌 값이라면, x.equals(null)은 항상 false를 리턴해야 한다.
  • 공식 문서에는 없는 규약이지만, equals, toString, hashCode의 동작은 빠를 거라 예측되므로, 빠르게 동작해야 한다.

 

 

URL과 관련된 equals 문제

 

equals를 잘못 설계한 예로는 java.net.URL이 있습니다.

java.net.URL 객체 2개를 비교하면 동일한 IP 주소로 해석될 때는 true, 아닐 때는 false를 리턴합니다.

문제는 이 결과가 네트워크 상태에 따라 달라지며, 동일한 IP 주소라도 네트워크가 끊겨있으면 false를 출력합니다.

 

 

반응형