Programming/Kotlin

코틀린(Kotlin) - 클래스 계층(인터페이스, open, final, abstract, 중첩 클래스, sealed 클래스)

JunsuKim 2021. 10. 3.
728x90

인터페이스

코틀린의 인터페이스는 자바에서의 인터페이스와 유사하다.

interface로 시작을 하며, 메서드를 구현할 때 override 변경자를 사용한다.(자바의 @override를 대체)

interface Clickable {
    fun click()
}

class Button: Clickable {
    override fun click() = println("I was clicked!")
}

인터페이스와 클래스 확장은 클래스: 인터페이스 클래스명 형식으로 구현한다.

 

여러 인터페이스를 구현 가능하지만, 클래스는 한 개만 상속 가능하다.

또한 override된 함수 이름과 다른 함수의 이름이 중복되서는 안 된다.

디폴트 구현

자바에서는 디폴트를 구현할 때 앞에 default 키워드를 붙여야 하지만, 코틀린에서는 default 키워드를 붙일 필요 없다. 

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") 
}

// 컴파일 에러

위 코드는 컴파일 에러가 뜨게 된다.

코틀린에서는 중복된 상위 메서드는 하위 클래스에서 반드시 구현되어야 하기 때문이다.

또한 디폴트 함수 이름이 같은 인터페이스를 정의하게 되어도 컴파일 에러가 발생한다.

이를 해결하기 우선 애매모호함을 없애기 위해 같은 이름인 showOff함수를 재정의를 하고, super <타입>을 이용하여 이를 사용할 상위 타입을 지정해야 한다.

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") 
    overrid showOff() {
        super<Clickable>.showOff()
    }
}

open, final abstract 수식어

하나의 부모 클래스와 여라 자식 클래스가 있을 때, 부모 클래스를 수정하게 되면 자식 클래스의 동작이 변경될 수 있다. 이를 취약한 기반 클래스(Fragile base class)라 하는데, 이러한 문제를 해결하기 위해 상속을 위한 설계와 문서를 갖추거나, 상속을 금지하는 것이다.

이를 간단히 하면 자식 클래스에서 override하게 의도된 클래스와 메서드가 아니면 final로 만들으라는 것이다.

코틀린에서는 기본적으로 클래스와 클래스의 멤버는 final이다.

이 때문에 상속을 허용 또는 오버라이딩을 허용하고 싶은 메서드나 프로퍼티 앞에 open 변경자를 붙여야 한다.

open class RichButton : Clickable { 
    fun disable() { }
    open fun animate() { }
    override fun click() { }
}

한 행씩 보면

1행에는 open키워드가 붙어있다. 이는 다른 클래스에서 이 클래스를 상속할 수 있음을 의미한다.

2행에선 따로 키워드가 붙어있지 않다. 이럴 때는 기본적으로 final이기 때문에 오버라이드 불가능하다.

3행에선 open이므로 오버라이드가 가능하며

4행에선 이미 오버라이딩이 됐지만, final을 명시하여 금지할 수 있다.

abstract class Animated {
    abstract fun animate()
    open fun stopAnimating() { }
    fun animateTwice() { }
}

abstract는 추상 클래스의 멤버에만 붙일 수 있다.

클래스에 abstract가 붙어 추상 클래스이며 이는 인스턴스를 만들 수 있다.

또한 2행을 보면 추상 함수이며, 구현이 없다. 때문에 하위 클래스에서 반드시 오버라이딩을 해야 한다.

 

이를 요약해보면 다음과 같다.

 수식어                       재정의                                   설명
final 오버라이딩 불가능 클래스 멤버의 기본 수식어
open 오버라이딩 가능 open을 명시해야 오버라이딩 가능
abstract 반드시 오버라이딩 해야 함 추상 클래스의 멤버에만 붙일 수 있다. 구현이 있으면 안 됨
override 상위 클래스나 인스턴스의 멤버를 오버라이딩 오버라이딩하는 멤버는 기본으로 open. 오버라이딩을 금지하려면 final을 명시해야 함

가시성 변경자(수식어)

가시성 변경자를 사용하여 한 클래스의 멤버 필드와 메서드에 대한 다른 클래스의 접근 여부를 제어할 수 있다.

  수식어                           클래스 멤버                       최상위 선언
public 모든 곳에서 접근 가능 모든 곳에서 접근 가능
internal 같은 모듈에서만 접근 가능 같은 모듈에서만 접근 가능
protected 같은 클래스 및 하위 클래스 안에서만 접근 가능 (최상위 선언에 적용 불가)
private 같은 클래스 안에서만 접근 가능 같은 파일 안에서만 접근 가능

가시성의 규칙

  • 기반 타입 목록에 있는 타입은 자신보다 더 가시성이 같거나 넓어야 한다.
  • 기반 타입의 제네릭 타입 파라미터에 있는 타입은 자신보다 가시성이 같거나 넓어야 한다.
  • 메서드 시그니처에 사용된 모든 타입의 가시성은 메서드의 가시성과 같거나 넓어야 한다.

중첩 클래스

자바와 같이 코틀린에서도 클래스 안에 클래스를 선언할 수 있다. 하지만 코틀린에서는 중첩 클래스를 명시적으로 요청하지 않으면 바깥 클래스 인스턴스에 대한 접근 권한이 없다는 차이점이 있다.

 

class Button : View {
    override fun getCurrentState(): State = ButtonState()
    override fun restoreState(state: State) { . . . . }
    
    class ButtonState : State { . . . . }
}

자바의 형태로 이 코드를 짰다면 오류가 났을 것이다. 자바에서는 다른 클래스 안에 정의한 클래스는 자동적으로 내부 클래스가 되어 바깥 클래스에 참조되기 때문이다. 하지만 코틀린에서는 문제가 되지 않는다.

중첩 클래스에 아무런 변경자가 붙지 않아 자바의 static 중첩 클래스와 같다고 보면 된다.

바깥 클래스를 참조하게 만들고 싶으면 inner 변경자를 붙이면 된다.

class Outer {
    inner class Inner {
        fun getOuterRef(): Outer = this@Outer
    }
}
                     클래스 B 안에 정의된 클래스 A 자바에서 코틀린에서
중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음) static class A class A
내부 클래스(바깥쪽 클래스에 대한 참조를 저장함) class A inner class A

sealed 클래스

  • 클래스 계층 정의 시 계층 확장 제한
    • 같은 파일 안에서만 하위 클래스 선언 가능하다.
    • sealed 클래스의 내부 클래스로만 하위 클래스 선언 가능하다.
  • sealed의 바로 하위 클래스가 한 파일에만 존재
    • 다른 파일에 하위 클래스를 선언하면 컴파일 에러가 난다.
    • 단 sealed 클래스를 상속한 하위 클래스를 다른 파일에서 다시 상속하는 것은 가능하다.
sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when (e) {
    is Expr.Num -> e.value
    is Expr.Su, -> eval(e.right) + eval(e.left)
}

selaed클래스를 사용함으로써, when을 사용할 때 else를 사용하지 않아도 된다. 

 

출처 : Kotlin In Action

728x90

댓글