코틀린 인액션 4장 - 2
내부 클래스와 중첩된 클래스: 기본적으로 중첩 클래스
봉인된 클래스: 클래스 계층 정의 시 계층 확장 제한
뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
- 자바와 마찬가지로 코틀린도 생성자를 하나 이상 선언할 수 있다.
- 코틀린은 주생성자(클래스 본문 밖에서 정의)와 부생성자(클래스 본문 안에서 정의)를 구분한다.
- 코틀린에서는 초기화 블록을 통해 초기화 로직을 추가할 수 있다.
클래스 초기화: 주 생성자와 초기화 블록
class User(val nickName: String)
- 위의 코드처럼 클래스 이름 뒤에 오는 괄호로 둘러싸인 코드를 주 생성자라고 부른다.
- 주 생성자는 생성자 파라미터를 지정하고 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적에 쓰인다.
위의 코드를 풀어서 작성해보자.
class User(nickName: String) { // 주 생성자
val nickName: String
init { // 초기화 블록
this.nickName = nickName
}
}
- constructor 키워드는 주 생성자나 부 생성자 정의를 시작할 때 사용한다.
- 주 생성자 앞에 별다른 애노테이션이나 가시성 변경자가 없다면 constructor를 생략해도 된다.
- init 키워드는 초기화 블록을 시작한다.
- 초기화 블록에는 클래스의 객체가 만들어질 때 실행될 초기화 코드가 들어간다.
- 초기화 블록은 주 생성자와 함께 사용된다.
- 필요하다면 클래스 안에 여러 초기화 블록을 선언할 수 있다.
이 예제에서는 nickname 프로퍼티를 초기화하는 코드를 nickname 프로퍼티 선언에 포하시킬 수 있어서 초기화 코드를 초기화 블록에 넣을 필요가 없다.
따라서 아래와 같이 코드를 변경할 수 있다.
class User(nickName: String) {
val nickName: String = nickName
}
- 프로퍼티를 초기화하는 식이나 초기화 블록 안에서만 주 생성자의 파라미터(nickName:String)를 참조할 수 있다.
주 생성자의 파라미터로 프로퍼티를 초기화한다면 그 주 생성자 파라미터 이름 앞에 val을 추가하는 방식으로 프로퍼티 정의와 초기화를 간략히 쓸 수 있다.
따라서 다시 아래와 같이 코드를 변경할 수 있다.
class User(val nickName: String)
- val은 이 파라미터에 상응하는 프로퍼티가 생성된다는 뜻이다.
- 지금까지 살펴본 세 가지 User 선언은 모두 동일하지만, 위의 방식이 가장 간결하다.
- 생성자 파라미터에도 디폴트 값을 지정할 수 있다.
class User(val nickName: String, val isMarried: Boolean = false)
fun main(args: Array<String>) {
val user = User("홍제")
println("Is ${user.nickName} married?: ${user.isMarried}")
val user2 = User("홍제2", isMarried = true)
println("Is ${user2.nickName} married?: ${user2.isMarried}")
}
모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어준다.
그렇게 자동으로 만들어진 파라미터 없는 생성자는 디폴트 값을 사용해 클래스를 초기화한다.
클래스에 부모 클래스가 있다면 주 생성자에서 부모 클래스의 생성자를 호출해야 하는데, 부모 클래스를 초기화하려면 부모 클래스 이름 뒤에 괄호를 치고 생성자 인자를 넘기면 된다.
open class Parent(val name:String)
class Child(val name: String, parentName: String) : Parent(parentName)
클래스를 정의할 때 별도로 생성자를 정의하지 않으면 컴파일러가 자동으로 디폴트 생성자를 만들어준다.
open class Child
- 만약 위의 Child 클래스를 상속받는 클래스는 Child 클래스의 파라미터 없는 디폴트 생성자를 호출해야 한다.
open class Child
class Grandson(val name: String) : Child()
만약 어떤 클래스를 외부에서 인스턴스화하지 못하게 막고 싶다면 모든 생성자를 private으로 만들면 된다.
class Grandson private constructor()
부 생성자: 상위 클래스를 다른 방식으로 초기화
- 자바에서는 생성자 오버로딩으로 많은 생성자를 만들어낼 수 있다. 하지만 코틀린에서는 디폴트 파라미터로 대부분의 그러한 상황을 해결할 수 있다.
- 그럼에도 불구하고 생성자가 여럿 필요한 경우가 있는데 아래의 경우를 보자.
open class View {
constructor(ctx: Context) {...} // 부 생성자
constructor(ctx: Context, attr: AttributeSet) {...} // 부 생성자
}
- 이 클래스는 주 생성자를 선언하지 않고(클래스 이름 뒤에 괄호가 없다.) 부 생성자만 2가지 생성했다.
- 부 생성자는 constructor 키워드로 시작하고 얼마든지 많이 선언해도 된다.
- 이 클래스를 확장하면서 똑같이 부 생성자를 정의할 수 있다. 아래의 예시를 보자.
open class View {
constructor(ctx: Context) {...} // 부 생성자
constructor(ctx: Context, attr: AttributeSet) {...} // 부 생성자
}
class MyButton : View {
constructor(ctx: Context) : this(ctx, MY_STYLE)
constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr)
}
- 위의 부 생성자는 this() 키워드를 통해 자신의 다른 생성자를 호출한다.
- 아래의 부 생성자는 super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자를 호출한다.
인터페이스에 선언된 프로퍼티 구현
게터와 세터에서 뒷받침하는 필드에 접근
접근자의 가시성 변경
- 접근자의 가시성은 get이나 set앞에 가시성 변경자를 추가해서 접근자의 가시성을 변경할 수 있다.
class LengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
- 외부 코드에서 단어 길이의 합을 마음대로 바꾸지 못하게 이 클래스 내부에서만 길이를 변경하도록 세터의 가시성을 private으로 지정했다.
Comments