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

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

기본적인 내용은 생략한다.


키워드 #

  • 인터페이스
    • 프로퍼티 선언 가능
  • 클래스
    • final
    • final public
    • sealed
  • 중첩 클래스
    • 중첩 클래스 vs 내부 클래스
  • 초기화 블록
  • data 클래스
  • 위임(delegation)
  • object
    • 싱글턴 객체
    • 동반 객체
    • 객체 식

+ 참고 : 스마트 캐스트는 val(불변)일 때에만 사용 가능하다.



4.1 클래스 계층 정의 #

기본 가시성 다르다.

  • java : default
  • kotlin : public

sealed 클래스는 상속을 제한한다. (중첩(?), 내부(?) 클래스만 허용한다.)


4.1.1 코틀린 인터페이스 #

  • 추상 메서드
  • 구현이 있는 메서드 (default 메서드)
  • 상태(필드)
    • 자바와 다른 점이다.

상속, 구현에서 오버라이딩 할 때 override 키워드 무조건 사용해야 한다.


default 메서드의 경우, default 키워드 사용하지 않는다.

그냥 구현체 작성하면 된다.

자바 8 이전, 6, 7과 대응할 때는 정적 메서드가 들어있는 클래스를 함께 제공한다고 한다.


super() 작성법은 다음과 같다.

class Button: Clickable, Focusable {
    override fun click() = println("I was clicked")
    override fun showOff() {
        super<Clickable>.showOff()  // 자바 : Clickable.super.showOff()
        super<Focusable>.showOff()  // 자바 : Focusable.super.showOff()
    }
}

접근 제어자 (가시성 변경자)

default : public

변경자클래스 멤버최상위 선언
public (기본 가시성)모든 곳에서 볼 수 있다.모든 곳에서 볼 수 있다.
internal같은 모듈 안에서만 볼 수 있다.같은 모듈 안에서만 볼 수 있다.
protected같은 클래스 + 하위 클래스에서만 볼 수 있다.* 최상위 선언에 적용 불가

* (자바 처럼)같은 패키지에서 볼 수 없음에 유의
private같은 클래스 안에서만 볼 수 있다.같은 파일 안에서만 볼 수 있다.

" 코틀린의 public, protected, private 변경자는 컴파일된 자바 바이트코드 안에서도 그대로 유지된다.
유일한 예외는 private 클래스다. 자바에서는 클래스를 private 으로 만들 수 없으므로, 내부적으로 코틀린은 private 을 default 클래스로 컴파일한다.

internal 변경자는 public 으로 컴파일된다.
즉, 코틀린에서는 접근할 수 없는 대상을 자바에서 접근할 수 있게 되는 경우가 생긴다.
하지만 코틀린 컴파일러가 internal 멤버의 이름을 나쁘게 바꾼다(mangle)는 사실을 기억해야 한다. 정리하면 기술적으로는 mangle 된 멤버를 문제없이 사용할 수 있지만, 사용하기 불편하고 코드가 지저분해질 것이다.

또, 이렇게 하는 두 가지 이유가 있다고 한다.

  1. 하위 클래스에서 우연히 이름이 같아 오버라이딩할 수 있는 경우를 방지한다.
  2. 실수로 internal 클래스를 모듈 외부에서 사용하는 일을 방지한다.
    "



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

자바

  • 중첩 클래스 작성 시, 기본적으로 inner class(내부 클래스) 이다.

코틀린

  • 중첩 클래스 작성 시, 기본적으로 nested class(중첩 클래스) 이다.

(자바와의 차이) 코틀린의 중첩 클래스(nested class)는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다는 점이다.

public class Button implements View {

    @Override
    public State getCurrentState() {
        return new ButtonState();
    }

    @Override
    public void restoreState(State state) { ... }

    public class ButtonState implements State { ... }
}

위 코드는 문제가 있다.

ButtonState 직렬화 시 java.io.NotSeriallizableException: Button 오류가 발생한다.

Java에서 다른 클래스 안에 정의한 클래스는 자동으로 내부 클래스(inner class)가 된다는 것을 기억하면 명확히 알 수 있다.

ButtonState 클래스는 바깥쪽 Button 클래스에 대한 참조를 묵시적으로 포함한다. 그 참조로 인해 ButtonState를 직렬화할 수 없다.

이 문제를 해결하려면 ButtonState 를 static 으로 선언해야 한다.

자바에서 중첩 클래스를 static 으로 선언하면 그 클래스를 둘러싼 바깥쪽 클래스에 대한 묵시적인 참조가 사라진다.


코틀린에서 중첩된 클래스가 동작하는 방식은 정반대이다.

클래스 B 안에 A자바코틀린
중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음)static class Aclass A
내부 클래스(바깥쪽 클래스에 대한 참조를 저장)class Ainner class A

바깥쪽 클래스의 인스턴스를 가리키는 참조 표기 방법도 다르다.

this@Outer



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

sealed

하위 클래스 정의를 제한한다. 하위 클래스 정의 시 반드시 상위 클래스 안에 중첩시켜야 한다.

자동으로 open 클래스가 된다.

클래스 계층을 만들되 그 계층에 속한 클래스 수를 제한하고 싶은 경우, 중첩 클래스를 쓰면 편하다.

특히? 예를들어 책에서 소개한 when 식과 함께 쓸 때 궁합이 좋다.

when 에서 else 구문 안써도 되고, 하위 클래스 추가되었을 경우 (when 식에 생기는)컴파일 오류 통해 바로 알 수 있다. (확인해보자.)



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

코틀린에서는 아래 기능이 있다.

  • 주 생성자
  • 부 생성자
  • 초기화 블록

4.2.1 클래스 초기화 : 주 생성자, 초기화 블록 #

// 위 코드, 아래 코드가 동일하다. //

class User(val nickname: String)

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

초기화 블록 안에서만 주 생성자의 파라미터에 참조할 수 있다.


모든 생성자 파라미터에 기본 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 기본 생성자를 만들어준다.

기본 생성자 사용 시 파라미터 기본 값으로 생성 가능하다.

DI 프레임워크와 쉽게 통합되게 한다.


상위 클래스 초기화는 다음과 같이 한다.

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

이 규칙으로 인해 상위 클래스쪽에는 항상 () 괄호를 쓴다.

인터페이스쪽에는 괄호 안쓴다. (인터페이스는 생성자 없으니까)



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