[TIL] 20230718: Kotlin In Action 4장 (2)

ERyukSa·2023년 7월 21일
0

TIL

목록 보기
5/6

학습 내용

1. sealed class

일반 클래스와 달리 상속에 대해 기본적으로 open이다. 그러나 멤버는 상속에 대해 final이다. 따라서, 프로퍼티나 메서드를 상속 가능하게 하려면 open 키워드를 붙여야 한다.

=> sealed class와 data class를 조합해서 자주 쓰이는 형태

sealed class ScreenState(open val data: Type) {

    data class Loading(override val data: Type) : ScreenState(data)
    data class Main(override val data: Type) : ScreenState(data)
    data class Sleep(override val data: Type) : ScreenState(data)
}

data class는 생성자에 파라미터를 가질 수 없다. 프로퍼티만 가질 수 있다. 그래서 val을 붙이면 그래도 에러가 난다. 왜냐면 sealed class의 멤버는 기본 final 상속이기 때문이다. 그래서 open을 붙이고, 하위 클래스에서는 override val data: Type 형식으로 공통 데이터를 정의하게 된다.

하위 클래스에 대해 when문으로 분기할 때 else문을 작성하지 않아도 되며, 빠뜨린 조건은 컴파일러가 잡아준다.

코틀린 1.0에서는 중첩 클래스 형태로만 상속 가능했지만 지금은 같은 패키지 안이라면 모두 상속 및 사용 가능하다.

sealed interface도 있다. sealed 클래스 내에 상태가 필요하면 sealed class를, 아니면 sealed interface를 사용하자. 아니면 상속 개수를 이유로 선택할 수도 있다.

2. protected constructor()

클래스를 protected constructor로 정의할 수 있다. 그러면 클래스 내부와 하위 클래스에서만 생성자를 호출할 수 있다.

주의할 점은 open과 함께 사용해야 주목적을 달성할 수 있다는 것이다. 왜냐면 코틀린 클래스는 final이므로 상속할 수가 없어서 open class가 아니라면 private constructor와 다를게 없기 때문이다.

3. init{}

init{}은 초기화 블록이다. 인스턴스가 생성될 때 실행된다.

주생성자와 함께 사용된다. 주생성자는 생성자 파라미터 정의와 프로퍼티 선언 및 초기화로 역할이 제한적이기 때문이다.

4. 클래스를 상속할 때는 뒤에 꼭 생성자를 호출해야 하고, 인터페이스는 생성자가 없으므로 괄호를 붙이지 않는다.

부모 클래스 뒤에 괄호를 붙이는 것은 자바에서 super() 처럼 부모의 생성자를 호출하는 것이다.

5. 생성자를 클래스 안에서만 호출하게 하고 싶으면 private contructor를 사용한다.

그리고 companion object에서 정적 함수로 객체 생성법을 제공할 수 있다.

6. 주생성자를 정의하지 않고 부생성자만 여럿 정의할 수도 있다. constructor 키워드를 사용한다.

부생성자는 this()로 또 다른 부생성자에게 객체 생성을 위임할 수 있다. 하지만 마지막엔 상위 클래스의 생성자(super)를 호출해야 한다.

7. 인터페이스에 추상 프로퍼티를 선언할 수 있다.

그러나 값을 지정할 순 없다. 인터페이스는 상태를 가질 수 없기 때문이다.

하지만 커스텀 getter/setter를 정의할 순 있다. backing field가 필요없기 때문이다. 단, setter에서 field 참조문을 사용할 수 없다.

8. 변수와 backing field

val 변수에 field를 사용하지 않는 getter를 정의하면 backing field가 생성되지 않는다.

field가 없는 var을 정의하려면 getter/setter 모두 field를 사용하지 않아야 한다. getter만 정의하면 컴파일러가 변수를 초기화하라고 에러를 낸다. 즉, getter만 정의해선 backing field가 없는 변수로 인식하지 않는다는 것이다.

9. equals, hashCode, toString.

자바에서는 데이터를 담는 클래스에 대해 equals, hashCode, toString를 직접 재정의해야 한다. 문제는 대부분 비슷한 방식으로 재정의 하는데도 매번 귀찮게 새로 정의한다는 것이다. IDE를 통해 편하게 자동 완성 시킬 수 있지만 코드 베이스가 보일러 플레이트로 번잡해진다는 문제가 남아 있다.

이것을 코틀린에서는 data class로 해결한다. 자세한 내용은 이따 아래에

11. 자바에서는 equals와 ==의 역할이 다르다.

자바에서 ==는 원시 타입 간에는 값의 비교, 참조 타입 간에는 메모리 주소를 비교한다. 반면, equals는 동등성을 비교한다. 객체의 동등성은 객체를 어떻게 정의하느냐에 따라 다르다. 그래서 equals를 요구 사항에 맞게 재정의해야 한다.

하지만 코틀린에서는 ==문을 컴파일을 하면 equals()가 된다. 그래서 자바에서는 equals를 ==를 같은 목적으로 사용하면 안되는 것과 달리 코틀린에선 equals 대신 ==을 사용해도 된다.

코틀린의 참조 비교는 ===이다.

12. toString, equal, hashCode 오버라이딩 이유

toString: 로그,디버깅을 목적으로 인스턴스 정보를 원하는 방식으로 출력하도록 바꾸고 싶을 때

equals는 클래스만의 특별한 동등성 검사가 필요할 때

hashCode: hash 자료구조에서 equals() 결과로 true를 반환하는 두 객체가 같은 hashCode 값을 반환하도록 만들기 위해 오버라이딩 한다.

13. hashCode 오버라이딩 추가 설명

hash 자료구조에서 객체를 key로 하여 자료를 조회할 때 입력된 객체가 자료구조에 저장되어 있는지 확인하기 위해 먼저 hashCode 값을 비교하고 같으면 equals 값을 비교한다.
따라서, equals 값이 같아도 hashCode를 오버라이딩 안하면 자료구조는 다르다고 판단한다.

14. data class

data class는 데이터를 담는 클래스를 위해 오버라이딩 해야 했던 toString, equals, hashCode 함수를 컴파일러가 대신 내장 구현해준다.

equals는 주생성자에 있는 모든 프로퍼티의 동등성을 비교하고, hashCode도 모든 프로퍼티의 hashCode 값을 기반으로 새로운 해시값을 계산한다. 참고로, 둘 다 주생성자 밖에 있는 프로퍼티는 고려하지 않는다.

copy 함수도 제공한다. copy는 데이터 클래스를 불변적으로 더 쉽게 다룰 수 있도록 도와준다. 객체를 다른 메모리에 복사하면서 일부 프로퍼티 값을 변경할 수 있다. 복사된 객체는 원본에 영향을 끼치지 않는다.

느낀점

가시성과 상속 제한 키워드가 여러 개라서 어려웠다. 자바랑 달라서 더 헷갈렸다.

미루지 말자

0개의 댓글