본문 바로가기

프로그래밍/Kotlin

[Kotlin 요약 정리] 7. 상속

7. 상속

1) Any 클래스

- 모든 클래스는 Any 클래스의 서브 클래스

- Any 클래스는 java.lang.Object 클래스와 달리 equals(), toString()을 제외한 다른 멤버들을 제공하지 않음

 

2) 상속을 통한 클래스 정의

- 클래스 정의시 다른 클래스를 상속받으면 정의된 멤버를 사용 가능

- open 예약어를 통해 상속이 허용된 클래스만 상속 가능

- 클래스 선언시 ':클래스명()' 의 형태로 상속

 

open class Parent constructor(val pr: String = "init"){

   fun hello() = println("Hello $pr")

}



class Child constructor(name: String): Parent(name){

   fun say() = println("My name is $pr")

}



fun main() {

   val child = Child("John")

   child.hello()

   child.say()

}

// 결과

Hello John

My name is John

 

3) 오버라이드

- 상위 클래스에 정의된 프로퍼티나 함수를 하위 클래스에서 재정의하는 것

- open 예약어를 통해 오버라이딩이 허용된 프로퍼티나 함수만 상속 가능

- final 예약어와 함께 선언된 경우 오버라이딩 불가

- override 예약어를 통해 구현

- override 예약어는 open 예약어가 포함되어있어서 하위 클래스에서 상속 가능(final을 추가해서 금지 가능)

- 프로퍼티 오버라이딩 제약조건

동일한 이름과 타입만 가능

val -> val, var 가능 / var -> var만 가능

(타입)? -> (타입)?, (타입) 가능 / (타입) -> (타입)만 가능 (*null 불허 -> 허용 불가)

 

open class ParentClass{

   open val parentProperty: String = "parentProperty"

       get() = field + " from ParentClass"

   open fun parentFunction(){

       println("Hello from ParentClass")

   }

}



class ChildClass: ParentClass(){

   override var parentProperty: String = "parentProperty"

       get() = field + " from ChildClass"

   override fun parentFunction(){

       println("Hello from ChildClass")

   }

   fun useSuper(){

       println("######### useSuper() #########")

       println(super.parentProperty)

       super.parentFunction()

   }

}



fun main() {

   val parentClass = ParentClass()

   val childClass = ChildClass()

   println("Parent: ${parentClass.parentProperty} / Child: ${childClass.parentProperty}")

   parentClass.parentFunction()

   childClass.parentFunction()

   childClass.useSuper()

}



// 결과

Parent: parentProperty from ParentClass / Child: parentProperty from ChildClass

Hello from ParentClass

Hello from ChildClass

######### useSuper() #########

parentProperty from ParentClass

Hello from ParentClass



4) 상속과 캐스팅

- 자손 객체를 부모 타입에 대입할 경우 스마트 캐스팅이 발생

- 부모 객체를 자손 타입에 대입할 경우 조건에 맞게 명시적 변환이 필요함

- 상속 관계의 객체를 명시적 형변환시 as 키워드를 사용

- null 허용 객체를 대입 시 as? 키워드를 사용

 

open class ParentCls{}

class ChildCls: ParentCls(){}



fun main() {

   val a: ParentCls = ChildCls()

//    val b: ChildCls = ParentCls() // 에러!

   val b: ChildCls = ParentCls() as ChildCls



   val x: ParentCls? = null

   val y: ChildCls? = x as? ChildCls

}

 

5) 접근 제한자

- 클래스, 프로퍼티, 함수 등을 이용할 때 접근 범위를 지정

- 접근 제한자의 종류

public: 어느 곳에서나 접근 가능(기본값)

internal: 같은 모듈 내에서만 접근 가능

protected: 최상위 레벨에서는 사용 불가(클래스 내부에 선언시 해당 클래스 + 서브 클래스에서만  접근 가능)

private: 같은 파일 내에서만 접근 가능 (클래스 내부에 선언시 해당 클래스에서만 접근 가능)

 

- open과 private는 함께 사용 불가하며 오버라이딩시 접근 범위를 줄일 수 없다.

 

- private 키워드를 활용해서 클래스 내의 프로퍼티를 읽기 전용으로 사용 가능

 

class PrivateEx{

   private var data: Int = 3



   fun getData() = data

}



fun main() {

   val pe = PrivateEx()

//    pe.data = 5 // 에러!

   println(pe.getData()) // 결과 3

}