본문 바로가기
책을 읽겠습니다!/Kotlin in Action

[Kotlin in Action] 4장 클래스, 객체, 인터페이스

by Unagi_zoso 2023. 10. 12.

자바와 달리 클래스와 인터페이스 final이며 public. 중첩 클래스는 기본적으로 내부 클래스가 아니라 외부 클래스에 대한 참조가 없다.
짧은 주 생성자 구문으로도 거의 모든 경우를 잘 처리할 수 있다. 복잡한 초기화 로직 수행할 경우 대비한 완전한 문법 존재. 프로퍼티도 마찬가지.

data class 선언 시 컴파일러가 일부 표준 메소드를 생성해준다. 코틀린 언어가 제공하나ㅡㄴ 위임을 사용하면 위임을 처리하기 위한 준비 메소드를 직접 작성할 필요가 없다.
클래스, 인스턴스를 동시에 선언하면서 만들 때 쓰는 object 키워드에 대해서도 나온다. 싱글턴 클래스, 동반 객체 companion object, 객체 식 object expression (자바의 무명 클래스에 해당) 을 표현할 때 object 키워드를 쓴다.

클래스 계층 정의

코틀린 인터페이스

자바 8 인터페이스와 비슷, 추상 메소드 뿐만 아니라 구현이 있는 메소드도 정의할 수 있다. (자바8의 디폴트 메소드와 비슷). 다만 아무런 상태(필드)도 들어갈 수 없다.

같은 이름의 메소드를 두 인터페이스에서 받으면 무조건 오버라이드 해야해

override fun showOff() {
    super<Clickable>.showOff()
    super<Focusable>.showOff()
}

super로 어떤 상위 타입의 멤버 메소드 호출할껀지 지정가능하다. 여러 개 인터페이스 받을 경우

참고 코틀린은 자바 6와 호환되게 만들었다. 그래서 디폴트 메소드를 지원하지 않는다. 코틀린은 디폴트 메소드가 있는 인터페이스를 일반 인터페이스와 디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다. 자바에서 코틀린 디폴트 메소드 구현 어렵다.

open final, abstract 변경자: 기본적으로 final

fragile base class 취약한 기반 클래스라는 문제는 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스를 변경함으로 깨져버린 경우 발생. 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙(어떤 메소드를 어떻게 오버라이드해야하는지)을 제공하지 않는다면 그 클래스의 클라이언트느 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다. 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위 클래스의 동작이 예기치 않게 바뀔 수도 있다는 면에서 기반 클래스는 '취약'하다.
이펙티브 자바에서는 '상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라'라는 조언을 한다. 특별히 하위 클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다. 코틀린의 클래스와 메소드는 기본적으로 final이다. 그래서 상속 허용하려면 open 적어야한다. 오버라이드하고싶으면 메소드나 프로퍼티 앞에 open 붙여야한다.
오버라이드한 ㅎ메소드는 기본적으로 열려있따.

열린 클래스와 스마트 캐스트

클래스의 기본적인 상속 가능 상태를 final로 함으로써 다양한 경우에 스마트 캐스트가 가능하다. 스마트 캐스트는 타입 검사 뒤 변경될 수 없는 변수에만 적용 가능하다. 클래스 프로퍼티의 경우 이는 val이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미다. 이 요구 사항은 프로퍼티가 final이어야만 한다는 뜻이다. 그렇지 않으면 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구 사항을 깰 수 있다. 프로퍼티는 기본적으로 final이기 때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다. 프로퍼티도 열려있다는건 프로퍼티에 접근자 말하나ㅡㄴ건가?

코틀린에서도 클래스를 abstract로 선언할 수 있따. 인스턴스화 할 수 없다. 추상 클에는 구현이 없는 추상 멤버가 있기 때문에 하위 클래스에서 그 추상 멤버를 오버라이드해야만하는게 보통이다. 추상 멤버는 항상 열려있다. 따라서 추상 멤버 앞 open 변경자를 명시할 필요가 없다.

abstract class Animated {
    abstract fun animate() // 추상 함수. 이 함수에는 구현이 없다. 하위 클래스에서는 이 함수를 반드시 오버라이드해야 한다.
    open fun stopAnimating() {} // 추상 클래스에 속했더라도 비추상 함수는 기본적으로 파이널이지만 원한다면 open으로 오버라이드를 허용할 수 있다.
    fun animateTwice() {}
}

인터페이스 멤버의 경우 final, open, abstract를 사용하지 않는다.
인터페이스 멤버는 항상 열려 있으며 final로 변경할 수 없다. 인터페이스 멤버에게 본문이 없으면 자동으로 추상 멤버가 되지만, 그렇더라도 따로 멤버 선언 앞에 abstract 덧붙일 필요 없음

final : 오버라이드 불가 : 클래스 멤버의 기본 변경자다.
open : 오버라이드 가능 : 반드시 open을 명시해야 오버라이드 가능
abstract : 반드시 오버라이드해야 함 : 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다. 추상 멤버에는 구현이 있으면 안 된다. 본인이 구현 안하고 넘기는건가? 본인이 구현해도 되는데. 그러면 진짜 인터페이스와 차이가 뭐야
override : 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 : 오버라이드 하는 멤버는 기본적으로 열림 하위 클래스에게 오버라이드 금지하려면 final 추가

가시성 변경자: 기본적으로 공개

코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다. 어떤 클래스 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부 코드를 깨지 않고 클래스 내부 구현을 변경할 수 있다. private, public, protected 있지만 기본 가시성은 자바와 다르다. 아무 변경자도 없으면 public이다. 자바의 기본 가시성인 패키지 전용 (package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스를 관리하기 위한 용도로만 사용한다. 이 대안으로 internal이란 가시성 도입. 모듈 내부라고 번역한단다. 인텔리J, 이클립스, 메이븐, 그레이들 등의 프로젝트가 모듈이 될 수 있따. 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다. 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바는 어떤 ㅍ로젝트라도 외부의. 패키지 전용 선언만 하면 쉽게 접근할 수 있어서 캡슐화 쉽게 깨졌다. 코틀린엣는 최상위 선언에 대해 private 가시성을 허용한다. 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다. 비공개 가기성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있따. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용한 방법.

변경자 | 클래스 멤버 | 최상위 선언
public(기본 가시성임) : 모든 곳에서 볼 수 있따 | 모든 곳에서 볼 수 있다.
internal | 같은 모듈 안에서만 볼 수 있따. | 같은 모듈 안에서만 볼 수 있다.
protected | 하위 클래스 안에서만 볼 수 있다. | (최상위 선언에 적용할 수 없음)
private | 같은 클래스 안에서만 볼 수 있다. | 같은 파일 안에서만 볼 수 있다.

코틀린은 public 함수인 giveSpeech 안에서 기보다 가시성이 더 낮은 (이 경우 internal) 타입인 TalkativeButton을 참조하지 못하게 한다. 어떤 클래스의 기반 타입 목록에 들어있는 타입이나 제네릭 클래스의 타입 파라미터에 들어있는 타입의 가시성은 그 클래스 자신의 가시성과 같거나 더 높아야 하고, 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더높아야 한다는 더 일반적인 규칙에 해당된다. 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근할 수 있게 보장한다. 여기서 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성 interanal로 바꾸고. TalkativeButton 클래스의 가시성을 public으로 바꾸어야한다.

protected 자바와 다르다. 오직 어떤 클래스나 그 클래스를 상속 한 클래스 안에서만 보인다. 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근할 수 없다.

코틀린의 public, protected, private 변경자는 컴파일된 바이트코드 안에서도 유지된다. 자바에서 똑같은 가ㅣ성을 사용해 선언한 경우와 같다. 예외는 private 클래스다. 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일.

internal 자바에 맞느게 없다 public 행.

코틀린에서 접근할 수 없는 대상을 자바에서 접근할 수 있는 경우 있다. 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서 접근할 수 있다. 또 코틀린 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서 접근할 수 있다. 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다(mangle) 사실을 기억하자. internal 멤버를 사용할 순 있지만 그지같이 만든다. 첫 번째 이유는 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드 하는 경우를 방지하기 위함ㅇ미고, 두번쨰는 실수로 internal 클래스를 모듈외부에서 사용하는 일을 막기 위함이다.

코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근할 수 없다.

내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스

클래스 안에 다른 클래스 선언가능 도우미 클래스를 캡슐화하거나 코드 정의를 그 코드를 사용하는 곳 가까이에 두고싶을 때 유용하다.차이로는 중첩 클래스 는 명시적으로 요청하지 않는 한바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 것이다.

자바에서다른 클래스 안에 정의한 클래스는 자동을 내부 클래스가 된다.그래서 내부 클래스는 묵시적으로 외부클래스를 참조하고 외부클래스는 직렬화불가라 실패. static 클래스 선언하면 바깥쪽 클래스에 대한 묵시적인 참조 사라진다.

코틀린 중첩 클래스에 아무런 변경자 붙지 않으면 자바 static 중첩 클래스와 같다. 이르 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner 변경자를 붙여야한다.

클래스 B 안에 정의된 클래스 A | 자바에서는 | 코틀린에서는
중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장하지 않음 | static class A | class A
내부 클래스( 바깥 클래스 참조 저장) | class A | inner class A

내부 클래스 Inner 안에서 바깥쪽 Outer 참조 접근하려면 this@Outer라고 쓰면된다. private도 접근가능할까.

클래스 계층을 만들되 그 계층에 속한 클래스의 수를 제한하고 싶은 경우 중첩 클래스를 쓰면 편리하다.
연관된 클래스 마늘고싶은데 무작정 수 늘리기 그러면 클래스 안에 선언해라..

봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한

상위 클래스인Expr에는 숫자를 표현하는 Num과 덧셈 연산을 표현하는 Sum이라는 두 하위 클래스가 있다. when 식에서 이 모든 ㅎ아위 클래스를 처리하면 편리하다. 하지만 when 식에서 Num, Sum 아닌 경우 처리하는 else 분기 넣어줘야한다.
else 마ㄸㅇ한 반환값 없어 예외던진다.

항상 디폴트 분기 추가하는게 편하지 않다. 디폴트 분기가 있으면 이런 클래스 계층ㅇ에 새로운 하위 클래스르 추가하더라도 컴파일러가 when이 모든 겨우를 제대로 검사하는지 알 수 없다.

인터페이스 구현을 통해 식 표현해도 참 쓸만하다.

interface Expr
class Num(val value: Int) : Expr
class SUm(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.right) + eval(e.left)
        else ->
            throw IllegalArgumentException("Unknown expression")
     }

인터페이스 인자로 받아 그걸 구현한 클래스 받아서 타입 캐스팅 후 거기에 접근할 수 있는데 when이라 보니 else로 디폴트처리를 해야한다.
새로운 클래스 추가되면 별다른 경고없이 else에 뭉뜨그려져서 실수할 확률이 크다.
그래서 sealed 를 쓴다.

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval (e: expr) : Int =
    when (e) {
        is Expr.Num -> e.value
        is Expr.Sum -> eeval(e.right) + eval(e.left)
    }

when 식에서 sealed 클래스 모든 하위 클래스 처리하면 디폴트 필요없다. sesaled는 자동으로 open.

봉인된 클래스는 클래스 외부에 자신을 상속한 클래스를 둘 수 없다. 기존 인터페이스를 좀 더 깐깐하게 잘 조직적으로 구현할 수 있을 거 같아 하위클래스가 그렇게 많지 않고 사용되는 목표가 좁고 구체적인 클래스에선 인터페이스 말고 얘를 고려해볼 수 있을거 같다. 아닌가?!

내부적 Expr 클래스는 private 생성자 가진다. 인터페이스를 정의할 순 없다. 허용하면 자바 쪽에서 구현하지 못하게 막을 수 있는 수단이 없다.

코틀린 1.0에서는 모든 하위 클래스가 중첩 클래스여야했지만 1.1 부터는 실드 클래스와 같은 파일의 아무데서나 봉인된 클래스를 상속한 하위 클래스를 만들 수 있고, 데이터 클래스로 하위 클래스를 정의할 수 있다.

잘 보면 상속 시 : Expr()라고 되어있다. 생성자냐!!!

뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언

코틀린은 주 생성자(클래스 초기화할 때 주로 사용하는 간략한 생성자, 클래스 본문 밖에서 정의한다.) 와 부 생성자 (클래스 본문 안에서 정의한다) 또한 코틀린에서는 초기화 블록 initializer block을 통해 초기화 로직을 추가할 수 있다.

클래스 초기화: 주 생성자와 초기화 블록

클래스 이름 뒤 오는 괄호로 두러싸인 코드를 주 생성자라고 부른다.. 생성자 파라미터를 지정하고, 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적에 쓰인다. 명시적으로 풀어서 사용할 수도 있다.

class User(val nickname: String)

class User constructor(_nickname: String) {
    val nickname: String
    init {
        nickname = _nickname
    }
}

멋지다!!!
constructor와 init이란 키워드가 있다. constructor는 주 생성자나 부 생성자 정의를 시작할 때 사용한다. init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 클래스의 객체가 만들어질 때 (인스턴스화될 때) 실행될 초기화 코드가 들어간다. 주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언할 수 있다.

생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 파라미터를 구분해준다. 다른 방법으로 자바에서 쓰는 방식처럼 this.nickname = nickname 같은 식으로 생성자 파라미터와 프로퍼티의 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.

이 예제에서는 nickname 프로퍼티 초기화 코드를 nickname 프로퍼티 선언에 포함시킬 수 있어서 초기화 코드를 초기화 블록에 너을 필요가 없다. 주 생성자 앞에 별다른 애노테이션이나 가시성 변경자가 없다면 constructor를 생략해도 된다.

class User(_nickname: String) {
    val nickname = _nickname
}

프로퍼티를 초기화하는 식이나, 초기화 블록 안에서만 주 생성자의 파라미터를 참조할 수 있다.

class User(val nickname: String) // 파라미터 앞에 val 붙임으로 프로퍼티화 가능

default 값도 넣어줄 수 있다.

val hey = User("혜원", isSubscribed = false)

이름 지정해주는 거 전부 다 해줘야했나? 뒤죽박죽이면 힘든가..

코틀린에서 모든 새엇ㅇ자 파라미터에 디폴트 값 지정하면 자동으로 파라미터가 없는 생성자를 만들어준다. 자동으로 만드러진 노알그 컨스트는 디폴트값으로 초기화한다 DI 프레임워크 같은 파라미터 없는 생성자를 통해 객체 생성하는 라이브러리와의 통합 쉽게 해준다.

클래스에 기반 클래스가 있다면 주 생성자에서 기반 클래스의 생성자를 호출해야 할 필요가 있다. 기반 클래스를 초기화하려면 기반 클래스 이름 뒤에 괄호를 치고 생성자 인자를 넘긴다.

open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }

클래스를 정의할 때 별도로 생성자를 정의하지 않으면 디폴트 생성자 만들어준다.

그 클래스 상속하는 하위 클래스는 반드시 Button 클래스의 생성자를 호출해야한다.

이러한 규칙으로 인해 기반 클래스의 이름 뒤에는 꼭 빈괄호가 들어간다. ( 생성자 인자가 있다면 넣어야하지만) .. 관례란 말이야? 상속의 기반은 디폴트 생성자가 있도록 유지하자는

그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무 괄호도 없다. 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름 뒤에 괄호가 붙었는지 살펴보면 쉽게 기반 클래스와 인터페이스를 구별할 수 있다. 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고싶다면 모든 생성자를 private으로 만들면된다. 이러면 하위 클래스도 못 만들려나?

class Secretive private constructor() {}

유일한 주 생성자가 비공개됐다.

COmpanion object에 대해 설명하면 동반 객체 안에서 이런 비공개 생성자를 호출하면 좋은 이유가 나온다.


비공개 생성자에 대한 대안

유틸리티 함수 전용 클래스는 인스턴스화 필요 없다. 최상위로 빼나? 그래도 의미있게 클래스 안에 둘려나.
싱글턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야한다. 자바에서는 private 생성자 정의해서 인스턴스화를 막는다. 자바는 언어에서 지원한다. 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.

실제 대부분 클래스 생성자는 단순하다. 파라미터 없는 클래스도 많고. 생성자가 받은 값을 프로퍼티에 설정하는 생성자도 많다.

부 생성자: 상위 클래스를 다른 방식으로 초기화

생성자 여러 개 있으면 좋은 상황있다. 자바 보다 생성자 수가 적다. 생성자 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름 붙은 인자 문법을 사용해 해결한다. 오버로드 생성자는 이렇게 처리할 수 있겠다.

하위 클래스가 상위 클래스의 생성자를 호출함으로 생성을 위임한다.

생성자에서 this()를 통해 클래스 자신의 다른 생성자를 호출할 수 있다.
이를 통해 또 위임할 수 있다.

부생성자가 필요한 주된 이유는 자바와의 상호운용성, 파라미터목록이 다른 생성 방법이 존재할 수 있다.

인터페이스에 선언된 프로퍼티 구현

코틀린에서는 인터페이스에 추상 프로ㅓ티 선언을 넣을 수 있다.

interface User {
    val nickname: String
}

이는 이를 구현하는 클래스가 이 값을 얻을 수 있는 방법을 제공해야한다. 인터페이스에는 이를 뒷받침하지 않는다. 사실 인터페이스에는 아무 상대 포함 불가. 상태 저장해야하면 인터페이스 구현한 하위 클래스에서 상태 저장을 위한 프로퍼티 등을 마들어야한다.

interface User {
    val nickname: String
}

class PrivateUser(override val nickname: String) : User
class SubscribingUser(val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}

privateUser는 주 생성자 안에 프로퍼티를 직접 선언하는 간결한 구문을 사용한다.
이 프로퍼티는 User의 추상 프로퍼티를 구현하고 있으므로 override를 표시해야한다.

SubscribingUser는 커스텀 게터로 nickname 프로퍼티를 설정한다. 이 프로퍼티는 두시받침하느 필드에 값을 저장하지 않고 매번 이메일 주소에서 별명을 계산해 반환한다.

FacebookUser는 초기화식으로 닉네임을 초기화한다. 페이스북 접속해 인증을 거친 후 원하는 데이터를 가져와야 해서 비용이 많이 들 수도 있다. 객체 초기화 하는 단계에 한 번만 getFacebookName을 호출하게 설계한다.

구독, 페이스 구현 차이에 주의해라. 비슷해 보여도 구독은 호출해 계산하는 커스텀 게터를 활용하고 페이스북은 객체 고히ㅘ시 계산한 데이터를 뒷받침하는 필드에 저장했다가 불러온다.

인터페이스에 추상 프로퍼티 말고 그냥 프로퍼티 선언할 수 있다. 게터, 세터 있는. 게터, 세터 뒷받침하는 필드를 참조할 수 없다. ( 뒷받침하는 필드가 있다면 인터페이스에 상태를 추가하는 셈인데 인터페이스는 상태를 정할 수 없다.

interface User {
    val email: String
    val nickname: String
        get() = email.substringBefore('@')
}

이 인터페이스에는 추상 프로퍼티인 email과 커스텀 게터가 있는 nickname 프로퍼티가 함께 들어있다. 하위 클래스는 추상 프로퍼티인 email을 반듣시 오버로드해야한다. 반면 nickname은 오버로드할 필요 없이 사용할 수 있다.

인터페이스에 선언된 프로퍼티와 달리 클래스에 구현된 프로퍼티는 뒷받침하는 필드를 원하는 대로 사용할 수 있다...?

게터와 세터에서 뒷받침하는 필드에 접근

어렵네... 인터페이스 프로퍼티...

지금까진 1. 값을 저장하는 프로퍼티와 커스텀 접근자에서 매번 값을 계산하는 프로퍼티에 대해 봤다. 두 유형 조합해 어떤 값을 저장하되 그 값을 변경하거나 읽을 때마다 정해진 로직을 실행하는 유형의 프로퍼티를 만드는 방법을 살펴보자. 커스텀 세터랑 뭐가 다른겨
값을 저장하는 동시에 로직을 실행할 수 있게 하기 위해서는 접근자 안에서 프로퍼티를 뒷받침하는 필드에 접ㅈ근할 수 있어야한다.
이게 필드와 접근자를 같이 보는 개념이라 살짝 햇갈리는거 같다.

class User(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println("""
                Address was changed for $name:
                "$field" -> "$value".""".trimIndent()) // 뒷받침하는 필드값 읽음
             field = value 뒷받침하는 필드 값 변경
         }
 }

커스텀 세터를 정의해 추가 로직을 실행한다.

젖ㅂㅈ근자의 본문에서는 field라는 특별한 식별자를 통해 뒷받침하는 필드에 접근할 수 있다. 게터에서 field 값을 읽을 수만 있고, 세터에서는 field 값을 읽거나 쓸 수 있다.

뒷받침하는 필드가 있는 프로퍼티와 그런 필드가 없는 프로퍼티라는게 있네 필드없는 프로퍼티가 어디있냐고!!!!!!!!!!!!!!!!!!!!!!!!

클래스의 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는 방법이나 쓰는 방법은 두십다침하는 필드의 유무와 관계없다. 컴파일러는디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다. 다만 field를 사용하지 않는 커스텀 접근자 구현을 정의하면 뒷받침하는 필드는 존재하지 않는다. 서브스크 같이 말하는건가.
프로퍼티가 val인 경우 게터에 field가 없으면 되지만, var인 경우 게터, 세터 모두 없어야한다. 그래야 필드가 없어진다.

가시성을 바꿀 필요가 있을 때가 있다.

get, set 가시성을 변겨알 수 있다.

프로퍼티에 대해 나중에 다룰 내용

  • lateinit 변경자를 널이 될 수 없는 프로퍼티에 지정하면 프로퍼티 생성자가 호출된 다음 초기화한다.

  • 요청이 들어오면 비로소 초기화 되는 지연 초기화 프로퍼티는 더 일반적인 위임 프로퍼티의 일종이다.

  • 자바 프레임워크와 호환성을 위해 자바의 특징을 콭ㄹ레에서 엠ㄹ레이션하는 애노테이션을 활용할 수 있다.

    @JvmField 애노테이션을 프로퍼티에 붙이면 접근자가 없는 public 필드를 노출시켜준다. const 변경자를 사용하면 애노테이션을 더 편리하게 다룰 수 있고 원시 타입이나 String 타입인 값을 애노테이션의 인자로 활용할 수 있다.

    값- 객체 클래스를 더 편하게 작성하는 방법인 data 클래스에 대해 다룬다.

    데이터 클래스

    어떤 클래스가 데이터를 저장한다면 toString, equals, hashCode를 반드시 오버라이드해야한다. 안그러면 해쉬셋 비교 시 해쉬코드가 틀려서 중복 못 거르거나 동등성 비교를 못하거나 문자열출력 시 해쉬값이 나오게괸다. 이걸 원래 직접 정의해야했다. 코틀린은 data 를 앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.

    data class Client(val name: String, val postalCode: Int)
  • 인스턴스 간 비교를 위한 equals

  • HashMap과 같은 해시 기반 컨테이너에서 키로 사용할 ㅅ 있는hashCode

  • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString

    equals는 모든 프로퍼티 값의 동등성을 확인한다.
    hashCode 모든 프로퍼티의 해시 값을 바탕을 계산한 해시 값을 반환한다.
    이 때 주 생성자 밖에 정의된 프로퍼티는 equals나 hashCode를 계산할 때 고려의 대상이 아니다.

    몇 가지 유용한거 더 알려준다.

    데이터 클래스와 불변성: copy

    데이터클래스의 프로퍼티가 꼭 val일 필요없다. 하지만 모든 프로퍼티를 읽깆 ㅓㄴ용으로 만들어서 데이터 클래스를 불변클래스로 만들라고권장한다. hashmap 같은데서 키로 쓰이는 객체의 프로퍼티가 변경가능하면 안 좋다. 불변을 쓰면 쉽게 추론 가능하다. 다중스레드 프로그램에서 이런 성질은 중요하다.복사해서 사용할 수 있게해서 동기화 시킬 일을 줄인다.

클래스위임 by

대규모 객체지향 시스템 설계할때 문제발생하는게 구현 상속에 의해서다. 하위 클래스가 상위 클래스의 메소드 중 일부 오버라이드하면 하위 클은 상위 클의 세부 구현 사항에 의존한다. 시스템 변함에 따라 상위클 구현이 바뀌거나 상위클에서 새로운 메소드 추가된다. 하위 클은 기존 가정이 깨져 코드갖 어상적인 동작 안할 가능성 존재

ㄱ래서 기본 final이다. 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 때 있다. 데코레이터패턴을 쓴다. 상속 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가제공하게 만들고 기존 클래스를 데코레이터 내부에 필드로유지하는 것이다. 이 때 새로 정의할 건 데코레이터의메소드에 새로 정의하고 ( 이때 기존 크래스의메소드나 필드를 활용할 수 있다) 기존 기능이 그대로필요한 부분은 데코레이터의메소드가 기존 클래스의 메소드에게 요청을 전달한다. 여기에 준비코드가 상당히 많다.

이런 위임을 언어가 제공하는 일급 시민 기능으로 지원한다는 점이 코틀릔의 장점
인터페이스를 구현할 때 by 키우드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이란 사실을 명시할 수 있다.

클래스 안의 모든 메소드 정의가 사라진다.컴파일러가 전달 메소드를 자동 생성하고 자동생성한 코드의 구현은 DelegationgCOllection에 있던 구현과 비슷하다.. 자동으로 해준다. 메소드 중 일부 동작 변경하고 싶은경우 메소드를 오버라이드하면 생성한 메소드 대신 오버라이드한게 쓰인다.

변수 하나 타입 같은거한테 by로 위임한다. 데코레이터 패턴을 편하게 해준다. 이너셋이 별 다른 일을 하는건 아니지 않나..

object 키워드:클래스선언과 인스턴스 생성

클래스 정의와 인스턴스 생성한다.

  • 객체 선언은 싱글턴을 정의하는 방법 중 하나다.
  • 동반 객체는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리를 메소드를 담을 때 ㅡ인다. 동박 객체 메소드에 접근할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있따.
  • 객체 식은 자바의무명 내부 클래스 대신 쓰인다.

객체 선언 싱글턴을쉽게 만든다.

객체 선언 안에도 프로퍼티, 메소드 , 초기화 블록이 들어갈 수 있다. 생성자는 만들 수 없다. 변수와 마찬가맂로 객체 선언에 사용한 이름뒤에 마침표를붙이면 객체에 속한 메소드나 프ㅗ퍼티에 접근할 수 있다. 확장가능한가.

객체 선언도 클래스나 인스턴스를 상속할 수 있다. 인터페이스를 구현해야하는데 상태를 유지할 필요없을 떄 사용하면 좋다.

  • 싱글톤과 의존관계 주입. 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지 않다. ㅇ객체 생성을 제어할 방법이 없고 생성자 파라밑를 지정할 수 없다. 생성을 제어할 수 없고 생성자를 파라미터를 지정할 수 없어 단위 테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다. 그런 기능 필요하면 의존관계 주입 프레임워크와 코틀린 클래스를 같이 써야한다.

클래스 안에 객체 성너 가능하고 그런 객체도 인스턴스는 단 하나다.

동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소

코틀린 클래스 안에는 정적 멤버 없다. statiㅊ 지원안한다. 대신 패키지 수준의 최상위 함수. 객체 선언을 활용한다. 대부분 최상위 함수를 활용하는 편을더 권장한다. 최상이 함수는 프라이빗으로 표시된 클래스 비공개 멤버에 접근할 수 없다. 클래스의 인스턴스와 고나계없이 호출해야하지만 클래스 내부 정보에 접근해야하는 함수가 필요할 때는 클래스에 중ㅈㅊㅂ된 객체 선언의 멤버함수로 정읳애ㅑ한다. 팩토리 메소드가 있따.

클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 동반 객체로 만들 수 있따.

프라이빗 생성자를 호출하기 좋은 위치다.팩토리 메소드 이름 지어줄 수 있고, 팩토리 메소드는 생성할 필요가 없는 개체를 생성하지 않으 ㄹ수도 있따. 하지만 클래스 확장해야하는 경우 동반 객체멤버를 하위클래스에 오버라이드 할 수 없어 여러 생성자를 사용하느편이 나은 해법이다.

동반 객체를 일반 객체처럼

동반객체는 클ㄹ스 안에 정의된 일반 객체다.

이르 ㅁ지ㅓㅇ주 ㄹ핑료없다 Companion이라 된다네

동반 객체에서 인터페이스 구현

코틀린 동반객체와 정적 멤버
동반객체느 ㄴ일반 객체와 비슷한 방식으로 클래스에 정의된 인스턴스를가리키는 정적 필드로 컴파일된다. 동반 객체에 이름을 붙이지 않다면 자바쪽에서 컴패니넝ㄴ이라는 이름으로 지어준다.

동반 객체의 확장

클래스 이름 ㅏㄲㄹ고 컴패닌언 꺼ㅏㄹ고 확장ㅎ 애ㅑ한다.

객체 식 : 무명 내부 클래스르다른 방식으로 작성

무명 객체를 정의할 때도 사용한다. 무명 내부 클래스를 대신한다. 이벤트 리스너 처럼

무명 객체는 싱글턴이 아니다. 객체 식이 쓰일떄마다 새로운 인스턴스가 생성된다.

객체 식 안의 코드는 그 식이 포함된 함수의 변수에 접근할 수 있다. final이 아닌 변수도 객체 식안에서 사용할 수 있따. 그 변수의 값을 바꿀 수 있따.살짝 위험한거 아니야?

  • 객체식은 무명 객체 안에서 여러 메소드를 오버라이드 해야하는 경우에 훨 유용하다. 메소드가 하나뿐인 SAM을 지원을 활용하는 편이 낫다. 무명 객체 대신 함수 리터럴을 사용해야한다.

요약

  • 코틀린의 인터페이스는 자바 인터페이스와 비슷하지만 디폴트 구현을 포함할 수 있고 프로퍼티 포함할 수 있다.
  • 모든 코틀린 선언은 기본적으로 final이며 poublic이다
  • 모선언이 final 되지 않게 하려면 open을 붙여야한다
  • internal 선언은 같은 모듈 안에엇만 볼수 있따.
  • 중첩 크래스는 기본적으로 내부 클래스가 아니다. 바깥족 클래스에 대한 참조를 중첩 클래스 안에 넣ㅇ려면 inner 써
  • sealed 클래스르랏ㅇ속하는 클래스를 정의하려면 반드시 부모클래스 정의 안에 중첩 클래스로 정의해야한다.
  • 초기화 블록ㄱ과 부 생성자를 활용해 클래스 인스턴스를 더 유연하게 초기화할 수 있다.
  • field 식별자를 통해 프로퍼티 접근자안에서 프로퍼티으 데이터를 저장하는데 쓰이는 뒷받침하는 필드를 참조할 수 있따.
  • 데이터 클래스르 사용하면 컴파일러가 equals, hashCOde, toString, Copy의 메소드를 졷ㅇ으로 생성해주낟.
  • 클래스 위임을 사용하면 위임 패턴을 ㅜㄱ현할 떄 성가신 코드를 대신한다
  • 객체 선언하면 코들린 답게 싱글턴 클래스릊 ㅓㅇ의한다.
  • 동반객체느 ㄴ자바의 정적 메소드와 필드정의를 대신한다.
  • 동반객체도 다른 객체와 마찬가지로 인퍼ㅔ이스 구현할 수 있따. 외부에서 도반객체대한 호가장함수와 프로퍼티 정의할 수 ㅣㅆ따.
  • 코틀린의 객체식은 자바의 무명내부 크래스를 대신한다. 하지만 코틀린 객체식은 인스턴스르룩현하거나 객체 포함된 영역에있는 변수를 변경할 수 있다.

댓글