Binary World

클래스와 상속(Classes and Inheritance) 본문

개발자의 길/Kotlin

클래스와 상속(Classes and Inheritance)

모쿠 2019. 5. 31. 16:33

<클래스(Classes)>

코틀린에서 클래스는 class 키워드를 사용한다. 

class Invoice {...}

클래스 선언은 클래스 이름, 클래스 헤더(타입 매개 변수, 기본 생성자 등), 클래스 본문, 괄호로 구성된다. 헤더와 본문은 선택적이다. 클래스가 본문이 없다면 괄호는 제거된다.

class Empty

 

생성자(Constructors)

코틀린에서 클래스는 기본 생성자하나 이상의 보조 생성자를 가질 수 있다. 기본 생성자는 클래스 헤더의 일부다 : 클래스 이름 뒤에 위치.

class Person constructor(firstName: String) { ... }

만약 기본 생성자에 특정 어노테이션 또는 접근 제한자가 없으면, constructor 키워드는 제거된다. 

class Person(firstName: String) { ... }

기본 생성자는 어떤 코드도 포함하지 않는다. 초기화 코드는 초기화 블록에 놓을 수 있고, 이 접두사에는 init 키워드가 붙는다.

인스턴스 초기화 중에, 초기화 블록이 실행된다. 초기화 블록은 클래스 본문에 나타내는 순서와 동일하게 실행되고, 속성 초기화 값을 인터리브한다.

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

fun main() {
    InitOrderDemo("hello")
}

/* 출력 결과 */
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

기본 생성자의 매개 변수는 초기화 블록에서 사용될 수 있다. 또한, 클래스 본문에 선언된 속성 초기화 변수에 사용될 수 있다.

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

사실, 코틀린에서는, 기본 생성자로부터 속성 선언과 초기화를 위해 간결한 문구를 사용할 수 있다.

class Person(val firstName: String, val lastName: String, var age: Int) { ... }

정규 속성들과 거의 같은 방식으로, 기본 생성자에서 속성 선언은 var(변수) 또는 val(읽기 전용)으로 한다.

생성자가 어노테이션 또는 접근 제한자를 갖는다면, constructor 키워드가 필요하다.

class Customer public @Inject constructor(name: String) { ... }

보조 생성자(Secondary Constructors)

contstructor 접두사를 사용하여, 보조 생성자를 선언할 수 있다.

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

클래스가 기본 생성자를 갖는다면, 다른 보조 생성자(들)을 통해 직접 또는 간접적으로, 각 보조 생성자가 기본 생성자에 위임을 해야 한다. 같은 클래스의 다른 생성자로 위임은 this 키워드를 사용한다.

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

초기화 블록의 코드는 효과적으로 기본 생성자의 일부가 된다. 기본 생성자에 대한 위임은 보조 생성자의 첫 번째 상태로 발생하므로, 초기화 블록의 코드는 보조 생성자의 본문보다 먼저 실행된다. 기본 생성자가 없더라도, 위임은 여전히 발생되며, 초기화 블록 역시 실행된다.

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

fun main() {
    Constructors(1)
}

/* 출력 결과 */
Init block
Constructor

추상화 클래스가 아닌 클래스가 어떤 생성자도 생성하지 않으면, 매개 변수가 없는 기본 생성자를 생성한다. 생성자의 접근 상태는 public으로 된다. 클래스가 public 생성자를 갖는 것을 원치 않는다면, 기본 접근 제한자 외에 제한자를 갖는 빈 기본 생성자를 선언해야 한다.

class DontCreateMe private constructor () { ... }

 

클래스의 인스턴스 생성(Creating instance of classes)

클래스의 인스턴스를 생성하기 위해, 정규 함수의 생성자를 호출해 보자.

val invoice = Invoice()

val customer = Customer("Joe Smith")

참고 : new 키워드를 사용하지 않는다.

 

클래스 멤버(Class Members)

클래스는 아래 항목들을 포함한다.

  • 생성자와 초기화 블록
  • 함수
  • 속성들
  • 중복 클래스와 내부 클래스
  • 객체 선언

<상속(Inheritance)>

코틀린에서 모든 클래스는 공통의 상위클래스 any를 갖는다. 이것은 상위타입 선언이 되지 않은 기본 슈퍼클래스다.

class Example // Any로 부터 암묵적인 상속

명시적으로 상위 타입을 선언하려면, 클래스 헤더에 콜론을 사용한 후 타입을 명시한다.

open class Base(p: Int)

class Derived(p: Int) : Base(p)

클래스가 기본 생성자를 갖지 않는다면, 각 보조 생성자들은 super 키워드를 사용하여 초기화되거나, 다른 생성자로 위임된다. 서로 다른 보조 생성자들은 기본 타입의 다른 생성자들을 호출할 수 있다.

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

 

오버라이딩 메소드(Overriding Methods)

자바와 다르게, 코틀린은 오버라이드될 멤버와 오버라이드에 명시적 수식어를 요구한다.

open class Base {
    open fun v() { ... }
    fun nv() { ... }
}
class Derived() : Base() {
    override fun v() { ... }
}

override로 표시된 멤버는 열린 형태가 된다. 이 멤버는 하위클래스에서도 오버라이드 가능하다. 만약 오버라이딩을 금지하려면 final을 사용한다.

open class AnotherDerived() : Base() {
    final override fun v() { ... }
}

 

속성 오버라이딩(Overriding Properties)

속성 오버라이딩은 메소드 오버라이딩과 비슷하게 작동된다. 파생된 클래스에서 재정의된 상위클래스에서 선언된 속성은 overrride로 우선 적용되어야 하며, 호환 가능한 타입이어야 한다.

open class Foo {
    open val x: Int get() { ... }
}

class Bar1 : Foo() {
    override val x: Int = ...
}

var 속성으로 val 속성을 오버라이드 할 수 있다. val 속성이 기본적으로 getter 메소드를 선언하고, 이를 var로 오버라이드하면 setter 메소드를 추가로 선언하기 때문이다.

 

참고 : override 키워드는 기본 생성자에서도 사용이 가능하다.

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

 

파생 클래스 초기화 순서(Derived class initialization order)

파생된 클래스의 새로운 인스턴스의 구성 중에, 기본 클래스 초기화는 첫 단계로 수행된다. 따라서, 파생된 클래스의 초기화 논리가 실행되기 전에 수행된다.

open class Base(val name: String) {

    init { println("Initializing Base") }

    open val size: Int = 
        name.length.also { println("Initializing size in Base: $it") }
}

class Derived(
    name: String,
    val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {

    init { println("Initializing Derived") }

    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}

fun main() {
    println("Constructing Derived(\"hello\", \"world\")")
    val d = Derived("hello", "world")
}

/* 출력 결과 */
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

 

상위 클래스 구현 호출(Calling the superclass implementation)

파생 클래스의 코드는 super 키워드를 사용하여 상위 클래스 함수 및 속성 접근자를 구현할 수 있다. 

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }
    
    override val x: Int get() = super.x + 1
}

내부 클래스 안에서는, 외부 클래스의 상위 클래스 접근은 외부 클래스 이름으로 지정된 super 키워드를 사용한다: super@Outer

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            super@Bar.f() // Foo의 f() 실행 호출
            println(super@Bar.x) // Foo의 x getter 실행 호출
        }
    }
}

 

오버라이딩 규칙(Overriding Rules)

다양한 클래스에서 상속 할 때, 멤버 이름이 동일한 경우에는 이 멤버를 재정의하는 방법을 제공한다. 상속된 구현으로부터 상위타입을 나타내려면, 다음과 같은 표현을 사용한다 : super<Base>

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // 인터페이스 멤버는 open이 기본 상태이다.
    fun b() { print("b") }
}

class C() : A(), B {
    // f() 함수 오버라이드가 필요하다.
    override fun f() {
        super<A>.f() // call to A.f()
        super<B>.f() // call to B.f()
    }
}

 

추상 클래스(Abstract Classes)

일부 클래스와 멤버들은 abstract로 선언될 수 있다. 추상 멤버는 해당 클래스에 구현되지 않는다. 

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

 

'개발자의 길 > Kotlin' 카테고리의 다른 글

함수(Functions)  (0) 2019.05.30
리턴과 점프(Returns and Jumps)  (0) 2019.05.29
제어 흐름(Control Flow)  (0) 2019.05.28
패키지와 임포트(Packages and Imports)  (0) 2019.05.28
기본 타입(Basic Type)  (0) 2019.05.28
Comments