본문 바로가기
Kotlin in Action

4. kotlin 클래스, 객체, 인터페이스

by 히예네 2024. 7. 13.
728x90
반응형

https://wooded-aphid-86c.notion.site/4-4163fed31cec4b54a35d017e79786526?pvs=4

 

4장. 클래스, 객체, 인터페이스 | Notion

4장 목차

wooded-aphid-86c.notion.site

 

1. 클래스 계층 정의

(1) 코틀린 인터페이스

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

a. 간단한 인터페이스 선언

//간단한 인터페이스 선언
interface Clickable {
    fun click()
}
  • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
  • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.

b. 단순한 인터페이스 구현하기

fun main(args: Array<String>) {
		Button().click() //출력 I was Clicked!
}

class RedButton : Clickable {
    override fun click() = println("I was Clicked!")
}
  • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
  • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
  • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
  • 클래스는 오직 하나만 확장할 수 있다.
  • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
    • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
    • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
  • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.

c. 인터페이스 안에 본문이 있는 메소드 정의하기

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}
  • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
  • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.

d. 동일한 메소드를 구현하는 다른 인터페이스 정의

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

interface Focusable {
    fun setFocus(b: Boolean) {
        println("I ${if (b) "got" else "lost"} focus.")
    }

    fun showOff() = println("I'm focusable!")
}
  • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
    • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
    Class 'RedButton' must override public open fun showOff(): 
    Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
    

e. 상속한 인터페이스의 메소드 구현 호출하기

class RedButton : Clickable, Focusable {
    override fun click() = println("I was Clicked!")
    override fun showOff() {
        super<Clickable>.showOff() 
        super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. 
    }
}

fun main(args: Array<String>) {
    RedButton().showOff()
    RedButton().click()
    
    //출력값
    I'm clickable!
		I'm focusable!
		I was Clicked!
}
  • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
  • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
  • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
    • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
    • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
    • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
    • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
    • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.

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

  • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
  • 취약한 기반 클래스(fragile base class)
    • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
    • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
    • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
    • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
    • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
    • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
    • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
    • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.

a. 열린 메소드를 포함하는 열린 클래스 정의하기

open class RichButton : Clickable { 
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. 
}
  • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
  • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
  • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
  • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
    • final override fun click(){ }

b. 오버라이드 금지하기

open class RichButton : Clickable {
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다.
}
  • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
  • 열린 클래스와 스마트 캐스트
    • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
      • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
      • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
      • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
      • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
      • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.

c. 추상 클래스 정의하기

  • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
    • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
    • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
    • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
    abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
    open fun stopAnimating() {
	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
    }
    fun animateTwice() {
    // 비추상함수는 final이다.
    }
}
  • 코틀린의 상속 제어 변경자
    • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
    • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
    • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
    변경자 이 변경자가 붙은 멤버는 … 설명
    final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
    open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
    abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
    추상 멤버에는 구현이 있으면 안 된다.    
    override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
    하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    

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

  • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
  • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
  • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
    • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
    • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
  • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
    • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
      • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
      • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
        • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
      • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
      • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
      • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
        public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
        internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
        protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
        private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.

a. 가시성 규칙을 위반한 예시

internal open class TalkativeButton : Focusable {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk!")
}

fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함.
    yell()  // 에러! "yell"은 private
    whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다.
}
  • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
  • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
  • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
  • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
  • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
  • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
  • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.

b. 코틀린의 가시성 변경자와 자바

  • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
    • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
    • 유일한 예외는 private 클래스이다.
      • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
    • internal은?
      • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
    • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
      • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
      • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
    • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
      • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
        • 이렇게 하는 이유는 2가지이다
          1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
          2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서

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

  • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
  • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
    • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
    • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.

a. 직렬화할 수 있는 상태가 있는 뷰 선언

  • View 요소를 하나 만들다고 생각해보자.
    • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
    • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
interface State : Serializable //Serializable을 구현한다.

interface ViewComponent {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}

b. 자바에서 내부 클래스를 사용해 View 구현하기

public class ButtonTest implements ViewComponent{
    @NonNull
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }
    @Override
    public void restoreState(@NonNull State state) {
        ViewComponent.super.restoreState(state);
    }
    public class ButtonState implements State { /**/}
}
  • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
  • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
  • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
  • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
    • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
      • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
      • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.

c. 중첩 클래스를 사용해 코틀린에서 View 구하기

class ButtonTest : ViewComponent {
    override fun getCurrentState(): State = ButtonState()
    
    override fun restoreState(state: State) {
        super.restoreState(state)
    }
    
    class ButtonState : State {}
}
  • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
  • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
    중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
    이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
    • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
    • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.

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

a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.

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.left) + eval(e.right)
        else ->
            throw IllegalArgumentException("Unknown Expression")
    }
  • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
    • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
  • 항상 디폴트 분기를 추가하는건 편하지 않다.
    • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
    • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.

b. sealed 클래스로 식 표현하기

  • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
sealed class Expr2 {
    class Num(val value: Int) : Expr2()
    //class Num2(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr2()
}

fun eval2(e: Expr2): Int =
    when (e) {
        is Expr2.Num -> e.value
        is Expr2.Sum -> eval(e.left) + eval(e.right)
    }
  • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
    • sealed class는 기본 open이다.
    • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
  • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
  • sealed 인터페이스는 정의 할 수 없다.
    • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

  • 코틀린에서는 주생성자와 부생성자를 구별한다.
    • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
    • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
    • 초기화블록

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

a. 주생성자 선언하는 방법

class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
    val nickname: String
    
    init {
        nickname = _nickname
    }
}
  • 주생성자 목적
    1. 생성자 파라미터를 지정한다.
    2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
  • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
  • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
  • 초기화블록은 주 생성자와 함께 사용된다.
    • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
  • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
  • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
    • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.

b. 같은 코드를 다른 방법으로 선언하기

class ShoppingUser (_nickname: String) { 
    val nickname = _nickname
}
  • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
  • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
  • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
    1. 프로퍼티를 초기화하는 식
    2. 초기화 블록 안에서

c. 더 간결하게 선언하기

class ShoppingUser (val nickname: String)
//val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다. 
  • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
    • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.

d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.

class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true)
//생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
  • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
  • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
  • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.

e. 기반 클래스(부모 클래스)가 있을 경우

open class ShoppingUser constructor(val nickname: String) 
class AmazonUser(nickname: String) : ShoppingUser(nickname)
  • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
  • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.

f. 클래스에 별도로 생성자를 정의하지 않을 경우

open class Button // 인자가 없는 디폴트 생성자가 만들어진다. 
  • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
    • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
class RadioButton: Button()
  • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
  • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
  • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.

g. 생성자 앞에 private 붙이기

class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
  • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
    • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
  • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
  • 비공개 생성자에 대한 대안
    • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
    • 코틀린은 그런 경우를 언어에서 기본지원한다.
      • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.

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

  • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
  • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
    • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.

a. 부생성자가 필요한 경우

  • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
    • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
      • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
      • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
    • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }

b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.

class MyButton : View {
    constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. 
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
  • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.

c. this()를 통해 자신의 다른 생성자를 호출하기

class MyButton : View {
    constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다.  
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
  • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
  • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.

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

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

interface User {
    val nickname: String
}
  • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
  • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
    • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.

b. 인터페이스의 프로퍼티 구현하기

//주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다.
//별명만 저장
class PrivateUser(override val nickname: String) : User

//이메일도 함께 저장하기
class SubscribingUser(val email: String) : User {
    override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다.
        get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다.
}

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

fun main() {
    println(PrivateUser("kim@naver.com").nickname) //kim@naver.com
    println(SubscribingUser("kim@naver.com").nickname) //kim
}

c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)

interface User {
    val email: String //오버라이드해야함
    val nickname: String //오버라이드하지않고 상속 할 수 있다.
        get() = email.substringBefore('@') 
        //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. 
}

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

a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때

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

fun main() {
    val user: User3 = User3("Lee")
    user.address = "Seoul kangnam"
    //결과값
    //    Address was changed for Lee:
    //"unspecified" -> "Seoul kangnam".
}
  • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
    • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.

(5) 접근자의 가시성 변경

  • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.

a. 비공개 세터가 있는 프로퍼티 선언하기

class LengthCounter {
    var counter: Int = 0
        private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다.
    fun addword(word: String) {
        counter += word.length
    }
}

fun main(){
    val lengthCounter = LengthCounter()
    lengthCounter.addword("hello world")
    println(lengthCounter.counter)
}

3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

  • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
    • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
    • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
    • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.

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

  • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
  • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자

a. 문자열 표현: toString

fun main() {
    println(Client("kim",1111).toString())
}

class Client(val name: String, val postalCode: Int) {
    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}
  • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
    • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.

b. 객체의 동등성: equals

    //참조값의 주소를 비교한다. 
    val client1 = Client("고양이",1111)
    val client2 = Client("고양이",1111)
    println(client1 == client2) //false
  • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
    • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
    • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
  • 자바에서의 == 비교
    • 원시타입
      • 두 피연산자의 값이 같은지 비교한다.
    • 참조타입
      • 두 피연산자의 주소값이 같은지 비교한다.
      • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
      • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
        • equals를 오버라이드하여 인스턴스를 비교해보자.
fun main() {
    val client1 = Client("고양이", 1111)
    val client2 = Client("고양이", 1111)
    println(client1 == client2) //true
}

class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
        //null이거나 Client 객체가 아니라면?
            return false
        return name == other.name &&
                postalCode == other.postalCode
    }

    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}

c. 해시 컨테이너: hashCode()

  • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
  • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
    • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
      • false
        • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
        • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
          • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
            • Client는 이를 어기고있다.
            • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
              • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
              • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
    • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false

(2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성

  • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
    • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
data class Client(val name: String, val postalCode: Int)
  • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
    • 인스턴스 간 비교를 위한 equals
    • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
  • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString

a. 데이터 클래스와 불변성: Copy() 메소드

  • 데이터 클래스의 프로퍼티는 val을 권장한다.
    • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
      • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
      • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
      • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
        • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
  • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
    • Copy()
      • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
      • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
        • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
        • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }

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

  • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
    • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
      • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
    • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
      • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
  • 데코레이터 패턴 (Decorator)
    • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
      • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
      • 이때 새로 정의해야 하는 기능은!
        • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
    • 단점: 준비 코드가 상당히 많이 필요하다.
      • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
        • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
        • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
      • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

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

(1) 객체 선언: 싱글턴을 쉽게 만들기

  • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
    • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
    • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
      • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
        • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
        • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
      • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
    • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.

a. 객체 선언을 사용해 Comparator 구현하기

fun main() {
    println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User")))
}

object CaseInsensitiveFileComparator : Comparator<File> {
    override fun compare(o1: File, o2: File): Int {
        return o1.path.compareTo(o2.path, ignoreCase = true)
    }
}
  • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
  • Comparator 안에는 데이터를 저장할 필요가 없다.
    • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
  • 싱글톤과 의존관계 주입
    • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
      • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
      • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
        • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
          • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.

b. 중첩 객체를 사용해 Comparator 구현하기

fun main() {
    val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee"))
    println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator))
}

data class Person(val name: String) {
    object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> {
        override fun compare(
            o1: com.anehta.athenakotlinlab.objecttest.Person,
            o2: com.anehta.athenakotlinlab.objecttest.Person
        ): Int = o1.name.compareTo(o2.name)
    }

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

  • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
  • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
    • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
      • static이 없다면 객체로 만들어서 가져와야한다.
  • 코틀린에서는 이런 기능들을 활용할 수 있다.
    • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
    • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
  • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
    • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
      • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
      •  
  • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
    • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
    • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.

(3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성

  • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
    • 이벤트 리스너
    • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
    • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.

1. 클래스 계층 정의

(1) 코틀린 인터페이스

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

a. 간단한 인터페이스 선언

//간단한 인터페이스 선언
interface Clickable {
    fun click()
}
  • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
  • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.

b. 단순한 인터페이스 구현하기

fun main(args: Array<String>) {
		Button().click() //출력 I was Clicked!
}

class RedButton : Clickable {
    override fun click() = println("I was Clicked!")
}
  • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
  • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
  • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
  • 클래스는 오직 하나만 확장할 수 있다.
  • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
    • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
    • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
  • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.

c. 인터페이스 안에 본문이 있는 메소드 정의하기

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}
  • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
  • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.

d. 동일한 메소드를 구현하는 다른 인터페이스 정의

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

interface Focusable {
    fun setFocus(b: Boolean) {
        println("I ${if (b) "got" else "lost"} focus.")
    }

    fun showOff() = println("I'm focusable!")
}
  • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
    • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
    Class 'RedButton' must override public open fun showOff(): 
    Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
    

e. 상속한 인터페이스의 메소드 구현 호출하기

class RedButton : Clickable, Focusable {
    override fun click() = println("I was Clicked!")
    override fun showOff() {
        super<Clickable>.showOff() 
        super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. 
    }
}

fun main(args: Array<String>) {
    RedButton().showOff()
    RedButton().click()
    
    //출력값
    I'm clickable!
		I'm focusable!
		I was Clicked!
}
  • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
  • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
  • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
    • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
    • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
    • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
    • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
    • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.

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

  • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
  • 취약한 기반 클래스(fragile base class)
    • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
    • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
    • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
    • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
    • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
    • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
    • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
    • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.

a. 열린 메소드를 포함하는 열린 클래스 정의하기

open class RichButton : Clickable { 
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. 
}
  • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
  • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
  • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
  • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
    • final override fun click(){ }

b. 오버라이드 금지하기

open class RichButton : Clickable {
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다.
}
  • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
  • 열린 클래스와 스마트 캐스트
    • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
      • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
      • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
      • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
      • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
      • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.

c. 추상 클래스 정의하기

  • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
    • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
    • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
    • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
    abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
    open fun stopAnimating() {
	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
    }
    fun animateTwice() {
    // 비추상함수는 final이다.
    }
}
  • 코틀린의 상속 제어 변경자
    • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
    • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
    • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
    변경자 이 변경자가 붙은 멤버는 … 설명
    final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
    open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
    abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
    추상 멤버에는 구현이 있으면 안 된다.    
    override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
    하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    

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

  • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
  • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
  • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
    • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
    • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
  • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
    • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
      • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
      • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
        • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
      • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
      • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
      • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
        public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
        internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
        protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
        private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.

a. 가시성 규칙을 위반한 예시

internal open class TalkativeButton : Focusable {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk!")
}

fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함.
    yell()  // 에러! "yell"은 private
    whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다.
}
  • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
  • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
  • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
  • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
  • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
  • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
  • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.

b. 코틀린의 가시성 변경자와 자바

  • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
    • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
    • 유일한 예외는 private 클래스이다.
      • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
    • internal은?
      • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
    • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
      • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
      • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
    • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
      • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
        • 이렇게 하는 이유는 2가지이다
          1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
          2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서

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

  • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
  • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
    • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
    • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.

a. 직렬화할 수 있는 상태가 있는 뷰 선언

  • View 요소를 하나 만들다고 생각해보자.
    • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
    • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
interface State : Serializable //Serializable을 구현한다.

interface ViewComponent {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}

b. 자바에서 내부 클래스를 사용해 View 구현하기

public class ButtonTest implements ViewComponent{
    @NonNull
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }
    @Override
    public void restoreState(@NonNull State state) {
        ViewComponent.super.restoreState(state);
    }
    public class ButtonState implements State { /**/}
}
  • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
  • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
  • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
  • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
    • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
      • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
      • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.

c. 중첩 클래스를 사용해 코틀린에서 View 구하기

class ButtonTest : ViewComponent {
    override fun getCurrentState(): State = ButtonState()
    
    override fun restoreState(state: State) {
        super.restoreState(state)
    }
    
    class ButtonState : State {}
}
  • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
  • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
    중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
    이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
    • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
    • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.

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

a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.

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.left) + eval(e.right)
        else ->
            throw IllegalArgumentException("Unknown Expression")
    }
  • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
    • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
  • 항상 디폴트 분기를 추가하는건 편하지 않다.
    • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
    • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.

b. sealed 클래스로 식 표현하기

  • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
sealed class Expr2 {
    class Num(val value: Int) : Expr2()
    //class Num2(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr2()
}

fun eval2(e: Expr2): Int =
    when (e) {
        is Expr2.Num -> e.value
        is Expr2.Sum -> eval(e.left) + eval(e.right)
    }
  • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
    • sealed class는 기본 open이다.
    • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
  • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
  • sealed 인터페이스는 정의 할 수 없다.
    • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

  • 코틀린에서는 주생성자와 부생성자를 구별한다.
    • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
    • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
    • 초기화블록

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

a. 주생성자 선언하는 방법

class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
    val nickname: String
    
    init {
        nickname = _nickname
    }
}
  • 주생성자 목적
    1. 생성자 파라미터를 지정한다.
    2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
  • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
  • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
  • 초기화블록은 주 생성자와 함께 사용된다.
    • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
  • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
  • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
    • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.

b. 같은 코드를 다른 방법으로 선언하기

class ShoppingUser (_nickname: String) { 
    val nickname = _nickname
}
  • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
  • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
  • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
    1. 프로퍼티를 초기화하는 식
    2. 초기화 블록 안에서

c. 더 간결하게 선언하기

class ShoppingUser (val nickname: String)
//val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다. 
  • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
    • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.

d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.

class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true)
//생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
  • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
  • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
  • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.

e. 기반 클래스(부모 클래스)가 있을 경우

open class ShoppingUser constructor(val nickname: String) 
class AmazonUser(nickname: String) : ShoppingUser(nickname)
  • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
  • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.

f. 클래스에 별도로 생성자를 정의하지 않을 경우

open class Button // 인자가 없는 디폴트 생성자가 만들어진다. 
  • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
    • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
class RadioButton: Button()
  • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
  • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
  • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.

g. 생성자 앞에 private 붙이기

class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
  • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
    • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
  • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
  • 비공개 생성자에 대한 대안
    • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
    • 코틀린은 그런 경우를 언어에서 기본지원한다.
      • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.

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

  • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
  • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
    • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.

a. 부생성자가 필요한 경우

  • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
    • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
      • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
      • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
    • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }

b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.

class MyButton : View {
    constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. 
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
  • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.

c. this()를 통해 자신의 다른 생성자를 호출하기

class MyButton : View {
    constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다.  
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
  • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
  • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.

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

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

interface User {
    val nickname: String
}
  • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
  • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
    • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.

b. 인터페이스의 프로퍼티 구현하기

//주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다.
//별명만 저장
class PrivateUser(override val nickname: String) : User

//이메일도 함께 저장하기
class SubscribingUser(val email: String) : User {
    override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다.
        get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다.
}

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

fun main() {
    println(PrivateUser("kim@naver.com").nickname) //kim@naver.com
    println(SubscribingUser("kim@naver.com").nickname) //kim
}

c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)

interface User {
    val email: String //오버라이드해야함
    val nickname: String //오버라이드하지않고 상속 할 수 있다.
        get() = email.substringBefore('@') 
        //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. 
}

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

a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때

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

fun main() {
    val user: User3 = User3("Lee")
    user.address = "Seoul kangnam"
    //결과값
    //    Address was changed for Lee:
    //"unspecified" -> "Seoul kangnam".
}
  • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
    • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.

(5) 접근자의 가시성 변경

  • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.

a. 비공개 세터가 있는 프로퍼티 선언하기

class LengthCounter {
    var counter: Int = 0
        private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다.
    fun addword(word: String) {
        counter += word.length
    }
}

fun main(){
    val lengthCounter = LengthCounter()
    lengthCounter.addword("hello world")
    println(lengthCounter.counter)
}

3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

  • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
    • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
    • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
    • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.

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

  • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
  • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자

a. 문자열 표현: toString

fun main() {
    println(Client("kim",1111).toString())
}

class Client(val name: String, val postalCode: Int) {
    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}
  • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
    • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.

b. 객체의 동등성: equals

    //참조값의 주소를 비교한다. 
    val client1 = Client("고양이",1111)
    val client2 = Client("고양이",1111)
    println(client1 == client2) //false
  • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
    • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
    • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
  • 자바에서의 == 비교
    • 원시타입
      • 두 피연산자의 값이 같은지 비교한다.
    • 참조타입
      • 두 피연산자의 주소값이 같은지 비교한다.
      • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
      • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
        • equals를 오버라이드하여 인스턴스를 비교해보자.
fun main() {
    val client1 = Client("고양이", 1111)
    val client2 = Client("고양이", 1111)
    println(client1 == client2) //true
}

class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
        //null이거나 Client 객체가 아니라면?
            return false
        return name == other.name &&
                postalCode == other.postalCode
    }

    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}

c. 해시 컨테이너: hashCode()

  • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
  • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
    • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
      • false
        • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
        • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
          • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
            • Client는 이를 어기고있다.
            • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
              • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
              • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
    • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false

(2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성

  • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
    • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
data class Client(val name: String, val postalCode: Int)
  • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
    • 인스턴스 간 비교를 위한 equals
    • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
  • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString

a. 데이터 클래스와 불변성: Copy() 메소드

  • 데이터 클래스의 프로퍼티는 val을 권장한다.
    • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
      • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
      • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
      • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
        • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
  • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
    • Copy()
      • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
      • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
        • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
        • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }

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

  • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
    • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
      • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
    • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
      • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
  • 데코레이터 패턴 (Decorator)
    • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
      • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
      • 이때 새로 정의해야 하는 기능은!
        • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
    • 단점: 준비 코드가 상당히 많이 필요하다.
      • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
        • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
        • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
      • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

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

(1) 객체 선언: 싱글턴을 쉽게 만들기

  • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
    • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
    • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
      • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
        • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
        • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
      • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
    • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.

a. 객체 선언을 사용해 Comparator 구현하기

fun main() {
    println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User")))
}

object CaseInsensitiveFileComparator : Comparator<File> {
    override fun compare(o1: File, o2: File): Int {
        return o1.path.compareTo(o2.path, ignoreCase = true)
    }
}
  • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
  • Comparator 안에는 데이터를 저장할 필요가 없다.
    • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
  • 싱글톤과 의존관계 주입
    • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
      • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
      • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
        • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
          • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.

b. 중첩 객체를 사용해 Comparator 구현하기

fun main() {
    val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee"))
    println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator))
}

data class Person(val name: String) {
    object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> {
        override fun compare(
            o1: com.anehta.athenakotlinlab.objecttest.Person,
            o2: com.anehta.athenakotlinlab.objecttest.Person
        ): Int = o1.name.compareTo(o2.name)
    }

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

  • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
  • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
    • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
      • static이 없다면 객체로 만들어서 가져와야한다.
  • 코틀린에서는 이런 기능들을 활용할 수 있다.
    • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
    • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
  • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
    • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
      • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
  • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
    • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
    • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.

(3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성

  • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
    • 이벤트 리스너
    • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
    • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.

1. 클래스 계층 정의

(1) 코틀린 인터페이스

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

a. 간단한 인터페이스 선언

//간단한 인터페이스 선언
interface Clickable {
    fun click()
}
  • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
  • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.

b. 단순한 인터페이스 구현하기

fun main(args: Array<String>) {
		Button().click() //출력 I was Clicked!
}

class RedButton : Clickable {
    override fun click() = println("I was Clicked!")
}
  • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
  • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
  • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
  • 클래스는 오직 하나만 확장할 수 있다.
  • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
    • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
    • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
  • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.

c. 인터페이스 안에 본문이 있는 메소드 정의하기

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}
  • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
  • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.

d. 동일한 메소드를 구현하는 다른 인터페이스 정의

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

interface Focusable {
    fun setFocus(b: Boolean) {
        println("I ${if (b) "got" else "lost"} focus.")
    }

    fun showOff() = println("I'm focusable!")
}
  • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
    • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
    Class 'RedButton' must override public open fun showOff(): 
    Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
    

e. 상속한 인터페이스의 메소드 구현 호출하기

class RedButton : Clickable, Focusable {
    override fun click() = println("I was Clicked!")
    override fun showOff() {
        super<Clickable>.showOff() 
        super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. 
    }
}

fun main(args: Array<String>) {
    RedButton().showOff()
    RedButton().click()
    
    //출력값
    I'm clickable!
		I'm focusable!
		I was Clicked!
}
  • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
  • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
  • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
    • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
    • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
    • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
    • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
    • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.

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

  • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
  • 취약한 기반 클래스(fragile base class)
    • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
    • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
    • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
    • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
    • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
    • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
    • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
    • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.

a. 열린 메소드를 포함하는 열린 클래스 정의하기

open class RichButton : Clickable { 
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. 
}
  • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
  • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
  • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
  • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
    • final override fun click(){ }

b. 오버라이드 금지하기

open class RichButton : Clickable {
    fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다.
    open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다.
    final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다.
}
  • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
  • 열린 클래스와 스마트 캐스트
    • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
      • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
      • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
      • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
      • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
      • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.

c. 추상 클래스 정의하기

  • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
    • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
    • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
    • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
    abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
    open fun stopAnimating() {
	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
    }
    fun animateTwice() {
    // 비추상함수는 final이다.
    }
}
  • 코틀린의 상속 제어 변경자
    • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
    • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
    • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
    변경자 이 변경자가 붙은 멤버는 … 설명
    final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
    open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
    abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
    추상 멤버에는 구현이 있으면 안 된다.    
    override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
    하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    

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

  • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
  • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
  • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
    • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
    • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
  • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
    • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
      • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
      • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
        • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
      • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
      • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
      • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
        public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
        internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
        protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
        private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.

a. 가시성 규칙을 위반한 예시

internal open class TalkativeButton : Focusable {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk!")
}

fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함.
    yell()  // 에러! "yell"은 private
    whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다.
}
  • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
  • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
  • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
  • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
  • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
  • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
  • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.

b. 코틀린의 가시성 변경자와 자바

  • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
    • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
    • 유일한 예외는 private 클래스이다.
      • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
    • internal은?
      • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
    • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
      • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
      • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
    • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
      • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
        • 이렇게 하는 이유는 2가지이다
          1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
          2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서

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

  • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
  • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
    • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
    • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.

a. 직렬화할 수 있는 상태가 있는 뷰 선언

  • View 요소를 하나 만들다고 생각해보자.
    • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
    • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
interface State : Serializable //Serializable을 구현한다.

interface ViewComponent {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}

b. 자바에서 내부 클래스를 사용해 View 구현하기

public class ButtonTest implements ViewComponent{
    @NonNull
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }
    @Override
    public void restoreState(@NonNull State state) {
        ViewComponent.super.restoreState(state);
    }
    public class ButtonState implements State { /**/}
}
  • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
  • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
  • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
  • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
    • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
      • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
      • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.

c. 중첩 클래스를 사용해 코틀린에서 View 구하기

class ButtonTest : ViewComponent {
    override fun getCurrentState(): State = ButtonState()
    
    override fun restoreState(state: State) {
        super.restoreState(state)
    }
    
    class ButtonState : State {}
}
  • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
  • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
    중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
    이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
    • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
    • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.

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

a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.

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.left) + eval(e.right)
        else ->
            throw IllegalArgumentException("Unknown Expression")
    }
  • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
    • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
  • 항상 디폴트 분기를 추가하는건 편하지 않다.
    • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
    • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.

b. sealed 클래스로 식 표현하기

  • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
sealed class Expr2 {
    class Num(val value: Int) : Expr2()
    //class Num2(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr2()
}

fun eval2(e: Expr2): Int =
    when (e) {
        is Expr2.Num -> e.value
        is Expr2.Sum -> eval(e.left) + eval(e.right)
    }
  • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
    • sealed class는 기본 open이다.
    • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
  • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
  • sealed 인터페이스는 정의 할 수 없다.
    • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

  • 코틀린에서는 주생성자와 부생성자를 구별한다.
    • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
    • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
    • 초기화블록

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

a. 주생성자 선언하는 방법

class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
    val nickname: String
    
    init {
        nickname = _nickname
    }
}
  • 주생성자 목적
    1. 생성자 파라미터를 지정한다.
    2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
  • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
  • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
  • 초기화블록은 주 생성자와 함께 사용된다.
    • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
  • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
  • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
    • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.

b. 같은 코드를 다른 방법으로 선언하기

class ShoppingUser (_nickname: String) { 
    val nickname = _nickname
}
  • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
  • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
  • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
    1. 프로퍼티를 초기화하는 식
    2. 초기화 블록 안에서

c. 더 간결하게 선언하기

class ShoppingUser (val nickname: String)
//val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다. 
  • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
    • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.

d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.

class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true)
//생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
  • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
  • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
  • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.

e. 기반 클래스(부모 클래스)가 있을 경우

open class ShoppingUser constructor(val nickname: String) 
class AmazonUser(nickname: String) : ShoppingUser(nickname)
  • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
  • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.

f. 클래스에 별도로 생성자를 정의하지 않을 경우

open class Button // 인자가 없는 디폴트 생성자가 만들어진다. 
  • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
    • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
class RadioButton: Button()
  • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
  • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
  • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.

g. 생성자 앞에 private 붙이기

class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
  • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
    • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
  • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
  • 비공개 생성자에 대한 대안
    • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
    • 코틀린은 그런 경우를 언어에서 기본지원한다.
      • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.

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

  • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
  • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
    • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.

a. 부생성자가 필요한 경우

  • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
    • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
      • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
      • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
    • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }

b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.

class MyButton : View {
    constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. 
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
  • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.

c. this()를 통해 자신의 다른 생성자를 호출하기

class MyButton : View {
    constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다.  
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. 
}
  • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
  • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
  • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.

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

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

interface User {
    val nickname: String
}
  • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
  • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
    • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.

b. 인터페이스의 프로퍼티 구현하기

//주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다.
//별명만 저장
class PrivateUser(override val nickname: String) : User

//이메일도 함께 저장하기
class SubscribingUser(val email: String) : User {
    override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다.
        get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다.
}

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

fun main() {
    println(PrivateUser("kim@naver.com").nickname) //kim@naver.com
    println(SubscribingUser("kim@naver.com").nickname) //kim
}

c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)

interface User {
    val email: String //오버라이드해야함
    val nickname: String //오버라이드하지않고 상속 할 수 있다.
        get() = email.substringBefore('@') 
        //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. 
}

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

a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때

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

fun main() {
    val user: User3 = User3("Lee")
    user.address = "Seoul kangnam"
    //결과값
    //    Address was changed for Lee:
    //"unspecified" -> "Seoul kangnam".
}
  • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
    • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.

(5) 접근자의 가시성 변경

  • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.

a. 비공개 세터가 있는 프로퍼티 선언하기

class LengthCounter {
    var counter: Int = 0
        private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다.
    fun addword(word: String) {
        counter += word.length
    }
}

fun main(){
    val lengthCounter = LengthCounter()
    lengthCounter.addword("hello world")
    println(lengthCounter.counter)
}

3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

  • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
    • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
    • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
    • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.

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

  • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
  • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자

a. 문자열 표현: toString

fun main() {
    println(Client("kim",1111).toString())
}

class Client(val name: String, val postalCode: Int) {
    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}
  • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
    • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.

b. 객체의 동등성: equals

    //참조값의 주소를 비교한다. 
    val client1 = Client("고양이",1111)
    val client2 = Client("고양이",1111)
    println(client1 == client2) //false
  • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
    • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
    • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
  • 자바에서의 == 비교
    • 원시타입
      • 두 피연산자의 값이 같은지 비교한다.
    • 참조타입
      • 두 피연산자의 주소값이 같은지 비교한다.
      • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
      • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
        • equals를 오버라이드하여 인스턴스를 비교해보자.
fun main() {
    val client1 = Client("고양이", 1111)
    val client2 = Client("고양이", 1111)
    println(client1 == client2) //true
}

class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
        //null이거나 Client 객체가 아니라면?
            return false
        return name == other.name &&
                postalCode == other.postalCode
    }

    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
    //결과값
    //override 시: Client(name=kim, postalCode=1111)
    //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
}

c. 해시 컨테이너: hashCode()

  • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
  • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
    • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
      • false
        • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
        • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
          • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
            • Client는 이를 어기고있다.
            • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
              • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
              • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
    • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false

(2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성

  • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
    • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
data class Client(val name: String, val postalCode: Int)
  • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
    • 인스턴스 간 비교를 위한 equals
    • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
  • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString

a. 데이터 클래스와 불변성: Copy() 메소드

  • 데이터 클래스의 프로퍼티는 val을 권장한다.
    • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
      • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
      • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
      • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
        • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
  • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
    • Copy()
      • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
      • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
        • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
        • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }

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

  • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
    • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
      • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
    • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
      • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
  • 데코레이터 패턴 (Decorator)
    • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
      • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
      • 이때 새로 정의해야 하는 기능은!
        • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
    • 단점: 준비 코드가 상당히 많이 필요하다.
      • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
        • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
        • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
      • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

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

(1) 객체 선언: 싱글턴을 쉽게 만들기

  • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
    • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
    • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
      • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
        • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
        • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
      • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
    • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.

a. 객체 선언을 사용해 Comparator 구현하기

fun main() {
    println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User")))
}

object CaseInsensitiveFileComparator : Comparator<File> {
    override fun compare(o1: File, o2: File): Int {
        return o1.path.compareTo(o2.path, ignoreCase = true)
    }
}
  • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
  • Comparator 안에는 데이터를 저장할 필요가 없다.
    • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
  • 싱글톤과 의존관계 주입
    • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
      • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
      • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
        • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
          • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.

b. 중첩 객체를 사용해 Comparator 구현하기

fun main() {
    val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee"))
    println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator))
}

data class Person(val name: String) {
    object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> {
        override fun compare(
            o1: com.anehta.athenakotlinlab.objecttest.Person,
            o2: com.anehta.athenakotlinlab.objecttest.Person
        ): Int = o1.name.compareTo(o2.name)
    }

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

  • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
  • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
    • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
      • static이 없다면 객체로 만들어서 가져와야한다.
  • 코틀린에서는 이런 기능들을 활용할 수 있다.
    • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
    • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
  • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
    • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
      • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
  • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
    • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
    • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.

(3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성

  • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
    • 이벤트 리스너
    • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
    • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.</aside>(1) 코틀린 인터페이스
      • 코틀린 인터페이스는 자바 8 인터페이스와 비슷하다.
      • 코틀린 인터페이스 안에는 추상 메소드뿐만 아니라 구현이 있는 메소드도 정의 할 수 있다. ( 자바 8의 디폴트 메소드와 비슷하다.)
      • 다만 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.
      a. 간단한 인터페이스 선언
      • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
      • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.
      b. 단순한 인터페이스 구현하기
      • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
      • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
      • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
      • 클래스는 오직 하나만 확장할 수 있다.
      • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
        • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
        • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
      • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.
      c. 인터페이스 안에 본문이 있는 메소드 정의하기
      • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
      • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.
      d. 동일한 메소드를 구현하는 다른 인터페이스 정의
      • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
        • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
        Class 'RedButton' must override public open fun showOff(): 
        Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
        
      e. 상속한 인터페이스의 메소드 구현 호출하기
      • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
      • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
      • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
        • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
        • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
        • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
        • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
        • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.
      (2) open, final, abstract 변경자: 기본적으로 final
      • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
      • 취약한 기반 클래스(fragile base class)
        • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
        • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
        • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
        • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
        • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
        • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
        • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
        • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.
      a. 열린 메소드를 포함하는 열린 클래스 정의하기
      • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
      • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
      • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
      • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
        • final override fun click(){ }
      b. 오버라이드 금지하기
      • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
      • 열린 클래스와 스마트 캐스트
        • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
          • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
          • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
          • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
          • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
          • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.
      c. 추상 클래스 정의하기
      • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
        • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
        • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
        • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
      abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
          abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
          open fun stopAnimating() {
      	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
      	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
          }
          fun animateTwice() {
          // 비추상함수는 final이다.
          }
      }
      
      • 코틀린의 상속 제어 변경자
        • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
        • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
        • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
        변경자 이 변경자가 붙은 멤버는 … 설명
        final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
        open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
        abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
        추상 멤버에는 구현이 있으면 안 된다.    
        override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
        하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    
      (3) 가시성 변경자: 기본적으로 공개
      • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
      • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
      • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
        • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
        • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
      • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
        • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
          • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
          • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
            • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
          • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
          • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
          • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
            public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
            internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
            protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
            private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.
      a. 가시성 규칙을 위반한 예시
      • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
      • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
      • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
      • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
      • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
      • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
      • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.
      b. 코틀린의 가시성 변경자와 자바
      • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
        • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
        • 유일한 예외는 private 클래스이다.
          • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
        • internal은?
          • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
        • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
          • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
          • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
        • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
          • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
            • 이렇게 하는 이유는 2가지이다
              1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
              2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서
      (4) 내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
      • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
      • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
        • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
        • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.
      a. 직렬화할 수 있는 상태가 있는 뷰 선언
      • View 요소를 하나 만들다고 생각해보자.
        • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
        • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
      interface State : Serializable //Serializable을 구현한다.
      
      interface ViewComponent {
          fun getCurrentState(): State
          fun restoreState(state: State) {}
      }
      
      b. 자바에서 내부 클래스를 사용해 View 구현하기
      • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
      • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
      • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
      • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
        • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
          • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
          • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.
      c. 중첩 클래스를 사용해 코틀린에서 View 구하기
      • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
      • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
        중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
        이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
        • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
        • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.
      (5) 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
      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.left) + eval(e.right)
              else ->
                  throw IllegalArgumentException("Unknown Expression")
          }
      
      • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
        • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
      • 항상 디폴트 분기를 추가하는건 편하지 않다.
        • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
        • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.
      b. sealed 클래스로 식 표현하기
      • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
      sealed class Expr2 {
          class Num(val value: Int) : Expr2()
          //class Num2(val value: Int) : Expr()
          class Sum(val left: Expr, val right: Expr) : Expr2()
      }
      
      fun eval2(e: Expr2): Int =
          when (e) {
              is Expr2.Num -> e.value
              is Expr2.Sum -> eval(e.left) + eval(e.right)
          }
      
      • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
        • sealed class는 기본 open이다.
        • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
      • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
      • sealed 인터페이스는 정의 할 수 없다.
        • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

      • 코틀린에서는 주생성자와 부생성자를 구별한다.
        • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
        • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
        • 초기화블록
      (1) 클래스 초기화: 주 생성자와 초기화 블록
      class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
          val nickname: String
          
          init {
              nickname = _nickname
          }
      }
      
      • 주생성자 목적
        1. 생성자 파라미터를 지정한다.
        2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
      • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
      • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
      • 초기화블록은 주 생성자와 함께 사용된다.
        • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
      • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
      • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
        • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.
      b. 같은 코드를 다른 방법으로 선언하기
      • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
      • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
      • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
        1. 프로퍼티를 초기화하는 식
        2. 초기화 블록 안에서
      c. 더 간결하게 선언하기
      • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
        • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.
      d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.
      • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
      • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
      • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.
      e. 기반 클래스(부모 클래스)가 있을 경우
      • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
      • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.
      f. 클래스에 별도로 생성자를 정의하지 않을 경우
      • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
        • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
      class RadioButton: Button()
      
      • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
      • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
      • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.
      g. 생성자 앞에 private 붙이기
      • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
        • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
      • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
      • 비공개 생성자에 대한 대안
        • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
        • 코틀린은 그런 경우를 언어에서 기본지원한다.
          • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.
      (2) 부생성자: 상위 클래스를 다른 방식으로 초기화
      • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
      • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
        • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.
      a. 부생성자가 필요한 경우
      • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
        • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
          • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
          • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
        • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }
      b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.
      • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
      • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.
      c. this()를 통해 자신의 다른 생성자를 호출하기
      • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
      • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
      • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.
      (3) 인터페이스에 선언된 프로퍼티 구현
      interface User {
          val nickname: String
      }
      
      • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
      • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
        • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.
      b. 인터페이스의 프로퍼티 구현하기c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)(4) 게터와 세터에서 뒷받침하는 필드에 접근
      class User3(val name: String) {
          var address: String = "unspecified"
              set(value: String) {
                  println(
                      """
                      Address was changed for $name:
                  "${field}" -> "${value}".""".trimIndent()
                  ) //뒷받침하는 필드 값 읽기
                  field = value //뒷받침하는 필드값 변경하기
              }
      }
      
      fun main() {
          val user: User3 = User3("Lee")
          user.address = "Seoul kangnam"
          //결과값
          //    Address was changed for Lee:
          //"unspecified" -> "Seoul kangnam".
      }
      
      • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
        • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.
      (5) 접근자의 가시성 변경
      • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.
      a. 비공개 세터가 있는 프로퍼티 선언하기

      3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

      • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
        • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
        • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
        • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.
      (1) 모든 클래스가 정의해야하는 메소드
      • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
      • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자
      a. 문자열 표현: toString
      • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
        • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.
      b. 객체의 동등성: equals
      • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
        • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
        • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
      • 자바에서의 == 비교
        • 원시타입
          • 두 피연산자의 값이 같은지 비교한다.
        • 참조타입
          • 두 피연산자의 주소값이 같은지 비교한다.
          • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
          • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
            • equals를 오버라이드하여 인스턴스를 비교해보자.
      fun main() {
          val client1 = Client("고양이", 1111)
          val client2 = Client("고양이", 1111)
          println(client1 == client2) //true
      }
      
      class Client(val name: String, val postalCode: Int) {
          override fun equals(other: Any?): Boolean {
              if (other == null || other !is Client)
              //null이거나 Client 객체가 아니라면?
                  return false
              return name == other.name &&
                      postalCode == other.postalCode
          }
      
          override fun toString() = "Client(name=$name, postalCode=$postalCode)"
          //결과값
          //override 시: Client(name=kim, postalCode=1111)
          //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
      }
      
      c. 해시 컨테이너: hashCode()
      • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
      • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
        • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
          • false
            • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
            • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
              • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
                • Client는 이를 어기고있다.
                • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
                  • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                    • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
                  • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
        • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false
      (2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
      • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
        • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
      data class Client(val name: String, val postalCode: Int)
      
      • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
        • 인스턴스 간 비교를 위한 equals
        • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
      • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString
      a. 데이터 클래스와 불변성: Copy() 메소드
      • 데이터 클래스의 프로퍼티는 val을 권장한다.
        • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
          • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
          • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
          • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
            • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
      • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
        • Copy()
          • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
          • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
            • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
            • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }
      (3) 클래스 위임: by 키워드 사용
      • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
        • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
          • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
        • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
          • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
      • 데코레이터 패턴 (Decorator)
        • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
          • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
          • 이때 새로 정의해야 하는 기능은!
            • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
        • 단점: 준비 코드가 상당히 많이 필요하다.
          • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
            • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
            • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
          • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

      • object 키워드를 사용하는 경우
        • 객체 선언(object declaration)은 싱글턴을 정의하는 방법 중 하나이다.
        • 동반 객체 (companion object)는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 때 쓰인다.
          • 동반 객체 메소드에 접근 할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다.
        • 객체 식은 자바의 무명 내부 클래스 (annonymous inner class) 대신 쓰인다.
      (1) 객체 선언: 싱글턴을 쉽게 만들기
      • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
        • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
        • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
          • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
            • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
            • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
          • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
        • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.
      a. 객체 선언을 사용해 Comparator 구현하기
      • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
      • Comparator 안에는 데이터를 저장할 필요가 없다.
        • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
      • 싱글톤과 의존관계 주입
        • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
          • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
          • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
            • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
              • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.
      b. 중첩 객체를 사용해 Comparator 구현하기(2) 동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
      • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
      • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
        • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
          • static이 없다면 객체로 만들어서 가져와야한다.
      • 코틀린에서는 이런 기능들을 활용할 수 있다.
        • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
        • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
      • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
        • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
          • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
      • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
        • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
        • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.
      (3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성
      • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
        • 이벤트 리스너
        • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
        • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.</aside>(1) 코틀린 인터페이스
          • 코틀린 인터페이스는 자바 8 인터페이스와 비슷하다.
          • 코틀린 인터페이스 안에는 추상 메소드뿐만 아니라 구현이 있는 메소드도 정의 할 수 있다. ( 자바 8의 디폴트 메소드와 비슷하다.)
          • 다만 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.
          a. 간단한 인터페이스 선언
          • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
          • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.
          b. 단순한 인터페이스 구현하기
          • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
          • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
          • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
          • 클래스는 오직 하나만 확장할 수 있다.
          • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
            • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
            • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
          • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.
          c. 인터페이스 안에 본문이 있는 메소드 정의하기
          • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
          • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.
          d. 동일한 메소드를 구현하는 다른 인터페이스 정의
          • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
            • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
            Class 'RedButton' must override public open fun showOff(): 
            Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
            
          e. 상속한 인터페이스의 메소드 구현 호출하기
          • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
          • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
          • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
            • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
            • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
            • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
            • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
            • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.
          (2) open, final, abstract 변경자: 기본적으로 final
          • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
          • 취약한 기반 클래스(fragile base class)
            • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
            • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
            • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
            • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
            • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
            • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
            • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
            • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.
          a. 열린 메소드를 포함하는 열린 클래스 정의하기
          • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
          • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
          • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
          • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
            • final override fun click(){ }
          b. 오버라이드 금지하기
          • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
          • 열린 클래스와 스마트 캐스트
            • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
              • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
              • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
              • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
              • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
              • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.
          c. 추상 클래스 정의하기
          • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
            • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
            • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
            • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
          abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
              abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
              open fun stopAnimating() {
          	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
          	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
              }
              fun animateTwice() {
              // 비추상함수는 final이다.
              }
          }
          
          • 코틀린의 상속 제어 변경자
            • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
            • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
            • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
            변경자 이 변경자가 붙은 멤버는 … 설명
            final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
            open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
            abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
            추상 멤버에는 구현이 있으면 안 된다.    
            override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
            하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    
          (3) 가시성 변경자: 기본적으로 공개
          • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
          • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
          • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
            • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
            • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
          • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
            • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
              • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
              • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
                • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
              • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
              • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
              • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
                public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
                internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
                protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
                private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.
          a. 가시성 규칙을 위반한 예시
          • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
          • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
          • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
          • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
          • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
          • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
          • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.
          b. 코틀린의 가시성 변경자와 자바
          • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
            • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
            • 유일한 예외는 private 클래스이다.
              • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
            • internal은?
              • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
            • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
              • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
              • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
            • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
              • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
                • 이렇게 하는 이유는 2가지이다
                  1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
                  2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서
          (4) 내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
          • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
          • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
            • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
            • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.
          a. 직렬화할 수 있는 상태가 있는 뷰 선언
          • View 요소를 하나 만들다고 생각해보자.
            • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
            • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
          interface State : Serializable //Serializable을 구현한다.
          
          interface ViewComponent {
              fun getCurrentState(): State
              fun restoreState(state: State) {}
          }
          
          b. 자바에서 내부 클래스를 사용해 View 구현하기
          • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
          • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
          • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
          • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
            • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
              • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
              • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.
          c. 중첩 클래스를 사용해 코틀린에서 View 구하기
          • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
          • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
            중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
            이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
            • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
            • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.
          (5) 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
          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.left) + eval(e.right)
                  else ->
                      throw IllegalArgumentException("Unknown Expression")
              }
          
          • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
            • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
          • 항상 디폴트 분기를 추가하는건 편하지 않다.
            • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
            • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.
          b. sealed 클래스로 식 표현하기
          • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
          sealed class Expr2 {
              class Num(val value: Int) : Expr2()
              //class Num2(val value: Int) : Expr()
              class Sum(val left: Expr, val right: Expr) : Expr2()
          }
          
          fun eval2(e: Expr2): Int =
              when (e) {
                  is Expr2.Num -> e.value
                  is Expr2.Sum -> eval(e.left) + eval(e.right)
              }
          
          • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
            • sealed class는 기본 open이다.
            • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
          • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
          • sealed 인터페이스는 정의 할 수 없다.
            • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

          • 코틀린에서는 주생성자와 부생성자를 구별한다.
            • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
            • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
            • 초기화블록
          (1) 클래스 초기화: 주 생성자와 초기화 블록
          class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
              val nickname: String
              
              init {
                  nickname = _nickname
              }
          }
          
          • 주생성자 목적
            1. 생성자 파라미터를 지정한다.
            2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
          • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
          • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
          • 초기화블록은 주 생성자와 함께 사용된다.
            • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
          • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
          • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
            • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.
          b. 같은 코드를 다른 방법으로 선언하기
          • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
          • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
          • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
            1. 프로퍼티를 초기화하는 식
            2. 초기화 블록 안에서
          c. 더 간결하게 선언하기
          • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
            • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.
          d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.
          • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
          • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
          • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.
          e. 기반 클래스(부모 클래스)가 있을 경우
          • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
          • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.
          f. 클래스에 별도로 생성자를 정의하지 않을 경우
          • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
            • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
          class RadioButton: Button()
          
          • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
          • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
          • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.
          g. 생성자 앞에 private 붙이기
          • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
            • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
          • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
          • 비공개 생성자에 대한 대안
            • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
            • 코틀린은 그런 경우를 언어에서 기본지원한다.
              • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.
          (2) 부생성자: 상위 클래스를 다른 방식으로 초기화
          • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
          • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
            • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.
          a. 부생성자가 필요한 경우
          • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
            • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
              • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
              • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
            • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }
          b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.
          • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
          • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.
          c. this()를 통해 자신의 다른 생성자를 호출하기
          • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
          • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
          • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.
          (3) 인터페이스에 선언된 프로퍼티 구현
          interface User {
              val nickname: String
          }
          
          • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
          • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
            • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.
          b. 인터페이스의 프로퍼티 구현하기c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)(4) 게터와 세터에서 뒷받침하는 필드에 접근
          class User3(val name: String) {
              var address: String = "unspecified"
                  set(value: String) {
                      println(
                          """
                          Address was changed for $name:
                      "${field}" -> "${value}".""".trimIndent()
                      ) //뒷받침하는 필드 값 읽기
                      field = value //뒷받침하는 필드값 변경하기
                  }
          }
          
          fun main() {
              val user: User3 = User3("Lee")
              user.address = "Seoul kangnam"
              //결과값
              //    Address was changed for Lee:
              //"unspecified" -> "Seoul kangnam".
          }
          
          • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
            • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.
          (5) 접근자의 가시성 변경
          • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.
          a. 비공개 세터가 있는 프로퍼티 선언하기

          3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

          • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
            • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
            • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
            • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.
          (1) 모든 클래스가 정의해야하는 메소드
          • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
          • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자
          a. 문자열 표현: toString
          • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
            • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.
          b. 객체의 동등성: equals
          • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
            • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
            • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
          • 자바에서의 == 비교
            • 원시타입
              • 두 피연산자의 값이 같은지 비교한다.
            • 참조타입
              • 두 피연산자의 주소값이 같은지 비교한다.
              • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
              • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
                • equals를 오버라이드하여 인스턴스를 비교해보자.
          fun main() {
              val client1 = Client("고양이", 1111)
              val client2 = Client("고양이", 1111)
              println(client1 == client2) //true
          }
          
          class Client(val name: String, val postalCode: Int) {
              override fun equals(other: Any?): Boolean {
                  if (other == null || other !is Client)
                  //null이거나 Client 객체가 아니라면?
                      return false
                  return name == other.name &&
                          postalCode == other.postalCode
              }
          
              override fun toString() = "Client(name=$name, postalCode=$postalCode)"
              //결과값
              //override 시: Client(name=kim, postalCode=1111)
              //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
          }
          
          c. 해시 컨테이너: hashCode()
          • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
          • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
            • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
              • false
                • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
                • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
                  • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
                    • Client는 이를 어기고있다.
                    • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
                      • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                        • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
                      • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
            • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false
          (2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
          • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
            • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
          data class Client(val name: String, val postalCode: Int)
          
          • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
            • 인스턴스 간 비교를 위한 equals
            • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
          • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString
          a. 데이터 클래스와 불변성: Copy() 메소드
          • 데이터 클래스의 프로퍼티는 val을 권장한다.
            • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
              • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
              • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
              • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
                • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
          • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
            • Copy()
              • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
              • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
                • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
                • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }
          (3) 클래스 위임: by 키워드 사용
          • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
            • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
              • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
            • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
              • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
          • 데코레이터 패턴 (Decorator)
            • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
              • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
              • 이때 새로 정의해야 하는 기능은!
                • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
            • 단점: 준비 코드가 상당히 많이 필요하다.
              • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
                • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
                • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
              • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

          • object 키워드를 사용하는 경우
            • 객체 선언(object declaration)은 싱글턴을 정의하는 방법 중 하나이다.
            • 동반 객체 (companion object)는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 때 쓰인다.
              • 동반 객체 메소드에 접근 할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다.
            • 객체 식은 자바의 무명 내부 클래스 (annonymous inner class) 대신 쓰인다.
          (1) 객체 선언: 싱글턴을 쉽게 만들기
          • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
            • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
            • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
              • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
                • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
                • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
              • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
            • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.
          a. 객체 선언을 사용해 Comparator 구현하기
          • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
          • Comparator 안에는 데이터를 저장할 필요가 없다.
            • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
          • 싱글톤과 의존관계 주입
            • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
              • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
              • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
                • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
                  • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.
          b. 중첩 객체를 사용해 Comparator 구현하기(2) 동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
          • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
          • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
            • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
              • static이 없다면 객체로 만들어서 가져와야한다.
          • 코틀린에서는 이런 기능들을 활용할 수 있다.
            • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
            • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
          • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
            • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
              • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
          • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
            • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
            • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.
          (3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성
          • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
            • 이벤트 리스너
            • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
            • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.</aside>(1) 코틀린 인터페이스
              • 코틀린 인터페이스는 자바 8 인터페이스와 비슷하다.
              • 코틀린 인터페이스 안에는 추상 메소드뿐만 아니라 구현이 있는 메소드도 정의 할 수 있다. ( 자바 8의 디폴트 메소드와 비슷하다.)
              • 다만 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.
              a. 간단한 인터페이스 선언
              • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
              • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.
              b. 단순한 인터페이스 구현하기
              • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
              • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
              • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
              • 클래스는 오직 하나만 확장할 수 있다.
              • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
                • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
                • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
              • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.
              c. 인터페이스 안에 본문이 있는 메소드 정의하기
              • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
              • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.
              d. 동일한 메소드를 구현하는 다른 인터페이스 정의
              • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
                • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
                Class 'RedButton' must override public open fun showOff(): 
                Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
                
              e. 상속한 인터페이스의 메소드 구현 호출하기
              • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
              • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
              • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
                • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
                • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
                • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
                • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
                • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.
              (2) open, final, abstract 변경자: 기본적으로 final
              • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
              • 취약한 기반 클래스(fragile base class)
                • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
                • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
                • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
                • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
                • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
                • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
                • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
                • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.
              a. 열린 메소드를 포함하는 열린 클래스 정의하기
              • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
              • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
              • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
              • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
                • final override fun click(){ }
              b. 오버라이드 금지하기
              • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
              • 열린 클래스와 스마트 캐스트
                • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
                  • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
                  • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
                  • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
                  • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
                  • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.
              c. 추상 클래스 정의하기
              • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
                • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
                • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
                • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
              abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
                  abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
                  open fun stopAnimating() {
              	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
              	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
                  }
                  fun animateTwice() {
                  // 비추상함수는 final이다.
                  }
              }
              
              • 코틀린의 상속 제어 변경자
                • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
                • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
                • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
                변경자 이 변경자가 붙은 멤버는 … 설명
                final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
                open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
                abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
                추상 멤버에는 구현이 있으면 안 된다.    
                override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
                하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    
              (3) 가시성 변경자: 기본적으로 공개
              • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
              • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
              • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
                • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
                • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
              • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
                • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
                  • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
                  • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
                    • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
                  • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
                  • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
                  • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
                    public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
                    internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
                    protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
                    private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.
              a. 가시성 규칙을 위반한 예시
              • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
              • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
              • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
              • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
              • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
              • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
              • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.
              b. 코틀린의 가시성 변경자와 자바
              • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
                • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
                • 유일한 예외는 private 클래스이다.
                  • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
                • internal은?
                  • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
                • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
                  • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
                  • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
                • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
                  • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
                    • 이렇게 하는 이유는 2가지이다
                      1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
                      2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서
              (4) 내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
              • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
              • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
                • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
                • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.
              a. 직렬화할 수 있는 상태가 있는 뷰 선언
              • View 요소를 하나 만들다고 생각해보자.
                • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
                • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
              interface State : Serializable //Serializable을 구현한다.
              
              interface ViewComponent {
                  fun getCurrentState(): State
                  fun restoreState(state: State) {}
              }
              
              b. 자바에서 내부 클래스를 사용해 View 구현하기
              • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
              • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
              • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
              • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
                • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
                  • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
                  • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.
              c. 중첩 클래스를 사용해 코틀린에서 View 구하기
              • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
              • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
                중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
                이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
                • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
                • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.
              (5) 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
              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.left) + eval(e.right)
                      else ->
                          throw IllegalArgumentException("Unknown Expression")
                  }
              
              • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
                • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
              • 항상 디폴트 분기를 추가하는건 편하지 않다.
                • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
                • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.
              b. sealed 클래스로 식 표현하기
              • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
              sealed class Expr2 {
                  class Num(val value: Int) : Expr2()
                  //class Num2(val value: Int) : Expr()
                  class Sum(val left: Expr, val right: Expr) : Expr2()
              }
              
              fun eval2(e: Expr2): Int =
                  when (e) {
                      is Expr2.Num -> e.value
                      is Expr2.Sum -> eval(e.left) + eval(e.right)
                  }
              
              • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
                • sealed class는 기본 open이다.
                • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
              • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
              • sealed 인터페이스는 정의 할 수 없다.
                • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

              • 코틀린에서는 주생성자와 부생성자를 구별한다.
                • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
                • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
                • 초기화블록
              (1) 클래스 초기화: 주 생성자와 초기화 블록
              class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
                  val nickname: String
                  
                  init {
                      nickname = _nickname
                  }
              }
              
              • 주생성자 목적
                1. 생성자 파라미터를 지정한다.
                2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
              • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
              • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
              • 초기화블록은 주 생성자와 함께 사용된다.
                • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
              • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
              • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
                • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.
              b. 같은 코드를 다른 방법으로 선언하기
              • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
              • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
              • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
                1. 프로퍼티를 초기화하는 식
                2. 초기화 블록 안에서
              c. 더 간결하게 선언하기
              • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
                • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.
              d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.
              • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
              • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
              • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.
              e. 기반 클래스(부모 클래스)가 있을 경우
              • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
              • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.
              f. 클래스에 별도로 생성자를 정의하지 않을 경우
              • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
                • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
              class RadioButton: Button()
              
              • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
              • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
              • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.
              g. 생성자 앞에 private 붙이기
              • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
                • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
              • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
              • 비공개 생성자에 대한 대안
                • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
                • 코틀린은 그런 경우를 언어에서 기본지원한다.
                  • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.
              (2) 부생성자: 상위 클래스를 다른 방식으로 초기화
              • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
              • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
                • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.
              a. 부생성자가 필요한 경우
              • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
                • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
                  • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
                  • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
                • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }
              b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.
              • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
              • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.
              c. this()를 통해 자신의 다른 생성자를 호출하기
              • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
              • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
              • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.
              (3) 인터페이스에 선언된 프로퍼티 구현
              interface User {
                  val nickname: String
              }
              
              • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
              • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
                • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.
              b. 인터페이스의 프로퍼티 구현하기c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)(4) 게터와 세터에서 뒷받침하는 필드에 접근
              class User3(val name: String) {
                  var address: String = "unspecified"
                      set(value: String) {
                          println(
                              """
                              Address was changed for $name:
                          "${field}" -> "${value}".""".trimIndent()
                          ) //뒷받침하는 필드 값 읽기
                          field = value //뒷받침하는 필드값 변경하기
                      }
              }
              
              fun main() {
                  val user: User3 = User3("Lee")
                  user.address = "Seoul kangnam"
                  //결과값
                  //    Address was changed for Lee:
                  //"unspecified" -> "Seoul kangnam".
              }
              
              • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
                • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.
              (5) 접근자의 가시성 변경
              • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.
              a. 비공개 세터가 있는 프로퍼티 선언하기

              3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

              • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
                • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
                • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
                • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.
              (1) 모든 클래스가 정의해야하는 메소드
              • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
              • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자
              a. 문자열 표현: toString
              • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
                • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.
              b. 객체의 동등성: equals
              • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
                • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
                • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
              • 자바에서의 == 비교
                • 원시타입
                  • 두 피연산자의 값이 같은지 비교한다.
                • 참조타입
                  • 두 피연산자의 주소값이 같은지 비교한다.
                  • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
                  • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
                    • equals를 오버라이드하여 인스턴스를 비교해보자.
              fun main() {
                  val client1 = Client("고양이", 1111)
                  val client2 = Client("고양이", 1111)
                  println(client1 == client2) //true
              }
              
              class Client(val name: String, val postalCode: Int) {
                  override fun equals(other: Any?): Boolean {
                      if (other == null || other !is Client)
                      //null이거나 Client 객체가 아니라면?
                          return false
                      return name == other.name &&
                              postalCode == other.postalCode
                  }
              
                  override fun toString() = "Client(name=$name, postalCode=$postalCode)"
                  //결과값
                  //override 시: Client(name=kim, postalCode=1111)
                  //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
              }
              
              c. 해시 컨테이너: hashCode()
              • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
              • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
                • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
                  • false
                    • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
                    • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
                      • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
                        • Client는 이를 어기고있다.
                        • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
                          • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                            • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
                          • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
                • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false
              (2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
              • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
                • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
              data class Client(val name: String, val postalCode: Int)
              
              • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
                • 인스턴스 간 비교를 위한 equals
                • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
              • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString
              a. 데이터 클래스와 불변성: Copy() 메소드
              • 데이터 클래스의 프로퍼티는 val을 권장한다.
                • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
                  • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
                  • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
                  • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
                    • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
              • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
                • Copy()
                  • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
                  • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
                    • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
                    • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }
              (3) 클래스 위임: by 키워드 사용
              • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
                • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
                  • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
                • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
                  • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
              • 데코레이터 패턴 (Decorator)
                • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
                  • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
                  • 이때 새로 정의해야 하는 기능은!
                    • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
                • 단점: 준비 코드가 상당히 많이 필요하다.
                  • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
                    • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
                    • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
                  • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

              • object 키워드를 사용하는 경우
                • 객체 선언(object declaration)은 싱글턴을 정의하는 방법 중 하나이다.
                • 동반 객체 (companion object)는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 때 쓰인다.
                  • 동반 객체 메소드에 접근 할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다.
                • 객체 식은 자바의 무명 내부 클래스 (annonymous inner class) 대신 쓰인다.
              (1) 객체 선언: 싱글턴을 쉽게 만들기
              • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
                • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
                • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
                  • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
                    • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
                    • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
                  • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
                • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.
              a. 객체 선언을 사용해 Comparator 구현하기
              • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
              • Comparator 안에는 데이터를 저장할 필요가 없다.
                • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
              • 싱글톤과 의존관계 주입
                • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
                  • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
                  • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
                    • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
                      • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.
              b. 중첩 객체를 사용해 Comparator 구현하기(2) 동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
              • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
              • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
                • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
                  • static이 없다면 객체로 만들어서 가져와야한다.
              • 코틀린에서는 이런 기능들을 활용할 수 있다.
                • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
                • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
              • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
                • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
                  • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
              • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
                • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
                • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.
              (3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성
              • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
                • 이벤트 리스너
                • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
                • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.</aside>(1) 코틀린 인터페이스
                  • 코틀린 인터페이스는 자바 8 인터페이스와 비슷하다.
                  • 코틀린 인터페이스 안에는 추상 메소드뿐만 아니라 구현이 있는 메소드도 정의 할 수 있다. ( 자바 8의 디폴트 메소드와 비슷하다.)
                  • 다만 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.
                  a. 간단한 인터페이스 선언
                  • click이라는 추상 메소드가 있는 인터페이스를 정의한다.
                  • 이 인터페이스를 구현하는 모든 비추상 클래스는 click에 대한 구현을 제공해야 한다.
                  b. 단순한 인터페이스 구현하기
                  • 자바에서는 class 확장은 extends로, interface 구현은 implement 키워드를 사용한다.
                  • 코틀린에서는 둘 다 콜론 (:) 을 붙이고 클래스 확장과 인터페이스 구현을 한다.
                  • 자바와 마찬가지로 class는 interface를 원하는 만큼 개수 제한 없이 마음대로 구현할 수 있다.
                  • 클래스는 오직 하나만 확장할 수 있다.
                  • 자바는 @Override 어노테이션과 비슷한 override 변경자는 상위 클래스나 상위 인터페이스에 있는 프로퍼티나 메소드를 오버라이드한다는 표시이다.
                    • 자바와 달리 코틀린에서는 override 변경자를 꼭 사용해야한다. override 변경자는 실수로 상위 클래스의 메소드를 오버라이드 하는 경우를 방지해준다.
                    • 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우 컴파일이 안되기 때문에 override를 붙이거나 메소드 이름을 바꿔야한다.
                  • 인터페이스 메소드도 디폴트 구현을 제공할 수 있다. 그런 경우 메소드 앞에 default를 붙여야 하는 자바 8과 달리 코틀린에서는 그냥 메소드 본문에 메소드 시그니처 뒤에 추가하면 된다.
                  c. 인터페이스 안에 본문이 있는 메소드 정의하기
                  • 이 인터페이스를 구현하는 클래스는 click에 대한 구현을 제공해야한다.
                  • 반면 showOff 메소드의 경우 새로운 동작을 정의할 수 도있고, 그냥 정의를 생략해서 디폴트 구현을 사용할 수 도 있다.
                  d. 동일한 메소드를 구현하는 다른 인터페이스 정의
                  • 한 클래스의 이 두 인터페이스를 구현하면 어떻게 될까? 어느쪽 showOff()가 선택될까?
                    • 어느 쪽도 선택되지 않는다. 클래스가 구현하는 두 상위 인터페이스에 정의된 showOff() 구현을 대체할 오버라이딩 메소드를 직접 제공하지 않으면 다음과 같은 컴파일 오류가 발생한다.
                    Class 'RedButton' must override public open fun showOff(): 
                    Unit defined in com.anehta.athenakotlinlab.Clickable because it inherits multiple interface methods of it
                    
                  e. 상속한 인터페이스의 메소드 구현 호출하기
                  • 이름과 시그니처가 같은 멤버 메소드에 대해 둘 이상의 디폴트 구현이 있는 경우, 인터페이스를 구현하는 하위 클래스에 명시적으로 새로운 구현을 제공해야한다.
                  • 상위 타입의 이름을 꺾쇠 괄호 (<>) 사이에 넣어서 “super”를 지정하면 어떤 상위 타입의 멤버 메소드를 호출할 지 지정할 수 있다.
                  • 자바에서 코틀린 메소드가 있는 인터페이스 구현하기
                    • 코틀린은 자바6와 호환되게 설계됐다. 따라서 인터페이스의 디폴트 메소드를 지원하지 않는다.
                    • 따라서 코틀린은 디폴트 메소드가 있는 인터페이스를 1. 일반 인터페이스와 2.디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다.
                    • 인터페이스에는 메소드 선언만 들어간다. 인터페이스와 함께 생성되는 클래스에는 모든 디폴트 메소드 구현이 정적 메소드로 들어간다.
                    • 그러므로 디폴트 인터페이스가 포함된 코틀린 인터페이스를 자바 클래스에서 상속해 구현하고싶다면 코틀린에서 메소드 본문을 제공하는 메소드를 포함하는 모든 메소드에 대한 본문을 작성해야한다.
                    • 하지만 코틀린 1.5부터는 코틀린 컴파일러가 자바 인터페이스의 디폴트 메소드를 생성해준다.
                  (2) open, final, abstract 변경자: 기본적으로 final
                  • 자바에서는 final로 상속을 금지하는 클래스빼고는, 모든 클래스는 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.
                  • 취약한 기반 클래스(fragile base class)
                    • 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
                    • 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
                    • 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
                    • 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
                    • “상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
                    • 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
                    • 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
                    • 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.
                  a. 열린 메소드를 포함하는 열린 클래스 정의하기
                  • open을 붙였으므로 다른클래스가 이 RichButton 클래스를 상속할 수 있다.
                  • 이와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티 앞에도 open을 붙여야한다.
                  • 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메소드는 기본적으로 열려있다.
                  • 오버라이드하는 메소드의 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메소드 앞에 final을 명시해야한다.
                    • final override fun click(){ }
                  b. 오버라이드 금지하기
                  • “final”이 없는 “override” 메소드나 프로퍼티는 기본적으로 열려있다.
                  • 열린 클래스와 스마트 캐스트
                    • 클래스의 기본적인 상속 상태를 final 함으로써 얻을 수 있는 가장 큰 장점은 스마트 캐스트가 가능하다는 점이다.
                      • 스마트 캐스트는 타입 검사 뒤에 변경 될 수 없는 변수에만 적용 가능하다.
                      • 클래스의 프로퍼티의 경우 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미이다.
                      • 이것은 다른말로 프로퍼티가 final이어야만 한다는 뜻이기도한다.
                      • 그 프로퍼티가 final이 아니라면, 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰 수 있다.
                      • 프로퍼티는 기본적으로 final이기때문에 따로 고민할 필요 없이 대부분의 프로퍼티를 스마트 캐스트에 활용할 수 있다.
                  c. 추상 클래스 정의하기
                  • 자바처럼 코틀린에서도 클래스를 abstract로 선언 할 수 있다.
                    • abstract로 선언한 추상 클래스는 인스턴스화 할 수 없다.
                    • 추상 클래스에는 구현이 없는 추상 멤버가 있기때문에 하위 클래스에서 그 추상 멤버를 오버라이드 해야하만 한다.
                    • 추상멤버는 항상 열려있다. 따라서 추상멤버 앞에 open 변경자를 명시할 필요가 없다.
                  abstract class Animated { // 이 클래스는 추상클래스이다. 이 클래스의 인스턴스를 만들 수 없다.
                      abstract fun animate() // 이 함수는 추상함수이다. 이 함수에는 구현이 없다. 하위클래스에는 이 함수를 반드시 오버라이드해야한다.
                      open fun stopAnimating() {
                  	  // 추상 클래스에 속했더라도 비추상함수는 기본적으로 final이다.
                  	  // 원한다면 open으로 오버라이드를 허용 할 수 있다.
                      }
                      fun animateTwice() {
                      // 비추상함수는 final이다.
                      }
                  }
                  
                  • 코틀린의 상속 제어 변경자
                    • 인터페이스의 멤버의 경우 final, open, abstract를 사용하지 않는다.
                    • 인터페이스 멤버는 항상 열려있으며 final로 변경할 수 없다.
                    • 인터페이스 멤버에게 본문이 없으면 자동으로 추상멤버가 된다. (따로 abstract 키워드를 붙일 필요가 없다.)
                    변경자 이 변경자가 붙은 멤버는 … 설명
                    final 오버라이드 할 수 없음 클래스 멤버의 기본 변경자이다.
                    open 오버라이드 할 수 있음 반드시 open을 명시해야 오버라이드 할 수 있다.
                    abstract 반드시 오버라이드 해야함 추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
                    추상 멤버에는 구현이 있으면 안 된다.    
                    override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중 오버라이드하는 멤버는 기본적으로 open이다.
                    하위 클래스의 오버라이드를 금지하려면 final을 명시해야한다.    
                  (3) 가시성 변경자: 기본적으로 공개
                  • 가시성 변경자(visibility modifier)는 코드 기반에 있는 선언에 대한 클래스 외부 접근을 제어한다.
                  • 어떤 클래스의 구현에 대한 접근을 제한함으로써 그 클래스에 의존하는 외부코드를 깨지 않고도 클래스 내부 구현을 변경할 수 있다.
                  • 기본적으로 코틀린 가시성 변경자는 자바와 비슷하다.
                    • 자바와 같은 public, protected, private 변경자가 있다. 하지만 코틀린의 기본 가시성은 자바와 다르다.
                    • 코틀린에서는 아무 변경자도 없는 경우 선언은 모두 public으로 공개된다.
                  • 자바의 기본 가시성인 패키지 전용(package-private)은 코틀린에 없다. 코틀린은 패키지를 네임스페이스(namespace)를 관리하기 위한 용도로만 사용한다. 그래서 패키지를 가시성 제어에 사용하지 않는다.
                    • 패키지 전용 가시성에 대한 대안으로는 코틀린에는 internal이라는 새로운 가시성 변경자를 도입했다. (즉, 모듈 내부를 말한다.)
                      • internal은 “모듈 내부에서만 볼 수 있음”이라는 뜻이다.
                      • 모듈(module)은 한 번에 한꺼번에 컴파일되는 코틀린 파일들을 의미한다.
                        • 인텔리J나 이클립스, 메이븐, 크레이들 등의 프로젝트가 모듈이 될 수 있고, 앤트 태스크가 한 번 실행될 때 함께 컴파일되는 파일의 집합도 모듈이 될 수 있다.
                      • 모듈 내부 가시성은 모듈의 구현에 대해 진정한 캡슐화를 제공한다는 장점이 있다. 자바에서는 패키지가 같은 클래스를 선언하기만 하면 어떤 프로젝트의 외부에 있는 코드라도 패키지 내부에 있는 패키지 전용 선언에 쉽게 접근 할 수 있다. 그래서 모듈의 캡슐화가 쉽게 깨진다.
                      • 코틀린에서는 최상위 선언에 대해 private 가시성을 허용한다는 점이다. 그런 최상위 선언에는 클래스, 함수, 프로퍼티 등이 포함된다.
                      • 비공개 가시성인 최상위 선언은 그 선언이 들어있는 파일 내부에서만 사용할 수 있다. 이 또한 하위 시스템의 자세한 구현 사항을 외부에 감추고 싶을 때 유용하다.변경자 클래스 멤버 최상위 선언
                        public(기본 가시성임) 모든 곳에서 볼 수 있다. 모든 곳에서 볼 수 있다.
                        internal 같은 모듈 안에서만 볼 수 있다. 같은 모듈안에서만 볼 수 있다.
                        protected 하위 클래스 안에서만 볼 수 있다. 적용 불가
                        private 같은 클래스 안에서만 볼 수 있다. 같은 파일 안에서만 볼 수 있다.
                  a. 가시성 규칙을 위반한 예시
                  • 코틀린은 public 함수인 giveSpeech 안에서 그보다 가시성이 더 낮은 (이 경우 inernal) 타입인 TalkativeButton을 참조하지 못하게 된다.
                  • 그 클래스 자신의 가시성과 같거나 더 높아야한다. 또한 메소드의 시그니처에 사용된 모든 타입의 가시성은 그 메소드의 가시성과 같거나 더 높아야한다는 일반적인 규칙에 해당한다.
                  • 이런 규칙은 어떤 함수를 호출하거나 어떤 클래스를 확장할 때 필요한 모든 타입에 접근 할 수 있게 보장해준다.
                  • 컴파일 오류를 없애려면 giveSpeech 확장 함수의 가시성을 internal로 바꾸거나, TalkativeButton 클래스의 가시성을 public으로 바꿔야한다.
                  • 자바에서는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, 코틀린에서는 그렇지 않다.
                  • protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스안에서만 보인다.
                  • 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근 할 수 없다는 사실을 여기서 한 번 더 짚고 넘어가야한다.
                  b. 코틀린의 가시성 변경자와 자바
                  • 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
                    • 그렇게 컴파일된 코틀린 선언의 가시성은 마치 자바에서 똑같은 가시성을 사용해 선언한 경우와 같다.
                    • 유일한 예외는 private 클래스이다.
                      • 자바에서는 클래스를 private으로 만들 수 없으므로 내부적으로는 코틀린은 private 클래스를 패키지-전용 클래스로 컴파일한다.
                    • internal은?
                      • 자바에는 internal에 딱 맞는 가시성이 없다. 패키지-전용 가시성은 internal 과 전혀 다르다. 모듈은 보통 여러 패키지로 이뤄지며 서로 다른 모듈에 같은 패키지에 속한 선언이 들어 있을 수도 있다. 따라서 internal 변경자는 바이트코드상에서 public이 된다.
                    • 코틀린 선언과 그에 해당하는 자바 선언 (또는 바이트코드 표현)에 이런 차이가 있기때문에 코틀린에서 접근할 수 없는 대상을 자바에서 접근 할 수 있는 경우가 생긴다.
                      • 예를들어 다른 모듈에 정의된 internal 클래스나 internal 최상위 선언을 모듈 외부의 자바 코드에서는 접근 할 수 있다. 코틀린은 안된다.
                      • 또한 코틀린에서 protected로 정의한 멤버를 코틀린 클래스와 같은 패키지에 속한 자바 코드에서는 접근 할 수 있다.
                    • 하지만 코틀린 컴파일러가 internal 멤버의 이름을 보기 나쁘게 바꾼다는(mangle) 사실을 기억하라.
                      • 그로인해 기술적으로는 internal 멤버를 자바에서 문제없이 사용할 수 있지만, 멤버이름이 보기 불편하고 코드가 못생겨보인다.
                        • 이렇게 하는 이유는 2가지이다
                          1. 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우, 그 하위 클래스 내부의 메소드 이름이 우연히 상위 클래스의 internal 메소드와 같아져서 내부 메소드를 오버라이드하는 경우를 방지하기 위해서
                          2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위해서
                  (4) 내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
                  • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근 할 수 없다는 점이다.
                  • 자바처럼 코틀린에서도 클래스 안에 다른 클래스를 선언할 수 있다.
                    • 클래스 안에 다른 클래스를 선언하면 도우미 클래스를 캡슐화하거나, 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때 유용하다.
                    • 자바와의 차이는 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.
                  a. 직렬화할 수 있는 상태가 있는 뷰 선언
                  • View 요소를 하나 만들다고 생각해보자.
                    • 그 View의 상태를 직렬화해야 한다. 뷰를 직렬화하는 일은 쉽지 않지만, 필요한 모든 데이터를 다른 도우미 클래스로 복사 할 수는 있다.
                    • 이를 위해 State 인터페이스를 선언하고 Serializable을 구현한다. View 인터페이스 안에는 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState와 restoreState 메소드 선언이 있다.
                  interface State : Serializable //Serializable을 구현한다.
                  
                  interface ViewComponent {
                      fun getCurrentState(): State
                      fun restoreState(state: State) {}
                  }
                  
                  b. 자바에서 내부 클래스를 사용해 View 구현하기
                  • ButtonTest 클래스의 상태를 저장하는 클래스는 ButtonTest 클래스 내부에 선언하면 편하다.
                  • State 인터페이스를 구현한 ButtonState 클래스를 정의해서 ButtonTest에 대한 구체적인 정보를 저장한다.
                  • getCurrentState 메소드안에서는 ButtonState의 새 인스턴스를 만들다. 실제로는 ButtonState안에 필요한 모든 정보를 추가해야한다.
                  • 이 코드의 어디가 잘못된걸까? 왜 선언한 버튼의 상태를 직렬화하면 java.io.NotSerializableException: ButtonTest오류가 발생할까?
                    • 직렬화하려는 변수는 ButtonState타입의 state였는데 왜 ButtonTest를 직렬화 할 수 없다 할까? 다른 클래스안에 정의한 클래스는 자동으로 내부클래스(inner class)가 된다는 사실을 기억한다면 어디가 잘못된 건지 명확히 알 수 있다.
                      • 이 예제의 ButtonState클래스는 바깥쪽 ButtonTest클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다. ButtonTest를 직렬화할 수 없으므로 버튼에 대한 참조가 ButtonState를 방해한다.
                      • 이걸 해결하려면 ButtonState를 static 클래스로 선언해야한다. 자바에서 중첩 클래스를 static으로 선언하면 그 클래스를 둘러산 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.
                  c. 중첩 클래스를 사용해 코틀린에서 View 구하기
                  • 코틀린에서 중첩된 클래스가 기본적으로 동작하는 방식은 위와 정반대이다.
                  • 코틀린 중첩 클래스에 아무런 변경자가 붙지 않으면 자바 static 중첩 클래스와 같다.클래스 B 안에서 정의된 클래스 A 자바에서는 코틀린에서는
                    중첩 클래스 (바깥쪽 클래스에 대한 참조를 저장히지 않음) static class A class A
                    이너 클래스 (바깥쪽 클래스에 대한 참조를 저장함) class A inner class A
                    • 이를 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하게 만들고 싶다면 inner변경자를 붙여야한다.
                    • 코틀린에서 바깥쪽 클래스의 인스턴스를 가리키는 참조를 표기하는 방법도 자바와 다르다. 내부클래스 Inner안에서 바깥쪽 클래스 Outer의 참조에 접근하려면 this@Outer라고 써야한다.
                  (5) 봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
                  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.left) + eval(e.right)
                          else ->
                              throw IllegalArgumentException("Unknown Expression")
                      }
                  
                  • 코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때 반드시 디폴트 분기인 else 분기를 덧붙이게 강조한다.
                    • 이 예제의 else 분기는 의미있는 값이 없으므로 예외를 던진다.
                  • 항상 디폴트 분기를 추가하는건 편하지 않다.
                    • 그리고 디폴트 분기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도, 컴파일러가 when의 모든경우를 처리하는지 제대로 검사할 수 없다.
                    • 혹 실수로 새로운 클래스 처리를 하지 않았다해도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.
                  b. sealed 클래스로 식 표현하기
                  • 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위클래스 정의를 제한할 수 있다.
                  sealed class Expr2 {
                      class Num(val value: Int) : Expr2()
                      //class Num2(val value: Int) : Expr()
                      class Sum(val left: Expr, val right: Expr) : Expr2()
                  }
                  
                  fun eval2(e: Expr2): Int =
                      when (e) {
                          is Expr2.Num -> e.value
                          is Expr2.Sum -> eval(e.left) + eval(e.right)
                      }
                  
                  • when식에서 sealed class의 모든 하위클래스를 처리한다면 디폴트 분기가 필요없다.
                    • sealed class는 기본 open이다.
                    • 나중에 sealed class에 하위클래스가 추가되면 컴파일 에러가 뜬다. 추가해줘야한다는 걸 알려준다.
                  • 내부적으로는 Expr 클래스는 private 생성자를 가진다. 즉, 그 생성자는 내부 클래스에서만 호출 할 수 있다.
                  • sealed 인터페이스는 정의 할 수 없다.
                    • 이게 가능하다면, 그 인터페이스를 자바쪽에서 구현하지 못하게 막을 수 있는 수단이 코틀린 컴파일러에게 없기 때문이다.

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

                  • 코틀린에서는 주생성자와 부생성자를 구별한다.
                    • 주생성자(primary constructor): 클래스를 초기화할 때 주로 사용하는 간략한 생성자로, 본문 밖에서 정의한다.
                    • 부생성자(secondary constructor): 클래스 본문 안에서 정의한다.
                    • 초기화블록
                  (1) 클래스 초기화: 주 생성자와 초기화 블록
                  class ShoppingUser constructor(_nickname: String) { //클래스 선언 뒤에 constructor를 붙인다. 파라미터가 하나만 있는 주생성자 
                      val nickname: String
                      
                      init {
                          nickname = _nickname
                      }
                  }
                  
                  • 주생성자 목적
                    1. 생성자 파라미터를 지정한다.
                    2. 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
                  • constructor 키워드는 주생성자, 부생성자를 정의할 때 사용한다.
                  • init 키워드는 초기화 블록을 시작한다. 초기화 블록에는 객체가 만들어질 때(인스턴스화될 때), 실행될 초기화 코드가 들어간다.
                  • 초기화블록은 주 생성자와 함께 사용된다.
                    • 주생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없으므로 초기화 블록이 필요하다. 필요하다면 클래스 안에 여러 초기화 블록을 선언 할 수 있다.
                  • 생성자 파라미터 _nickname에서 맨 앞의 밑줄(_)은 프로퍼티와 생성자 프로퍼티를 구분해준다.
                  • 자바처럼 this.nickname = nickname으로 생성자 프로퍼티와 프로퍼티 이름을 같게하고 프로퍼티에 this를 써서 모호성을 없애도 된다.
                    • 여기서 this.nickname은 클래스 안에 선언된 val nickname: String을 말하고, 오른쪽 nickname은 생성자 프로퍼티로 받은 nickname을 말한다.
                  b. 같은 코드를 다른 방법으로 선언하기
                  • nickname 프로퍼티를 초기화하는 코드를 val nickname 프로퍼티 선언에 포함 시킬 수 있어서 init블록에 넣을 필요가 없다.
                  • 주생성자 앞에 별다른 어노테이션이나 가시성변경자가 없다면 constructor를 생략해도 된다.
                  • 주생서자 파라미터를 참조 할 수 있는 경우는 2개이다.
                    1. 프로퍼티를 초기화하는 식
                    2. 초기화 블록 안에서
                  c. 더 간결하게 선언하기
                  • 클래스 본문에서 val 키워드를 통해서 프로퍼티를 정의하지 말고 다른방법으로 해보자.
                    • 주생성자의 파라미터로 프로퍼티를 초기화한다면, 그 주생성자 파라미터 이름앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 할 수 있다.
                  d. 생성자 파라미터에도 디폴트 값을 정의 할 수 있다.
                  • 만약 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
                  • 이렇게 자동으로 만들어진 파라미터없는 생성자는 디폴트값을 사용해 클래스를 초기화한다.
                  • 의존관계 주입 (DI) 프레임워크 등 자바 라이브러리 중에는 파라미터가 없는 생성자를 통해 객체를 생성해야만, 라이브러리 사용이 가능한 경우가 있는데, 코틀린이 제공하는 파라미터 없는 생성자는 그런 라이브러리와 통합을 쉽게 해준다.
                  e. 기반 클래스(부모 클래스)가 있을 경우
                  • 클래스에 기반 클래스가 존재한다면, 주생성자에서 기반클래스의 생성자를 호출해야할 필요가 있다.
                  • 기반클래스를 초기화하려면 기반 클래스 이름뒤에 괄호를 치고 생성자 인자를 넘긴다.
                  f. 클래스에 별도로 생성자를 정의하지 않을 경우
                  • 클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만들어준다.
                    • 그래서 Button 클래스를 상속한 하위클래스는 반드시 Button 클래스의 생성자를 호출해야한다.
                  class RadioButton: Button()
                  
                  • 이 규칙으로 인해 기반클래스의 이름뒤에는 꼭 빈 괄호가 들어가야한다. (물론 생성자 인자가 있다면 괄호안에 인자가 들어간다.)
                  • 반면 인터페이스는 생성자가 없기때문에 어떤 클래스가 인터페이스를 구현하는 경우, 그 클래스의 상위 클래스 목록에 있는 인터페이스 이름 뒤에는 아무런 괄호가 없다.
                  • 클래스 정의에 있는 상위 클래스 및 인터페이스 목록에서 이름뒤에 괄호가 붙었는지 살펴보면 쉽게 기반클래스와 인터페이스를 구별 할 수 있다.
                  g. 생성자 앞에 private 붙이기
                  • 어떤 클래스를 클래스 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
                    • 유틸리티 함수를 담아두는 역할만하는 클래스는 인스턴스화할 필요가 없고, 싱클턴인 클래스는 미리 정한 팩토리 메소드 등의 생성 방법을 통해서만 객체를 생성해야만 한다.
                  • Secretive 클래스 안에는 주생성자밖에 없고 그 주생성자는 비공개이므로 외부에서는 Secretive를 인스턴스화할 수 없다.
                  • 비공개 생성자에 대한 대안
                    • 자바에서는 이런 일반적인 요구사항을 명시할 방법이 없으므로 어쩔 수 없이 private 생성자를 정의해서 클래스를 다른 곳에 인스턴스화하지 못하게 막는 경우가 생긴다.
                    • 코틀린은 그런 경우를 언어에서 기본지원한다.
                      • 정적 유틸리티 함수 대신 최상위 함수를 사용할 수 있고, 싱글턴을 사용하고 싶으면 객체를 선언하면 된다.
                  (2) 부생성자: 상위 클래스를 다른 방식으로 초기화
                  • 실제로 대부분 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고, 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 그래서 코틀린은 간단한 주생성자 문법을 제공한다. (대부분 이런 간단한 주생성자 구문만으로도 충분하다.)
                  • 일반적으로 코틀린에서는 생성자가 여럿 있는 경우가 자바보다는 훨씬 적다. 자바에서 오버로드한 생상자가 필요한 상황 중 상당수는 코틀린의 디폴트 파라미터 값과 이름붙인 인자문법을 사용해 해결 할 수 있다.
                    • 인자에 대한 디폴트값을 제공하기 위해 부생성자를 여럿 만들지말라. 대신 파라미터의 디폴트값을 생성자 시그니처에 직접 명시해라.
                  a. 부생성자가 필요한 경우
                  • 가장 일반적인 상황은 프레임워크 클래스를 확장해야하는데 , 여러가지 방법으로 인스턴스를 초기화할 수 있게 다양한 생성자를 지원해야하는 경우이다.
                    • 예를들어 자바에서 선언된 생성자가 2개인 View 클래스가 있다고하자. 그 클래스를 코틀린으로는 다음과 같이 비슷하게 정의할 수 있다.
                      • 이 클래스는 주생성자를 선언하지 않고 부생성자만 2가지 선언한다. (부생성자 선언 방식 역시 constructor 키워드를 사용한다. )
                      • 필요에 따라 얼마든지 부생성자를 정의 할 수 있다.
                    • open class View { constructor(ctx: Context) { } constructor(ctx: Context, attr: AttributeSet) { } }
                  b. 클래스 확장 시 똑같이 부생성자를 정의할 수 있다.
                  • 여기서 2개의 부생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
                  • 그림에서 화살표는 생성자가 상위 클래스 생성자에게 객체 생성을 위임한다는 사실을 표시한다.
                  c. this()를 통해 자신의 다른 생성자를 호출하기
                  • 클래스에 주생성자가 없다면 모든 부생성자는 반드시 상위클래스를 초기화하거나, 클래스 자신의 다른 생성자에게 생성을 위임해야한다.
                  • 각 부생성자에게 객체 생성을 위임하는 화살표를 따라가면, 그 끝에는 상위 클래스 생성자를 호출하게 되어있다.
                  • 부생성자가 필요한 주된 이유는 자바 상호운용성이다. 또한 클래스 인스턴스를 생성할 때 파라미터 목록이 다른 생성 방법이 여럿 존재하는 경우에는 부 생성자를 여럿 둘 수 밖에 없다.
                  (3) 인터페이스에 선언된 프로퍼티 구현
                  interface User {
                      val nickname: String
                  }
                  
                  • User 인터페이스를 구현하는 클래스가 nickname의 값을 얻을 수 있는 방법을 제공해야한다는 뜻이다.
                  • 인터페이스에 있는 프로퍼티 선언에는 뒷받침하는 필드나 게터등의 정보가 들어있지 않다.
                    • 인터페이스는 아무 상태도 포함 할 수 없으므로 상태를 저장해야할 필요가 있다면, 인터페이스를 구현한 하위클래스에서 상태 저장을 위한 프로퍼티 등을 만들어야한다.
                  b. 인터페이스의 프로퍼티 구현하기c. 인터페이스 안에 게터와 세터가 있는 프로퍼티를 선언하기 (물론 게터와 세터를 뒷받침하는 필드는 참조할 수없다.)(4) 게터와 세터에서 뒷받침하는 필드에 접근
                  class User3(val name: String) {
                      var address: String = "unspecified"
                          set(value: String) {
                              println(
                                  """
                                  Address was changed for $name:
                              "${field}" -> "${value}".""".trimIndent()
                              ) //뒷받침하는 필드 값 읽기
                              field = value //뒷받침하는 필드값 변경하기
                          }
                  }
                  
                  fun main() {
                      val user: User3 = User3("Lee")
                      user.address = "Seoul kangnam"
                      //결과값
                      //    Address was changed for Lee:
                      //"unspecified" -> "Seoul kangnam".
                  }
                  
                  • 클래스 프로퍼티를 사용하는 쪽에서 프로퍼티를 읽는방법이나 쓰는 방법은 뒷받침하는 필드의 유무와 관계가 없다.
                    • 컴파일러는 디폴트 접근자 구현을 사용하건 직접 게터나 세터를 정의하건 관계없이 게터나 세터에서 field를 사용하는 프로퍼티에 대해 뒷받침하는 필드를 생성해준다.
                  (5) 접근자의 가시성 변경
                  • 접근자의 가시성은 기본적으로 프로퍼티 가시성과 같다.
                  a. 비공개 세터가 있는 프로퍼티 선언하기

                  3. 컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임

                  • 자바 플랫폼에서는 클래스가 equals, hashCode, toString 등의 메소드를 구현해야한다.
                    • 자바 IDE는 이런 메소드를 자동으로 만들어주어서 직접 생성하지않아도 된다.
                    • 그러나 생성해줄뿐 코드 베이스가 번잡해지는건 마찬가지이다.
                    • 코틀린 컴파일러는 한걸음 더 나가서 이런 기계적으로 생성해야하는 메소드를 보이지 않는 곳에서 진행한다.
                  (1) 모든 클래스가 정의해야하는 메소드
                  • 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드할 수 있다.
                  • 고객이름과 우편번호를 저장하는 간단한 Client 클래스를 만들어서 살펴보자
                  a. 문자열 표현: toString
                  • 주로 디버깅과 로깅 시에 이 메소드를 사용한다.
                    • toStirng을 오버라이드해서 기본 구현을 바꿀 수 있다.
                  b. 객체의 동등성: equals
                  • 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 봐야한다.
                    • 이름과 우편번호가 같지만 기본적으로 equals는 객체의 동등성을 검사하는 것이기 때문에 false라고 뜬다.
                    • 이런 경우 eqauls를 오버라이드하여 참조 동일성을 검사하도록 만들자.
                  • 자바에서의 == 비교
                    • 원시타입
                      • 두 피연산자의 값이 같은지 비교한다.
                    • 참조타입
                      • 두 피연산자의 주소값이 같은지 비교한다.
                      • 자바에서 두 객체의 동등성을 보려면 equals를 호출해야한다. (==를 호출하면 문제가 될 수 있다.)
                      • 반면, 코틀린에서는 == 연산자가 두 객체를 비교하는 기본적인 방법이다. == 은 내부적으로 equals를 호출해서 객체를 비교한다.
                        • equals를 오버라이드하여 인스턴스를 비교해보자.
                  fun main() {
                      val client1 = Client("고양이", 1111)
                      val client2 = Client("고양이", 1111)
                      println(client1 == client2) //true
                  }
                  
                  class Client(val name: String, val postalCode: Int) {
                      override fun equals(other: Any?): Boolean {
                          if (other == null || other !is Client)
                          //null이거나 Client 객체가 아니라면?
                              return false
                          return name == other.name &&
                                  postalCode == other.postalCode
                      }
                  
                      override fun toString() = "Client(name=$name, postalCode=$postalCode)"
                      //결과값
                      //override 시: Client(name=kim, postalCode=1111)
                      //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502
                  }
                  
                  c. 해시 컨테이너: hashCode()
                  • 자바에서는 equals를 오버라이드할 때 반드시 hashCode도 함께 오버라이드 해야한다.
                  • 원소가 “고양이”이라는 고객 하나 뿐인 집합을 만들자.
                    • 그 후 새로 원래의 “고양이”와 똑같으 프로퍼티를 포함하는 새로운 Client 인스턴스를 만들어서 그 인스턴스가 집합에 들어있는지 검사해보자.
                      • false
                        • Client 클래스가 hashCode 메소드를 정의하지 않았기 때문이다.
                        • JVM 언어에서는 hashCode가 지켜야하는 제약이 있다.
                          • “equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.”
                            • Client는 이를 어기고있다.
                            • HashSet은 원소를 비교할 때 비용을 줄이기 위해 먼저 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제 값을 비교한다.
                              • 두 Client는 이미 해시코드가 다르기때문에 두번째 인스턴스가 집합 안에 들어있지 않다고 판단한다.
                                • 코틀린에서는 이 모든 메소드를 자동으로 생성해준다.
                              • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
                    • val processed = hashSetOf(Client("고양이",1111)) println(processed.contains(Client("고양이",1111))) //false
                  (2) 데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
                  • 어떤 클래스가 데이터를 저장하는 역할만 수행한다면 toString, equals, hashCode 를 반드시 오버라이드 해야한다.
                    • 코틀린은 이런 메소드를 IDE를 통해서 생성할 필요도 없다. data라는 변경자를 클래스앞에 붙여주면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
                  data class Client(val name: String, val postalCode: Int)
                  
                  • equals 와 hashCode는 주 생성자에 나열된 모든 프로퍼티를 고려해 만들어진다. (주생성자 밖의 프로퍼티는 고려 대상이 아님을 유의하자.)
                    • 인스턴스 간 비교를 위한 equals
                    • HashMap 과 같은 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
                  • 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString
                  a. 데이터 클래스와 불변성: Copy() 메소드
                  • 데이터 클래스의 프로퍼티는 val을 권장한다.
                    • 데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들라고 권장한다.
                      • HashMap 등의 컨테이너에 데이터 클래스 객체를 담는 경우엔 불변성이 필수적이다.
                      • 데이터 클래스 객체를 키로 하는 값을 컨테이너에 담은 다음에, 키로 쓰인 데이터 객체의 프로퍼티를 변경하면 컨테이너 상태가 잘못될 수 있다.
                      • 불변 객체를 사용하면 프로그램이 훨씬 더 쉽게 추론한다.
                        • 특히 다중스레드 사용시 아주 중요한 부분이다. 스레드가 사용중인 데이터를 다른 스레드가 변경할 수 없으므로 스레드를 동기화해야할 필요가 줄어든다.
                  • 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용 할 수 있게 코틀린 컴파일러는 Copy()라는 메소드를 제공한다.
                    • Copy()
                      • 객체를 복사(copy)하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
                      • 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만든다.
                        • 복사본과 원본은 다른 생명주기를 가지고있다. 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 프로그램에서 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
                        • fun main() { val client1 = Client("고양이", 1111) val client2 = Client("고양이", 1111) println(client1 == client2) //true val processed = hashSetOf(Client("고양이", 1111)) println(processed.contains(Client("고양이", 1111))) //true val lee = Client("강아지", 2222) println(lee.copy(postalCode = 4000)) // Client(name=강아지, postalCode=4000) } class Client(val name: String, val postalCode: Int) { override fun hashCode(): Int = name.hashCode() * 31 + postalCode override fun equals(other: Any?): Boolean { if (other == null || other !is Client) //null이거나 Client 객체가 아니라면? return false return name == other.name && postalCode == other.postalCode } override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode) }
                  (3) 클래스 위임: by 키워드 사용
                  • 대규모 객체지향 시스템을 설계할 때 시스템을 취약하게 만드는 문제는 보통 구현 상속에서 일어난다.
                    • 하위클래스가 상위클래스의 메소드 중 일부를 오버라이드할 때, 하위 클래스는 상위 클래스의 세부 구현사항에 의존하게 된다.
                      • 상위 클래스의 구현이 바뀌거나 상위 클래스에 새로운 메소드가 추가된다면, 하위클래스가 상위 클래스에 대해 갖고있던 가정이 깨져서 코드가 정상적으로 작동하지 않을수도 있다.
                    • 코틀린은 이런 문제로 인해 클래스를 final 로 취급한다.
                      • open 변경자로 열어둔 클래스만이 상속 가능하다. open이 붙어 있으면 상속하리라 예상할 수 있으므로 변경 시 하위 클래스를 깨지않기 위해 좀 더 조심할 수 있다.
                  • 데코레이터 패턴 (Decorator)
                    • 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야할 경우도 생긴다. 이때 사용하는 것이 데코레이터 패턴이다.
                      • 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스는 데코레이터 내부의 필드로 유지하는 것이다.
                      • 이때 새로 정의해야 하는 기능은!
                        • 데코레이터의 메소드에 새로 정의하고, 기존 기능이 그대로 필요한 부분은 데코레이터의 메소드가 기존 클래스의 메소드에게 요청 전달(forwarding) 한다
                    • 단점: 준비 코드가 상당히 많이 필요하다.
                      • 일급 시민 기능을 지원하는 코틀린은 by 키워드로 간단하게 명시할 수 있다.
                        • by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다.
                        • fun main() { val cset = CountingSet<Int>() cset.addAll(listOf(1, 1, 2)) println("${cset.objectsAdded} objects were added, ${cset.size} remain") } class CountingSet<T>( val innerset: MutableCollection<T> = HashSet<T>() ) : MutableCollection<T> by innerset { var objectsAdded = 0 override fun add(element: T): Boolean { objectsAdded++ return innerset.add(element) } override fun addAll(c: Collection<T>): Boolean { objectsAdded += c.size return innerset.addAll(c) } }
                      • //위임 키워드 안 쓸 경우 class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.isEmpty() override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(element) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() } //위임 키워드 사용시 class DelegatingCollection<T>(innerList : Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}

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

                  • object 키워드를 사용하는 경우
                    • 객체 선언(object declaration)은 싱글턴을 정의하는 방법 중 하나이다.
                    • 동반 객체 (companion object)는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 때 쓰인다.
                      • 동반 객체 메소드에 접근 할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다.
                    • 객체 식은 자바의 무명 내부 클래스 (annonymous inner class) 대신 쓰인다.
                  (1) 객체 선언: 싱글턴을 쉽게 만들기
                  • 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
                    • 자바에서는 보통 클래스의 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
                    • 코틀린에서는 객체 선언 기능을 통해, 싱글턴을 언어에서 기본 지원한다.
                      • 객체 선언 : “클래스 선언” + “그 클래스에 속한 단일 인스턴스의 선언” 을 합친 선언이다.
                        • 객체선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한 문장으로 처리한다.
                        • 프로퍼티, 메소드, 초기화 블록 등 다 들어갈 수 있지만 생성자(주생성자, 부생성자)는 객체선언에 사용 할 수 없다.
                      • object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { // } }
                    • 객체 선언도 클래스나 인터페이스를 상속 할 수 있다.
                  a. 객체 선언을 사용해 Comparator 구현하기
                  • Comparator 구현은 두 객체를 인자로 받아 그중 어느 객체가 더 큰지 알려주는 정수를 반환한다.
                  • Comparator 안에는 데이터를 저장할 필요가 없다.
                    • 따라서 어떤 클래스에 속한 객체를 비교할 때 사용하는 Comparator는 보통 클래스마다 단 하나씩만 있으면 된다.
                  • 싱글톤과 의존관계 주입
                    • 싱글톤 패턴과 마찬가지로 코틀린의 객체 선언이 항상 적합한 것은 아니다.
                      • 의존관계가 별로 많지 않은 소규모 소프트웨어에서는 싱글톤이나 객체선언이 유용하다.
                      • 그러나, 시스템을 구현하는 다양한 구성요소와 상호작용하는 대규모 컴포넌트에는 싱글턴이 적합하지않다.
                        • 이유: 객체 생성을 제어할 방법이 없고, 생성자 파라미터를 지정할 수 없어서다.
                          • 생성을 제어할 수 없고, 생성자 파라미터를 지정할 수 없으므로 단위테스트를 하거나 소프트웨어 시스템의 설정이 달라질 때 객체를 대체하거나 객체의 의존관계를 바꿀 수 없다.
                  b. 중첩 객체를 사용해 Comparator 구현하기(2) 동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
                  • 동반객체도 클래스안에 중첩된 객체 중에 하나이다.
                  • 코틀린 클래스안에는 정적인 멤버가 없다. static 키워드를 지원하지 않는다.
                    • 자바에서 static 멤버는 클래스 선언 시, 클래스 뒤에 . 을 찍어서 바로 가져올수 있다.
                      • static이 없다면 객체로 만들어서 가져와야한다.
                  • 코틀린에서는 이런 기능들을 활용할 수 있다.
                    • 패키지 수준의 최상위 함수 (자바의 정적 메소드 역할을 거의 대신 할 수있다.)
                    • 객체 선언 (자바의 정적 메소드 역할 중 코틀린 최상위 함수가 대신할 수 없는 역할이나 정적 필드를 대신 할 수 있다.)
                  • 대부분의 경우 최상위 함수를 활용하는 편을 권장한다.
                    • 하지만, 최상위 함수는 그림처럼 private으로 표시된 클래스 비공개 멤버에 접근 할 수 없다.
                      • 그래서 클래스의 인스턴스와 관계없이 호출해야하지만! 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 정의해야한다. 대표적인 예로 팩토리 메소드를 들 수 있다.
                  • 클래스 안에 정의된 객체 중 하나에 companion이라는 특별한 표시를 붙이면 그 클래스의 동반 객체로 만들 수 있다.
                    • 클래스가 메모리에 올라갈 때, 동시에 companion object가 인스터스로서 힙에 올라간다 하여 '동반 객체'라고 한다.
                    • 동반객체의 프로퍼티나 메소드에 접근하려면 그 동반객체가 정의된 클래스 이름을 사용한다.
                  (3) 객체 식: 무명 내부 클래스를 다른 방식으로 작성
                  • 무명 객체는 자바의 무명 내부 클래스를 대신한다.
                    • 이벤트 리스너
                    • window.addMouseListener { object : MouseAdapter() { //무명 객체 선언 overried fun mouseClicked(e: MouseEvent) { //.... //MouseAdapter의 메소드를 오버라이드한다. } } }
                    • 무명 객체는 싱글톤이 아니다. 객체 식이 쓰일때마다 새로운 인스턴스가 생성된다.
                • fun main() { val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee")) println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator)) } data class Person(val name: String) { object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> { override fun compare( o1: com.anehta.athenakotlinlab.objecttest.Person, o2: com.anehta.athenakotlinlab.objecttest.Person ): Int = o1.name.compareTo(o2.name) }
                • fun main() { println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User"))) } object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File, o2: File): Int { return o1.path.compareTo(o2.path, ignoreCase = true) } }
                • //참조값의 주소를 비교한다. val client1 = Client("고양이",1111) val client2 = Client("고양이",1111) println(client1 == client2) //false
                • fun main() { println(Client("kim",1111).toString()) } class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
                • class LengthCounter { var counter: Int = 0 private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다. fun addword(word: String) { counter += word.length } } fun main(){ val lengthCounter = LengthCounter() lengthCounter.addword("hello world") println(lengthCounter.counter) }
                • a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때
                • interface User { val email: String //오버라이드해야함 val nickname: String //오버라이드하지않고 상속 할 수 있다. get() = email.substringBefore('@') //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. }
                • //주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다. //별명만 저장 class PrivateUser(override val nickname: String) : User //이메일도 함께 저장하기 class SubscribingUser(val email: String) : User { override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다. get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다. } class FacebookUser(val account: Int) : User { override val nickname: getFacebookName(account) } fun main() { println(PrivateUser("kim@naver.com").nickname) //kim@naver.com println(SubscribingUser("kim@naver.com").nickname) //kim }
                • a. 코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
                • class MyButton : View { constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
                • class MyButton : View { constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
                • class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
                • open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
                • open class ShoppingUser constructor(val nickname: String) class AmazonUser(nickname: String) : ShoppingUser(nickname)
                • class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true) //생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
                • class ShoppingUser (val nickname: String) //val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다.
                • class ShoppingUser (_nickname: String) { val nickname = _nickname }
                • a. 주생성자 선언하는 방법
                • a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.
                • class ButtonTest : ViewComponent { override fun getCurrentState(): State = ButtonState() override fun restoreState(state: State) { super.restoreState(state) } class ButtonState : State {} }
                • public class ButtonTest implements ViewComponent{ @NonNull @Override public State getCurrentState() { return new ButtonState(); } @Override public void restoreState(@NonNull State state) { ViewComponent.super.restoreState(state); } public class ButtonState implements State { /**/} }
                • internal open class TalkativeButton : Focusable { private fun yell() = println("Hey!") protected fun whisper() = println("Let's talk!") } fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함. yell() // 에러! "yell"은 private whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다. }
                • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
                • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
                • class RedButton : Clickable, Focusable { override fun click() = println("I was Clicked!") override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. } } fun main(args: Array<String>) { RedButton().showOff() RedButton().click() //출력값 I'm clickable! I'm focusable! I was Clicked! }
                • interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) { println("I ${if (b) "got" else "lost"} focus.") } fun showOff() = println("I'm focusable!") }
                • interface Clickable { fun click() fun showOff() = println("I'm clickable!") }
                • fun main(args: Array<String>) { Button().click() //출력 I was Clicked! } class RedButton : Clickable { override fun click() = println("I was Clicked!") }
                • //간단한 인터페이스 선언 interface Clickable { fun click() }
                • 1. 클래스 계층 정의
            • fun main() { val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee")) println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator)) } data class Person(val name: String) { object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> { override fun compare( o1: com.anehta.athenakotlinlab.objecttest.Person, o2: com.anehta.athenakotlinlab.objecttest.Person ): Int = o1.name.compareTo(o2.name) }
            • fun main() { println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User"))) } object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File, o2: File): Int { return o1.path.compareTo(o2.path, ignoreCase = true) } }
            • //참조값의 주소를 비교한다. val client1 = Client("고양이",1111) val client2 = Client("고양이",1111) println(client1 == client2) //false
            • fun main() { println(Client("kim",1111).toString()) } class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
            • class LengthCounter { var counter: Int = 0 private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다. fun addword(word: String) { counter += word.length } } fun main(){ val lengthCounter = LengthCounter() lengthCounter.addword("hello world") println(lengthCounter.counter) }
            • a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때
            • interface User { val email: String //오버라이드해야함 val nickname: String //오버라이드하지않고 상속 할 수 있다. get() = email.substringBefore('@') //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. }
            • //주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다. //별명만 저장 class PrivateUser(override val nickname: String) : User //이메일도 함께 저장하기 class SubscribingUser(val email: String) : User { override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다. get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다. } class FacebookUser(val account: Int) : User { override val nickname: getFacebookName(account) } fun main() { println(PrivateUser("kim@naver.com").nickname) //kim@naver.com println(SubscribingUser("kim@naver.com").nickname) //kim }
            • a. 코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
            • class MyButton : View { constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
            • class MyButton : View { constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
            • class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
            • open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
            • open class ShoppingUser constructor(val nickname: String) class AmazonUser(nickname: String) : ShoppingUser(nickname)
            • class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true) //생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
            • class ShoppingUser (val nickname: String) //val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다.
            • class ShoppingUser (_nickname: String) { val nickname = _nickname }
            • a. 주생성자 선언하는 방법
            • a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.
            • class ButtonTest : ViewComponent { override fun getCurrentState(): State = ButtonState() override fun restoreState(state: State) { super.restoreState(state) } class ButtonState : State {} }
            • public class ButtonTest implements ViewComponent{ @NonNull @Override public State getCurrentState() { return new ButtonState(); } @Override public void restoreState(@NonNull State state) { ViewComponent.super.restoreState(state); } public class ButtonState implements State { /**/} }
            • internal open class TalkativeButton : Focusable { private fun yell() = println("Hey!") protected fun whisper() = println("Let's talk!") } fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함. yell() // 에러! "yell"은 private whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다. }
            • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
            • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
            • class RedButton : Clickable, Focusable { override fun click() = println("I was Clicked!") override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. } } fun main(args: Array<String>) { RedButton().showOff() RedButton().click() //출력값 I'm clickable! I'm focusable! I was Clicked! }
            • interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) { println("I ${if (b) "got" else "lost"} focus.") } fun showOff() = println("I'm focusable!") }
            • interface Clickable { fun click() fun showOff() = println("I'm clickable!") }
            • fun main(args: Array<String>) { Button().click() //출력 I was Clicked! } class RedButton : Clickable { override fun click() = println("I was Clicked!") }
            • //간단한 인터페이스 선언 interface Clickable { fun click() }
            • 1. 클래스 계층 정의
        • fun main() { val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee")) println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator)) } data class Person(val name: String) { object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> { override fun compare( o1: com.anehta.athenakotlinlab.objecttest.Person, o2: com.anehta.athenakotlinlab.objecttest.Person ): Int = o1.name.compareTo(o2.name) }
        • fun main() { println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User"))) } object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File, o2: File): Int { return o1.path.compareTo(o2.path, ignoreCase = true) } }
        • //참조값의 주소를 비교한다. val client1 = Client("고양이",1111) val client2 = Client("고양이",1111) println(client1 == client2) //false
        • fun main() { println(Client("kim",1111).toString()) } class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
        • class LengthCounter { var counter: Int = 0 private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다. fun addword(word: String) { counter += word.length } } fun main(){ val lengthCounter = LengthCounter() lengthCounter.addword("hello world") println(lengthCounter.counter) }
        • a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때
        • interface User { val email: String //오버라이드해야함 val nickname: String //오버라이드하지않고 상속 할 수 있다. get() = email.substringBefore('@') //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. }
        • //주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다. //별명만 저장 class PrivateUser(override val nickname: String) : User //이메일도 함께 저장하기 class SubscribingUser(val email: String) : User { override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다. get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다. } class FacebookUser(val account: Int) : User { override val nickname: getFacebookName(account) } fun main() { println(PrivateUser("kim@naver.com").nickname) //kim@naver.com println(SubscribingUser("kim@naver.com").nickname) //kim }
        • a. 코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
        • class MyButton : View { constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
        • class MyButton : View { constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
        • class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
        • open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
        • open class ShoppingUser constructor(val nickname: String) class AmazonUser(nickname: String) : ShoppingUser(nickname)
        • class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true) //생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
        • class ShoppingUser (val nickname: String) //val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다.
        • class ShoppingUser (_nickname: String) { val nickname = _nickname }
        • a. 주생성자 선언하는 방법
        • a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.
        • class ButtonTest : ViewComponent { override fun getCurrentState(): State = ButtonState() override fun restoreState(state: State) { super.restoreState(state) } class ButtonState : State {} }
        • public class ButtonTest implements ViewComponent{ @NonNull @Override public State getCurrentState() { return new ButtonState(); } @Override public void restoreState(@NonNull State state) { ViewComponent.super.restoreState(state); } public class ButtonState implements State { /**/} }
        • internal open class TalkativeButton : Focusable { private fun yell() = println("Hey!") protected fun whisper() = println("Let's talk!") } fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함. yell() // 에러! "yell"은 private whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다. }
        • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
        • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
        • class RedButton : Clickable, Focusable { override fun click() = println("I was Clicked!") override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. } } fun main(args: Array<String>) { RedButton().showOff() RedButton().click() //출력값 I'm clickable! I'm focusable! I was Clicked! }
        • interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) { println("I ${if (b) "got" else "lost"} focus.") } fun showOff() = println("I'm focusable!") }
        • interface Clickable { fun click() fun showOff() = println("I'm clickable!") }
        • fun main(args: Array<String>) { Button().click() //출력 I was Clicked! } class RedButton : Clickable { override fun click() = println("I was Clicked!") }
        • //간단한 인터페이스 선언 interface Clickable { fun click() }
        • 1. 클래스 계층 정의
    • fun main() { val person = listOf(com.anehta.athenakotlinlab.objecttest.Person("kim"),com.anehta.athenakotlinlab.objecttest.Person("lee")) println(person.sortedWith(com.anehta.athenakotlinlab.objecttest.Person.NameComparator)) } data class Person(val name: String) { object NameComparator : Comparator<com.anehta.athenakotlinlab.objecttest.Person> { override fun compare( o1: com.anehta.athenakotlinlab.objecttest.Person, o2: com.anehta.athenakotlinlab.objecttest.Person ): Int = o1.name.compareTo(o2.name) }
    • fun main() { println(CaseInsensitiveFileComparator.compare(File("/User"),File("/User"))) } object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File, o2: File): Int { return o1.path.compareTo(o2.path, ignoreCase = true) } }
    • //참조값의 주소를 비교한다. val client1 = Client("고양이",1111) val client2 = Client("고양이",1111) println(client1 == client2) //false
    • fun main() { println(Client("kim",1111).toString()) } class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name=$name, postalCode=$postalCode)" //결과값 //override 시: Client(name=kim, postalCode=1111) //override 안하면: com.anehta.athenakotlinlab.dataclasstest.Client@7c30a502 }
    • class LengthCounter { var counter: Int = 0 private set //클래스 밖에서 이 프로퍼티의 값을 바꿀수없다. fun addword(word: String) { counter += word.length } } fun main(){ val lengthCounter = LengthCounter() lengthCounter.addword("hello world") println(lengthCounter.counter) }
    • a. 프로퍼티에 저장된 값의 변경 이력을 로그에 남기고 싶을 때
    • interface User { val email: String //오버라이드해야함 val nickname: String //오버라이드하지않고 상속 할 수 있다. get() = email.substringBefore('@') //상태를 저장하는게 아니다. 매번 결과값을 계산해 돌려준다. }
    • //주 생성자안에 프로퍼티를 직접 선언한 간결한 구문을 사용한다. //별명만 저장 class PrivateUser(override val nickname: String) : User //이메일도 함께 저장하기 class SubscribingUser(val email: String) : User { override val nickname: String //추상 프로퍼티 구현이므로 override가 필요하다. get() = email.substringBefore('@') //커스텀 게터. 이 프로퍼티는 뒷받침하는 필드를 저장하는게 아니라 그때마다 별명을 계산해서 토해낸다. } class FacebookUser(val account: Int) : User { override val nickname: getFacebookName(account) } fun main() { println(PrivateUser("kim@naver.com").nickname) //kim@naver.com println(SubscribingUser("kim@naver.com").nickname) //kim }
    • a. 코틀린에서는 인터페이스에 추상 프로퍼티 선언을 넣을 수 있다.
    • class MyButton : View { constructor(ctx: Context) : this(ctx, MY_STYLE) // 클래스 자신의 다른 생성자를 호출함 -> 객체 생성을 위임한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
    • class MyButton : View { constructor(ctx: Context) : super(ctx) //상위 클래스의 생성자를 호출한다. constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) //상위 클래스의 생성자를 호출한다. }
    • class Secretive private constructor() { } //이 클래스의 주생성자는 비공개다.
    • open class Button // 인자가 없는 디폴트 생성자가 만들어진다.
    • open class ShoppingUser constructor(val nickname: String) class AmazonUser(nickname: String) : ShoppingUser(nickname)
    • class ShoppingUser constructor(val nickname: String, val isSubScribed: Boolean = true) //생성자 프로퍼티 이름 앞에 val을 추가하면 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
    • class ShoppingUser (val nickname: String) //val는 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다.
    • class ShoppingUser (_nickname: String) { val nickname = _nickname }
    • a. 주생성자 선언하는 방법
    • a. when식에서 하위 클래스 처리할 때, else 분기를 반드시 넣어줘야한다.
    • class ButtonTest : ViewComponent { override fun getCurrentState(): State = ButtonState() override fun restoreState(state: State) { super.restoreState(state) } class ButtonState : State {} }
    • public class ButtonTest implements ViewComponent{ @NonNull @Override public State getCurrentState() { return new ButtonState(); } @Override public void restoreState(@NonNull State state) { ViewComponent.super.restoreState(state); } public class ButtonState implements State { /**/} }
    • internal open class TalkativeButton : Focusable { private fun yell() = println("Hey!") protected fun whisper() = println("Let's talk!") } fun TalkativeButton.giveSpeach(){ //확장함수 에러! "public" 멤버가 자신의 "internal" 수신 타입인 "TalkativeButton"을 노출함. yell() // 에러! "yell"은 private whisper() //에러! "whisper"은 protected임 TalkativeButton을 상속받은 하위 클래스에서만 사용 가능하다. }
    • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. final override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
    • open class RichButton : Clickable { fun disable() {} //이 함수는 파이널이다. 하위클래스가 이 메소드를 오버라이드 할 수 없다. open fun animate() {} //이 함수는 열려있다. 하위클래스에서 이 메소드를 오버라이드 할 수 있다. override fun click(){} // 이 함수는 (상위클래스에서 선언된) 열려있는 메소드를 오버라이드한다. 오버라이드 한 함수는 기본적으로 열려있다. }
    • class RedButton : Clickable, Focusable { override fun click() = println("I was Clicked!") override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() //둘 다 구현해도 되고 둘 중 하나만 구현해도 된다. } } fun main(args: Array<String>) { RedButton().showOff() RedButton().click() //출력값 I'm clickable! I'm focusable! I was Clicked! }
    • interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) { println("I ${if (b) "got" else "lost"} focus.") } fun showOff() = println("I'm focusable!") }
    • interface Clickable { fun click() fun showOff() = println("I'm clickable!") }
    • fun main(args: Array<String>) { Button().click() //출력 I was Clicked! } class RedButton : Clickable { override fun click() = println("I was Clicked!") }
    • //간단한 인터페이스 선언 interface Clickable { fun click() }
    • 1. 클래스 계층 정의
728x90
반응형