Kotlin(코틀린) 기본문법 6. Inheritance(상속) / Polymorphism(다형성) / Delegation(위임)

차선호·2023년 4월 2일
1

Kotlin

목록 보기
6/8
post-thumbnail

Inheritance(상속)


Inheritance

  • 모든 클래스는 Any의 하위 클래스이며, 기본적으로 상속 줄 수 없는 final class로 만들어짐

  • java와 반대로 기본이 final class로 선언(상속 불가)

    • 파생 클래스를 허용하려면 open 키워드를 사용하여 상속 가능한 상태로 선언해야 함
  • extends 키워드 대신 콜론( : ) 사용

    open class 부모 클래스 명{ //open으로 선언하여 파생 가능
    }
    class 자식 클래스 명: 부모 클래스 명( ){ //부모 클래스 상속, open 키워드 업음 -> 파생 불가
    }
  • java는 메소드만 오버라이딩 가능하지만 코틀린은 프로퍼티도 가능

상속 예제

open class Human(var name: String = "홍길동", var age: Int){ //주생성자
    fun play() = println("name : $name")
    fun sing(vol: Int) = println("Sing age : $age")
}
//주생성자를 사용하는 상속
class Woman(name: String, age: Int): Human(name, age){
    fun singHitone() = println("Happy song!")
}
//부생성자를 사용하는 상속
class Man: Human{
    val race: String
    constructor(name: String, age: Int, race: String): super(name, age){
        this.race = race
    }
}
fun main() {
    var woman = Woman("사임당",20)
    var man = Man("이순신",20,"아시아")
}

super, this

  • super

    • 상위 클래스의 메서드, 프로퍼티, 생성자를 사용하는 키워드

    • super.메서드명 / super.프로퍼티명 / super( )

  • this

    • 현재 클래스의 메서드, 프로퍼티, 생성자를 사용하는 키워드
    • this.메서드명 / this.프로퍼티명 / this( )



Polymorphism(다형성)


Polymorphism

  • Type Polymorphism

    • 타입추론으로도 사용 가능
  • Method Polymorphism

  • 오버라이딩

    • 상속 받아서 선언부는 동일하나 구현부는 다르게 바꿔 재설계 가능
  • 오버로딩

    • 함수 명은 동일하나 인자를 다르게 하여 여러 경우를 처리

오버라이딩

  • method는 기본적으로 final method로 오버라이딩을 금지

  • open 키워드를 사용하여 오버라이딩 허용

    open class Human(){
        fun play(){} //최종 method로 오버라이딩 불가
        open fun sing(){} //open mehtod로 오버라이딩 가능
        open fun sing2(){}
    }
    open class Animal: Human(){
        override fun sing() = println("override sing") //super 클래스 그대로 재정의하여 이 또한 open method
        final override fun sing2() = println("override sing2 final") //final method로 오버라이딩 불가
    }
    open class Animal2: Animal(){
        override fun sing() = println("override sing one more") //이 또한 마찬가지로 open method
    }
  • 런타임 시 만들어진 Object에서 최종적으로 오버라이딩된 메서드가 호출됨

    open class Human(){
        fun play() = println("Human play") //최종 method로 오버라이딩 불가
        open fun sing() = println("Human sing") //open mehtod로 오버라이딩 가능
        open fun sing2() = println("Human sing2")
    }
    open class Animal: Human(){
        override fun sing() = println("Animal sing") //super 클래스 그대로 재정의하여 이 또한 open method
        final override fun sing2() = println("Animal sing2") //final method로 오버라이딩 불가
    }
    open class Animal2: Animal(){
        override fun sing() = println("Animal2 sing") //이 또한 마찬가지로 open method
    }
    fun main() {
        var animal = Animal2()
        animal.play()
        animal.sing()
        animal.sing2()
    }
    
    <---실행 결과--->
    Human play
    Animal2 sing
    Animal sing2




Property와 초기화


Property

  • 자바에서 필드

    • 단순한 변수 선언만 가지기 때문에 접근을 위한 메서드를 따로 만들어야 함

      • setter, getter를 개발자가 생성
  • 코틀린의 프로퍼티

    • 변수 선언과 기본적인 접근 메서드를 모두 가지고 있음

      • setter, getter는 개발자가 할 필요 없이 내부적으로 생성하게 됨
  • 접근 메서드는 생략(내부적으로 생성)

    class User(id: Int, name: String, age: Int){
        val id: Int = id // val로 선언 -> 읽기 전용 
        var name: String = name // var로 선언 -> 변경 가능
        var age: Int = age
    }
  • 간략화하면 아래와 같고, getter / setter가 기본적으로 동작

    class User(val id: Int, var name: String, var age: Int){ }
    fun main() {
        val user = User(1,"길동",30)
        user.age = 25
        println("${user.name}의 나이 = ${user.age}")
    }
    
    <---실행 결과--->
    길동의 나이 = 25

setter, getter 직접 지정

  • 값에 대한 validation(확인) 가능
    • 기본적인 getter, setter를 대신해 지정 가능

    • 불변형인 val은 getter만 설정 가능

    • filed 변수를 활용하여 기존 값과 새로운 값 비교 가능
      - filed는 원래 값을 가지는 가상의 변수

      class User(id: Int, name: String, age: Int){
          val id: Int = id
              get() = field
          var name: String = name
              get() = field
              set(value){
                  field = value
              }
          var age: Int = age
              get() = field
              set(value){
                  println("현재값 : $field")
                  if(value<0 || value>150) println("다시 확인 바람")
                  else field = value
              }
      }

지연 초기화

  • 지연 초기화가 필요한 이유

    • 변수나 객체의 값은 생성 시 반드시 초기화가 필요함

    • 클래스 내에서 선언한 후 객체의 정보가 나중에 나타나는 경우 초기화 할 수 있는 방법이 필요함

    • 지연 초기화를 위해 lateinit과 lazy 키워드 사용

lateinit을 통한 지연 초기화

  • 의존성이 있는 초기화나 unit 테스트를 위한 코드 작성 시 프로퍼티 지연 초기화가 필요

    • 예) Car 클래스의 초기화 부분에 Engine 클래스와 의존성을 가지는 경우, Engine 객체가 생성되지 않으면 완전하게 초기화 될 수 없음
  • 클래스를 선언할 때 프로퍼티 선언은 null을 허용하지 않음

    • 선언하려면 ? 형으로 선언하고 null 할당

    • lateinit 키워드를 사용하면 굳이 바로 할당하지 않아도 됨

  • var로 선언된 프로퍼티만 가능

  • 프로퍼티에 대한 getter, setter 사용 불가

    class Person{
        lateinit var name: String
        fun test(){
            if(! ::name.isInitialized) { //초기화 여부 판단
                println("not initailized")
            }else{
                println("initialized")
            }
    
        }
    }
    fun main() {
        val gildong = Person()
        gildong.test()
        gildong.name = "gildong"
        gildong.test()
    }
    
    <---실행 결과--->
    not initailized
    initialized

lazy를 통한 지연 초기화

  • 호출 시점에 by lazy{…} 부분의 초기화를 진행

  • 불변의 변수 선언인 val에서만 사용 가능(읽기 전용)

  • val이므로 값을 다시 변경 불가

    class Lazy{
        init{println("init block")}
        val subject by lazy{
            println("lazy initialized")
            "lazy test"//lazy 반환 값
        }
        fun flow(){
            println("not initialized")
            println("subject inital : $subject")
            println("subject initialized : $subject")
        }
    }
    fun main() {
        val test = Lazy()
        test.flow()
    }
    
    <---실행 결과--->
    init block
    not initialized
    lazy initialized
    subject inital : lazy test
    subject initialized : lazy test
  • by lazy로 선언된 code block을 수행하는 것이므로, multi thread 동작 시 고려 필요

  • 3가지 모드 지정 가능

    • SYNCHRONIZED

      • lock을 사용해 단일 스레드만이 사용하는 것을 보장(기본값)
    • PUBLICATION

      • 여러 군데서 호출될 수 있으나 처음 초기화된 후 반환 값 사용
    • NONE

      • lock을 사용하지 않기 때문에 빠르지만 다중 스레드 접근 가능
      • 값의 일관성을 보장할 수 없음


Delegation(위임)


Delegation

  • 위탁자 → 수탁자 형태로 어떤 일의 책임 및 처리를 다른 클래스 또는 메서드에게 넘긴다는 의미

  • 한 객체가 기능 일부를 다른 객체로 넘겨주어 첫 번째 객체 대신 수행하도록 하는 일

  • 다른 클래스의 기능을 그대로 사용하면 상속 대신 위임

  • 위임을 활용하면 한 객체의 변경이 다른 객체에 미치는 영향이 적어짐

By를 이용한 클래스 위임

  • Interface 타입의 위임만 가능

  • 하나의 클래스가 다른 클래스에 위임하도록 선언하고 위임된 클래스가 가지는 멤버를 참조 없이 호출

  • 코틀린 라이브러리는 대부분 open되어 있지 않아 상속이나 직접 확장이 어려움

    • 위임을 통해 상속과 비슷하게 확장 가능
  • 다른 클래스의 멤버를 사용하도록 위임

    interface Animal{
        fun eat() = println("Animal.eat()")
    }
    class Cat: Animal{}
    val cat = Cat()
    class Robot: Animal by cat
    fun main() {
        var robot = Robot()
        robot.eat()
        println(robot::class.java)
    }
    
    <---실행 결과--->
    Animal.eat()
    class com.android.example.kotlinproject.Robot

observable( ) 함수를 통한 위임

  • 프로퍼티를 표준 위임 함수인 observable( )로 위임 가능

    • 콜백처럼 프로퍼티의 내용이 변경될 때 수행한 작업을 정의할 수 있음
      class Observable{
          var name: String by Delegates.observable("처음"){
              property, oldValue, newValue -> println("$oldValue -> $newValue")
          }
      }
      fun main() {
          var ob = Observable()
          ob.name = "두 번째"
          ob.name = "세 번째"
      }
      
      <---실행 결과--->
      처음 -> 두 번째
      두 번째 -> 세 번째

vetoable( ) 함수를 통한 위임

  • 프로퍼티를 표준 위임 함수인 vetoable( )로 위임 가능

    • observable( )과 거의 동일하지만 조건에 맞지 않으면 새로운 값 할당을 거부할 수 있음

      • observable( )은 단순히 바뀌는 것을 지켜보는 것이고, vetoable( )은 바뀔 때 할당할 지 결정할 수 있음
    • true를 반환하면 새로운 값 할당, false를 반환하면 할당 거부

      class Vetoable{
          var age: Int by Delegates.vetoable(22){
              property, oldValue, newValue ->
              println("property : ${property.name}, $oldValue -> $newValue, result : ${oldValue>newValue}")
              oldValue>newValue
          }
      }
      fun main() {
          var ve = Vetoable()
          ve.age = 20
          println(ve.age)
          ve.age = 25
          println(ve.age)
      }
      
      <---실행 결과--->
      property : age, 22 -> 20, result : true
      20
      property : age, 20 -> 25, result : false
      20

companion object


정적 변수와 컴페니언 객체

  • 코틀린은 static 키워드가 없음
    • 대신 companion object로 사용

      class Person{
          var id: Int = 0
          var name: String = "hong"
          companion object{ //고정된 static 내부 클래스처럼 정의
              var language: String = "Ko"
              fun work() = println("work()...")
          }
      }
      fun main() {
          println(Person.language)
          Person.language = "En"
          println(Person.language)
          Person.work()
      }
      
      <---실행 결과--->
      Ko
      En
      work()...
profile
dkssud!

0개의 댓글