티스토리 뷰

반응형
SMALL

2019-07-28 Kotlin 상속 (kotlin Override) -1

 

이전 글에 이어서 진행합니다. 아직 학습을 못하신 분들은 위 링크를 눌러 학습을 하고 오시기 바랍니다.

상위 클래스의 멤버 접근

오버라이드를 통해 재정의한 멤버가 있어도 때로는 상위 클래스에 정의한 멤버도 함께 이용해야 하는 경우가 있습니다.

이럴 때 super라는 Keyword(예약어)를 사용합니다.

open class Super {
    open var x: Int = 0
    open fun someFun() {
        println("Super... someFun()")
    }
}

class Sub : Super() {
    override var x: Int = 20
    override fun someFun() {
        super.someFun()
        println("Sub... ${super.x} .... $x")
    }
}

fun main(args: Array<String>) {
    var sub = Sub()
    sub.someFun()
}

main 메소드에서 호출한 sub.someFun()은 하위 클래스에 있는 someFun을 실행한 것입니다. 하지만 메소드 안에 super.someFun()이 있습니다. 이는 상위 클래스의 someFun()을 실행하라는 의미입니다.

출력 결과를 한 번 보도록 하겠습니다.

Super 예약어를 활용한 하위클래스에서의 멤버 메소드 호출 출력 결과

super.someFun()으로 인해 상위클래스의 someFun()이 실행되었습니다.

그리고 멤버 변수 역시 super.x와 같이 호출하였기 때문에 상위 클래스의 멤버 변수의 값이 출력되는 것을 알 수 있습니다.

 

여기서 질문이 하나 있을 수도 있습니다.

상위클래스 멤버를 하위에서 재정의해서 사용하면 되는 것이지 다시 상위 멤버를 super로 이용해야할까?

일반적으로 상속의 관계 속에서 상위 클래스에서는 여러 클래스에서 공통으로 처리할 로직을 작성하고, 하위 클래스의 오버라이드 함수에서는 해당 클래스만의 특징적인 로직을 작성합니다.

재정의한다는 뜻은 하위클래스만의 값을 가져야 한다는 것을 의미합니다. 예를 들어, PI는 3.14라고 가정합니다.

Circle이라는 클래스가 있고 넓이를 구하는 메소드가 있다고 가정할 때 PI를 이용한다고 칩시다. 그러면 PI를 재정의해야할까요? 

상위 멤버로부터 가져오면 됩니다. 이럴 때 재정의를 할 필요가 없다는 것이죠.

 

상속과 생성자

상위 클래스 생성자 호출

Kotlin에서는 생성자를 주 생성자와 보조 생성자로 구분합니다. 상속관계에서 상위 클래스와 하위 클래스 간의 생성자 관계에 대해 알아볼 필요가 있습니다.

상속 관계에서 생성자의 핵심 개념은 하위 클래스의 객체를 생성할 때 어떤 식으로든 상위클래스의 생성자는 무조건 실행되야 합니다.

open class Super {
    ...
}

class Sub : Super() {
    ...
}

fun main(args: Array<String>) {
    var sub = Sub()
    ...
}

Super를 상속받았기 때문에 Sub 생성자를 호출하면 Super생성자까지 함께 호출됩니다.

class Sub : Super() { }

Super()를 보셨나요? 괄호를 제거하면 컴파일 에러가 발생하게 됩니다.

클래스를 선언할 때 개발자가 생성자를 추가하지 않으면 컴파일러가 매개변수가 없는 주 생성자를 자동으로 생성합니다.

open class Super constructor() {
    
}

class Sub constructor(): Super() {
    
}

위와 같이 주 생성자가 호출됩니다. (생략 가능합니다.) 따라서 Sub는 주 생성자와 상위 생성자가 모두 실행됩니다.

상위 클래스에 명시적으로 생성자가 선언된 경우

이런 경우는 어떻게 될까요? 다음 코드를 봅시다.

open class Super(name: String) {

}

class Sub: Super() {    // Compile Error

}
open class Super(name: String) {

}

class Sub: Super("Terry_Kim") {

}

명시적으로 선언된 경우에는 하위 클래스에서 상속받을 때에도 명시적으로 값을 넣어주어야 에러가 발생하지 않습니다.

아니면, 이렇게 작성할 수도 있습니다.

open class Super(name: String) {

}

class Sub(name: String): Super(name) {

}

하위 생성자에서 받은 값을 상위 생성자의 매개변수로 전달할 수 있습니다. 이를 자바로 표현하면 어떻게 될까요?

class Super {
    Super(String name) { }
}

class Sub extends Super {
    Sub(String name) {
        super(name);
    }
}

이렇게 쓸 수 있겠죠? 코틀린이 왜 좋은지 한 번 더 느끼게 해주네요. :D

 

상하위 생성자의 수행 흐름

상속 관계에 있는 클래스들의 생성자들이 어떤 순서로 실행되는지 한번 코드를 통해 알아보도록 합시다.

open class Super {
    constructor(name: String, no: Int) {
        println("Super ... constructor(name, no)")
    }

    init {
        println("Super ... init call ...")
    }
}

class Sub(name: String) : Super(name, 10) {
    constructor(name: String, no: Int): this(name) {
        println("Sub ... constructor(name, no) call")
    }

    init {
        println("Sub ... init call ....")
    }
}

fun main(args: Array<String>) {
    Sub("KIM")
    println("-----DEVIDER-----")
    Sub("KIM", 25)
}

상속 관계에서의 생성자와 init의 수행 흐름

생성자가 호출되면 다음 순서로 실행됩니다.

1. this() 혹은 super()에 의한 다른 생성자 호출
2. init 블록 호출
3. 생성자의 { } 영역 실행

 

상속과 캐스팅

스마트 캐스팅

스마트 캐스팅(Smart Casting)이란 개발자가 코드에서 명시적으로 캐스팅을 선언하지 않아도 자동으로 캐스팅 되는 것을 의미합니다.

Kotlin에서는 is 연산자를 통해 타입을 확인할 수 있습니다.

fun smartCast(data: Any): Int {
    if(data is Int) return data * data
    else return 0
}

fun main(args: Array<String>) {
    println("result : ${smartCast(10)}")
    println("result : ${smartCast(10.0)}")
}

smartCast()의 매개변수 타입은 Any 타입입니다. 

그런데 Int 타입이면 data * data를 하라고 했는데 10 * 10을 하여 100이 반환됩니다. (???)

개발자가 명시하지 않아도 자동으로 스마트 캐스팅이 된 것입니다.

 

그런데 is 연산자에 의한 스마트 캐스팅은 기초 데이터 타입에만 적용되지는 않습니다.

class MyClass1 {
    fun fun1() {
        println("fun1()...")
    }
}

class MyClass2 {
    fun fun2() {
        println("fun2()...")
    }
}

fun smartCast(obj: Any) {
    if(obj is MyClass1) obj.fun1()
    else if(obj is MyClass2) obj.fun2()
}

fun main(args: Array<String>) {
    smartCast(MyClass1())
    smartCast(MyClass2())
}

as를 이용한 캐스팅

상속 관계에 있는 객체를 명시적으로 캐스팅할 때는 as를 이용합니다.

open class Super {
    fun superFun() {
        println("superFun()...")
    }
}

class Sub1: Super() {
    fun subFun1() {
        println("subFun1()...")
    }
}

class Sub2: Super() {
    fun subFun2() {
        println("subFun2()...")
    }
}

3가지 상황에서 as를 이용한 캐스팅을 살펴보도록 하겠습니다.

하위 타입 -> 상위 타입 -> 하위 타입
fun main(args: Array<String>) {
    val obj3: Super = Sub1()
    val obj4: Sub1 = obj3 as Sub1
    obj4.superFun()
    obj4.subFun1()
}

1번째 줄에서 하위 타입의 객체를 상위 타입의 객체인 obj3에 대입하고 있습니다. 스마트 캐스팅이 이루어지므로 문제가 발생하지 않는 것을 알 수 있습니다.

2번째 줄에서는 상위 타입의 객체 obj3을 하위 타입의 객체 obj4에 대입하려는 모습니다. 상위에서 하위로는 자동으로 스마트 캐스팅이 되지 않기 때문에 as를 사용하여 명시적으로 캐스팅해주어야 합니다. (as 안쓰면 에러 발생함)

상위 타입 -> 하위 타입
fun main(args: Array<String>) {
    val obj5: Sub1 = Super() as Sub1
    obj5.subFun1()
}

상위 타입에서 하위 타입으로 캐스팅 하려는 경우에는 하위 타입을 상위 타입으로 변환했다가 다시 하위 타입으로 변환하는 경우에만 가능합니다. 원래 상위 타입이었던 객체를 하위로 캐스팅하려면 컴파일 타임에는 에러 안나지만 런타임에 에러가 발생합니다.

하위 타입 -> 상위 타입
fun main(args: Array<String>) {
    val obj6: Sub2 = Sub1() as Sub2
}

Sub1과 Sub2는 직접적인 상하위 관계의 클래스가 아닙니다.

 

정리해보면 as를 이용한 캐스팅은 상속 관계에 있는 클래스끼리만 할 수 있으며, 하위 타입 -> 상위 타입 -> 하위 타입으로 변환할 때에만 정상적으로 캐스팅이된다는 것을 알 수 있습니다.

null 허용 객체의 캐스팅

null 허용 객체는 null이 대입될 수 있기 때문에 as 를 이용하여 캐스팅 할 때 주의할 필요가 있습니다.

fun main(args: Array<String>) {
    val obj7: Super? = Sub1()
    val obj8: Sub1 = obj7 as Sub1
}

우선 하위 타입 객체가 상위 타입으로 캐스팅 되었고 다시 as로 하위 타입으로 캐스팅 됩니다. 다만, obj7은 null이 대입될 수 있는 null 허용 객체입니다.

fun main(args: Array<String>) {
    val obj7: Super? = null
    val obj8: Sub1 = obj7 as Sub1 // RUNTIME ERROR
}

런타임 에러가 발생합니다. null은 캐스팅할 수 없기 때문입니다.

이럴 때에는 as?를 이용합니다. 이는 null 허용 객체에 null이 아닌 정상적인 객체가 대입되면 캐스팅되고, null이 대입되면 null을 반환하게 됩니다.

fun main(args: Array<String>) {
    val obj7: Super? = null
    val obj8: Sub1? = obj7 as? Sub1
}

 

이번 포스트에서는 코틀린 상속 2번째에 대해 알아보았습니다.

다음 포스트에서는 코틀린에서의 접근 제한자(Access Modifier)에 대해 알아보도록 하겠습니다.

반응형
LIST

'프로그래밍언어 > Kotlin' 카테고리의 다른 글

Kotlin - 추상 클래스  (0) 2019.08.01
Kotlin 접근 제한자  (0) 2019.07.28
Kotlin 상속 (Kotlin Override) - 1  (0) 2019.07.28
Kotlin에 대한 궁금증? (작성중)  (0) 2019.07.07
Kotlin 연산자 재정의  (0) 2019.07.07
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함