[KOTLIN] 클래스 계층 정의

코틀린 인터페이스 기본

  • 자바 8 의 인터페이스와 유사함
  • 인터페이스 내에 추상 메서드 + 구현이 있는 메서드( 자바 디폴트 메서드 ) 도 포함이 가능하다.
    • 하지만, 인터페이스에는 상태를 나타내는 필드를 포함할 수 없음.

인터페이스 정의 및 구현

interface Clickable {
    fun click()
}
  • click 이라는 추상 메서드를 가진 Clickable 인터페이스를 정의함.
    • 해당 인터페이스를 구현하는 모든 클래스는 click 메서드에 대한 구현을 제공해야함
class Button : Clickable {
    override fun click() {
        println("I was clicked")
    }
}
  • Button 클래스는 Clickable 인터페이스를 구현하고 click 메서드를 오버라이드
  • 코틀린에서는 override 키워드를 사용하여
    • 상위 클래스나 인터페이스의 메서드를 오버라이드한다는 것을 명시한다.

디폴트 구현이 있는 인터페이스 메서드

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}
  • 코틀린 인터페이스에서 메서드에 디폴트 구현이 가능
  • 특별한 키워드 필요 없음

인터페이스 충돌 해결

  • 두 인터페이스가 동일한 디폴트 구현을 가진 메서드를 제공
  • 해당 메서드를 구현하는 클래스는 명시적으로 메서드를 오버라이드해야함
interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

interface Focusable {
    fun showOff() = println("I'm focusable!")
}

class Button : Clickable, Focusable {
    override fun click() = println("I was clicked")
    override fun showOff() {
        super<Clickable>.showOff()
        super<Focusable>.showOff()
    }
}

호환성 및 디폴트 메서드 구현

  • 코틀린은 자바 6와 호환되도록 설계되었으며, 
    • 디폴트 메서드를 지원하지 않는 자바 버전에서는 인터페이스의 디폴트 메서드 구현을 정적 메서드로 가지는 별도의 클래스를 생성합니다.
  • 코틀린 1.5부터는
    • 코틀린 컴파일러가 자바 인터페이스의 디폴트 메서드를 자동으로 생성하게 되었음

인터페이스 사용 및 기반 클래스 메서드 오버라이드

  • 인터페이스는 추상 메서드의 선언과 메서드 구현을 모아두는 집합체입니다.
  • 클래스는 인터페이스를 구현하여 인터페이스에 정의된 추상 메서드를 구체화

open, final, abstract 변경자

기본적으로 final

  • 자바에서 명시적으로 final 로 금지하지 않으면 모든 클래스를 상속이 가능하다.
    • 해당 사항은..
      • 기반 클래스의 변경시 하위 클래스의 동작이 예기치 않게 바뀌는 취약한 기반 클래스 문제를 야기할 수 있음.
  • 코틀린에서는 해당 문제를 해결하기 위해, 클래스와 메서드를 기본적으로 final 로 설정한다.
  • 어떠한 클래스에 대한 상속을 허용하려면, 클래스 앞에 open 변경자를 붙여야 한다.
  • 오버라이드를 허용하고 싶은 메서드나 프로퍼티에도 open 변경자를 붙여야 한다.

예시

open class RichButton : Clickable {
    fun disable() {} // final
    open fun animate() {} // open
    override fun click() {} // open
}
  • RichButton
    • open 이므로 상속가능
  • disable
    • final 이므로 오버라이드가 불가능하다.
  • animate
    • open 이므로 오버라이드가 가능하다.
  • click
    • overrideClickable 메서드 오버라이드. 슈퍼클래스가 open 이겟죠

오버라이드 금지

open class RichButton : Clickable {
    final override fun click() {} // final
}
  • click() 함수 오버라이드 금지

스마트 캐스트

  • 클래스의 기본적인 상속 가능 상태를 final로 설정하면 스마트 캐스트가 가능하다.
  • 스마트 캐스트는 타입 검사 뒤에 변경될 수 없는 변수에만 적용이 된다.
  • 프로퍼티는 기본적으로 final이므로 대부분 스마트 캐스트에 활용할 수 있습니다.
    • 예시
      open class Animal {
          open fun speak() {}
      }
      
      class Dog : Animal() {
          override fun speak() {
              println("멍멍")
          }
      }
      
      class Cat : Animal() {
          override fun speak() {
              println("야옹")
          }
      }
      
      fun main() {
          val animal: Animal = Dog() // Animal 타입 변수에 Dog 객체 할당
      
          if (animal is Dog) { // animal이 Dog 타입인지 검사
              val dog = animal // animal을 Dog 타입으로 안전하게 캐스팅
              dog.speak() // Dog 객체의 speak() 메서드 호출
          } else {
              println("animal은 Dog 타입이 아닙니다.")
          }
      }
      
      
      //멍멍
      • Animal 클래스는 기본적으로 final 이고, open 키워드 사용으로 상속이 허용된 상태이다.
      • 위 코드에서 animal 변수는 Animal 타입으로 선언되었지만
      • 실제로는 Dog 객체를 참조한다.
      •  if (animal is Dog) 조건문에서 animal이 Dog 타입인지 검사하고,
      • 이 검사가 참이면 컴파일러는 animal을 Dog 타입으로 스마트 캐스트합니다.
      • 그 결과 animal.speak()를 호출할 때 Dog 클래스의 speak() 메서드가 호출됩니다.
      • 여기서 animal 변수는 val로 선언되어 있으므로 변경 불가능하며
        • 이는 스마트 캐스트가 가능한 조건 중 하나!

추상 클래스

  • abstract 클래스나 메서드는 인스턴스화할 수 없음
    • 반드시 하위 클래스에서 구현해야함
  • abstract 멤버는 항상 open이므로 open을 명시할 필요가 없다.

예시

abstract class Animated {
    abstract fun animate() // 반드시 오버라이드해야 함
    open fun stopAnimating() {} // open이므로 오버라이드할 수 있음
    fun animateTwice() {} // final이므로 오버라이드할 수 없음
}

인터페이스

  • 인터페이스 멤버는 항상 open이며 final로 변경할 수 없다.
  • 인터페이스 멤버에게 본문이 없으면 자동으로 추상 멤버가 된다.
  • abstract 키워드를 명시할 필요가 없다.

표 4.1 클래스 내에서 상속 제어 변경자의 의미

변경자이 변경자가 붙은 멤버는...설명
final오버라이드할 수 없음클래스 멤버의 기본 변경자
open오버라이드할 수 있음반드시 open을 명시해야 오버라이드 가능
abstract반드시 오버라이드해야 함추상 클래스의 멤버에만 사용 가능
override상위 클래스나 상위 인스턴스의 멤버를 오버라이드하는 중오버라이드하는 멤버는 기본적으로 open

가시성 변경자

protected 변경자

  • 선언이 해당 선언을 포함하는 클래스와 해당 클래스를 상속받은 하위 클래스 내에서만 접근이 가능하다.
    • 자바에서는 같은 패키지 내의 다른 클래스에서 접근이 가능했는데
    • 코틀린에서는 조금 더 명확한 의미를 부여가능하도록 변경됨

코틀린과 자바 가시성 차이

  • 코틀린의 public, protected, private 변경자는 자바 바이트코드에서도 유지
  • internal 변경자는 ⇒ 자바에선 없음 || 바이트코드 상에서는 public으로 컴파일됩니다.
  • 코틀린 컴파일러는 internal 멤버의 이름을 변경하여 자바 코드에서의 접근을 어렵게 만듭니다.
    • 예를 들어
      // Kotlin
      internal fun doSomethingInternal() {
          println("This is an internal function.")
      }

      해당 함수를 컴파일시 → 이름 변경

      // Java (컴파일된 코틀린 코드)
      public void doSomethingInternal$production_sources_for_module() {
          System.out.println("This is an internal function.");
      }
      • 이름이 이런 식으로 변경된다면 자바 코드에서 원래 함수 이름으로 접근은 불가능해짐.
      • 물론 변경된 함수를 호출하려면 변경된 이름을 정확하게 알아야함.
        • 아는 경우는 접근이 가능해질 수 있긴합니다
  • 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근할 수 없다.
    • 예를 들어
      class Outer {
          private val outerPrivate = "Outer Private"
      
          // 중첩된 클래스 (static nested class와 유사)
          class Nested {
              private val nestedPrivate = "Nested Private"
      
              fun accessNestedPrivate() = nestedPrivate
          }
      
          // 내부 클래스 (non-static nested class와 유사)
          inner class Inner {
              private val innerPrivate = "Inner Private"
      
              fun accessInnerPrivate() = innerPrivate
          }
      
          fun accessMembers() {
              val nested = Nested()
              // println(nested.nestedPrivate) // 오류: private 멤버에 접근할 수 없음
      
              val inner = Inner()
              // println(inner.innerPrivate) // 오류: private 멤버에 접근할 수 없음
          }
      }
      • 의 얘기입니다.
        • 접근이 가능하면 캡슐화를 저해하는 행위라고 생각되네요


Uploaded by N2T