기본적인 내용은 생략한다.
키워드 #
- 인터페이스
- 프로퍼티 선언 가능
- 클래스
- 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 된 멤버를 문제없이 사용할 수 있지만, 사용하기 불편하고 코드가 지저분해질 것이다.또, 이렇게 하는 두 가지 이유가 있다고 한다.
- 하위 클래스에서 우연히 이름이 같아 오버라이딩할 수 있는 경우를 방지한다.
- 실수로 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 A | class A |
내부 클래스(바깥쪽 클래스에 대한 참조를 저장) | class A | inner 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) {
...
}
이 규칙으로 인해 상위 클래스쪽에는 항상 ()
괄호를 쓴다.
인터페이스쪽에는 괄호 안쓴다. (인터페이스는 생성자 없으니까)