[Kotlin] 클래스, 객체, 인터페이스(3)

sw·2022년 3월 29일
0

1. 모든 클래스가 정의해야 하는 메소드

  • 문자열 표현: toString()
    주로 디버깅이나 로깅 시 이 메소드를 사용한다.
class Client(val name: String, val postalCode: Int){
    override fun toString() = "Client(name=${name}, postalCode=${postalCode})"
}

>>> val client = Client("홍길동", 2022)
>>> println(client)
Client(name=홍길동, postalCode=2022)

  • 객체의 동등성: equals()
    코틀린에서 == 연산자는 참조 동일성을 검사하지 않고 객체의 동등성을 검사한다. 따라서 == 연산은 equals를 호출하는 식으로 컴파일 된다. 참조 비교를 위해서는 === 연산자를 사용한다.
>>> val client1 = Client("홍길동", 1111)
>>> val client2 = Client("홍길동", 1111)
>>> println(client1 == client2)
false

Client 클래스의 객체 동등성을 만족시키려면 equals를 오버라이드 해야 한다.

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

  • 해시 컨테이너: hashCode()

    hash code란

    객체를 식별하는 하나의 정수 값. hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들기 때문에 객체마다 다른 값을 가지고 있다.

    hashCode 규약
    equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.

    equals()와 hashCode()를 같이 override해야 하는 이유
    HashSet, HashMap, HashTable
    1. hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시 코드값이 다르면 다른 객체로 판단한다.
    2. 해시 코드값이 같으면equals() 메소드로 다시 비교한다.
    이 2개 조건을 모두 만족해야 같은 객체로 판단하고 해시코드가 다르면 equals() 메소드로 비교해보지도 않는다.
class Client(val name: String, val postalCode: Int) {
    ...
    override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}



2. Data Class

  • 데이터를 저장하는 클래스로 toString, equals, hashCode를 생성할 필요도 없이 컴파일러가 자동으로 만들어준다.

  • equalshashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다.
    (주 생성자 밖에 정의된 프로퍼티는 고려 대상이 x)

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

  • 불변성 : copy() 메소드
    copy() 메소드는 객체를 복사하면서 일부 프로퍼티를 바꿀수 있게 해주는 메소드이며 자동으로 만들어진다.
    데이터 클래스의 프로퍼티에 var를 써도되지만 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.

    Why?

    HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우 불변성이 필수적이다. Key로 쓰인 데이터 객체의 프로퍼티를 변경했을 때 컨테이너 상태가 잘못될 수 있기 때문이다. 또한 다중 스레드 프로그램의 경우 불변 객체를 사용하면 스레드가 사용 중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야 할 필요가 줄어든다.

>>> val client = Client("홍길동", 1111)
>>> println(client.copy(postalCode = 2222).postalCode)
2222



3. 클래스 위임 : by 키워드 사용

  • 인터페이스를 구현할 때 by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다. 다시 말하면, 클래스를 다른 클래스에 위임하면 위임 클래스가 가지는 인터페이스 메소드를 참조 없이 호출하게 해준다.

  • 메소드 중 일부의 동작을 변경하고 싶은 경우 메소드를 오버라이드하면 컴파일러가 생성한 메소드 대신 오버라이드한 메소드가 사용된다.

class DelegatingCollection<T> : Collection<T> {
    private val innerList = arrayListOf<T>()
    
    override val size: Int get() = innerList.size

    override fun contains(element: T): Boolean = innerList.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean 
    = innerList.containsAll(elements)

    override fun isEmpty(): Boolean = innerList.isEmpty()

    override fun iterator(): Iterator<T> = innerList.iterator()
}

// by 키워드를 사용한다면 아래와 같이 쓸 수 있다.
class DelegatingCollection<T> (
    innerList: Collection<T>
) : Collection<T> by innerList {
	
    // 컴파일러가 생성한 메소드 대신 오버라이드한 메소드가 사용된다.
    override fun isEmpty(): Boolean {
        println("Empty!!!!")
        innerList.isEmpty()
    }
}



Reference

profile
끄적끄적

0개의 댓글