코틀린 컴파일러는 equals, hashCode, toString 등과 같이 클래스가 구현해야 하는 메소드를 컴파일 과정에서 자동으로 생성해준다.

클래스를 예시로 들어보자

class Client(val name: String, val postalCode: Int)

문자열 표현: toString()

기본 제공되는 객체의 문자열 표현은 Client@e9f23b4 같은 방식인데, 이 기본 규칙을 바꾸려면 toString 메소드를 오버라이드해야 한다.

class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Client (name=$name, postalCode=$postalCode)"
    }
}

객체의 동등성: equals()

서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 간주해야 하는 경우 어떻게 해야 할까? 현재의 Client는 내부 데이터가 동일한 서로 다른 두 객체를 equals로 확인하면 false가 반환된다.

val client1 = Client("A", 1)
val client2 = Client("A", 1)
println(client1 == client2) // false

자바에서는 ==를 원시 타입과 참조 타입을 비교할 때 사용한다. 원시 타입의 경우 ==는 두 피연산자의 값이 같은지 비교한다(동등성(equality)). 반면 참조 타입의 경우 ==는 두 피연산자의 주소가 같은지를 비교한다(참조 비교(reference comparision)). 따라서 자바에서는 두 객체의 동등성을 알려면 equals를 호출해야 한다. equals 대신 ==를 호출하면 문제가 될 수 있다.

Kotlin에서 == 연산자는 내부적으로 equals를 호출해서 객체를 비교한다. 참조 비교를 위해서는 === 연산자를 사용할 수 있다. Kotlin의 ===는 자바에서 객체의 참조를 비교할 때 사용하는 == 연산자와 같다.

이제 equals를 추가한 Client 클래스를 살펴보자

class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Client (name=$name, postalCode=$postalCode)"
    }

    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client) {
            return false
        }
        return name == other.name && postalCode == other.postalCode
    }
}

코틀린의 is 검사는 자바의 instanceof와 같다.

코틀린에서는 override 변경자가 필수여서 other: Any? 대신 other: Client를 파라미터로 입력받는 equals 함수를 작성할 수 없다.

현재 Client는 제대로 동작하지 않는 경우가 있는데, hashCode를 구현하지 않아서 그렇다.

해시 컨테이너: hashCode()

자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드해야 한다. 그 이유를 알아보자.