티스토리 뷰

Programming Language/Kotlin

[Kotlin] 클래스 계층

SdardewValley 2022. 7. 29. 23:43
반응형

인터페이스

  코틀린에서 인터페이스 정의를 위해서 interface 키워드를 사용한다. 코틀린 인터페이스에는 추상 메서드 뿐만 아니라 자바의 default method처럼 구현이 있는 메서드도 정의할 수 있다. 하지만 필드는 정의할 수 없다.

 

interface Clickable {
  fun click()

  fun print() {
    println("click")
  }
}

class Button : Clickable {
  override fun click() {
    println("override click")
  }
}

  인터페이스 Clickable의 click은 추상 메서드이고 print는 구현된 메서드이다. 그리고 Button은 이 Clickable 인터페이스를 구현하고 있다. 자바에서는 추상 클래스를 상속할 때 extends 키워드, 인터페이스를 구현할 때 implements 키워드를 사용하지만 코틀린은 콜론(:) 하나로 해결한다. 자바와 마찬가지고 클래스는 한 개만 상속 가능하고 인터페이스는 여러 개 구현 가능하다. 인터페이스를 여러개 구현할 때 쉼표(,)를 사용하여 나열하면 된다.

  override 키워드는 단어 그래도 오버라이드한다는 표시이다. 자바는 오버라이딩 할 때 @Override 어노테이션을 붙이긴 하지만 생략 가능하다. 하지만 코틀린에서 override 키워드는 생략 불가능하다.

  자바와 또 다른 차이점으로는 자바는 구현된 메서드에 default 키워드를 붙여야만 하지만, 코틀린은 키워드를 붙이지 않아도 된다.

 

interface Clickable {
  fun click()

  fun print() {
    println("click")
  }
}

interface Showable {
  fun print() {
    println("show")
  }
}

class Button : Clickable, Showable {
  override fun click() {
    println("override click")
  }
}

print를 구현하라고 한다

  위 코드에서 Button은 Clickable, Showable 인터페이스를 모두 구현하고 있다. 그리고 두 인터페이스 각각 구현된 메서드 print가 있다. Button의 인스턴스가 두 인터페이스를 동시에 상속받으면 컴파일 오류가 발생하면서 print 메서드를 구현하고 한다. 따라서 print를 오버라이드해서 구현하면 문제가 해결 된다. 만약 두 인터페이스의 print 메서드를 모두 사용하고 싶다면 어떻게 해야할까?

 

class Button : Clickable, Showable {
  override fun click() {
    println("override click")
  }

  override fun print() {
    super<Clickable>.print()
    super<Showable>.print()
  }
}

  super<인터페이스명>을 사용해서 어떤 인터페이스의 메서드를 호출할지 지정하면 두 메서드 모두 사용할 수 있다.

 

  

변경자

open

  자바에서는 기본적으로 상속이 허용된다. 만약 클래스 상속을 안 되게 하고 싶다면 final 키워드를 사용해서 final 클래스를 생성하면 된다. 상속은 코드 재활용 측면에서 좋을 수도 있지만 예기치 않는 결과가 발생할 수도 있다. 이를 'fagile class problem'이라고 한다. 

  코틀린에서도 상속은 가능하지만, 자바와 반대로 클래스와 메서드는 final 키워드가 기본이다. 기본이 final이기 때문에 상속을 허용하려면 open 키워드를 사용해야 한다. 클래스 앞에 open 키워드를 붙이고, 오버라이드를 허용하고 싶은 메서드아 프로퍼티 앞에도 open 키워드를 추가해야 한다.

 

open class RichButton : Clickable {
  fun disinherit() {}
  open fun inherit() {}
  override fun click() {}
}

위 코드의 클래스 및 메서드를 설명하면 아래와 같다.

  • RichButton은 open class이다. 이 클래스는 상속 가능하다.
  • disinherit은 final 메서드이다. 오버라이드 할 수 없다.
  • inherit은 open 메서드이다. 이 메서드는 오버라이드 가능하다.
  • click은 Clickable 인터페이스의 메서드를 오버라이드한 것이다. 오버라이드 메서드는 기본적으로 open이다. 따라서 이 메서드는 오버라이드 가능하다.

 

final

open class RichButton : Clickable {
  fun disinherit() {}
  open fun inherit() {}
  final override fun click() {}
}

  오버라이드한 메서드는 기본적으로 open이기 때문에 오버라이드 가능하다. 만약 이 오버라이드 된 메서드를 구현 불가능하게 하고 싶다면 final 키워드를 사용하면 된다. 오버라이드한 메서드 click은 RichButton의 하위 클래스에서 오버라이드 불가능하다.

 

abstract

  코틀린에도 자바와 같이 abstract 키워드가 있다. 추상 메서드는 그 자체로 인스턴스를 생성할 수 없고 상속 받은 하위클래스들만 생성이 가능하다. 추상 클래스는 오버라이드를 해야만 사용이 가능해지고 따라서 항상 open이기 때문에 open 키워드를 명시할 필요가 없다.

 

abstract class AbstractClass {
  abstract fun first()
  open fun second() {}
  fun third() {}
}

위 추상클래스를 설명하면 아래와 같다.

  • AbractClass는 추상 클래스이다.
  • first는 추상클래스이다. 하위 클래스에서 반드시 구현해야 하는 메서드이다.
  • second는 구현 메서드이고 open 키워드를 통해서 오버라이드를 허용했다.
  • third는 추상 클래스에 속했더라도 기본적으로 final이기 때문에 오버라이드 불가능하다.

 

interface의 기본 변경자

  인터페이스 멤버의 경우 final, open, abstract를 사용하지 않는다. 인터페이스들은 항상 변경에 열려 있고 final로 변경할 수 없다. 인터페이스는 본문이 없는 경우 자동으로 추상메서드가 되기 때문에 abstract 키워드가 필요 없다.

 

 

접근자

  접근자는 외부의 접근을 제어하기 위해서 사용한다. 코틀린은 접근자가 없다면 기본적으로 public 가시성을 가진다. 그리고 자바처럼 package-private(자바의 기본 접근자)가 없다. public, protected, private 외에도 코틀린에는 internal이라는 접근자가 있다. private, public은 자바와 같다. 코틀린에서 protectected는 하위 클래스의 접근만 허락한다. internal은 모듈 내부에서만 접근 가능하다. 모듈은 한번에 컴파일되는 코틀린 파일들이다.

 

class Modifier {
    private fun privateModifier() = println("private")
    protected fun protectedModifier() = println("protected")
    fun publicModifier() = println("public")
}

fun Modifier.callFun() {
    privateModifier()
    protectedModifier()
    publicModifier()
}

  코틀린은 기본이 public 접근자를 가지기 때문에 callFun은 pulbic 함수이다. 해당 코드에서 callFun은 privateModifier, protectedModifier 메서드를 참조하지 못한다. 왜냐하면 두 함수 모두 callFun보다 좁은 범위의 접근자이기 때문이다. 

 

 

내부 클래스

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

  코틀린에서 내부 중첩 클래스는 static 키워드가 없어도 자바의 static 중첩 클래스와 같다. 만약 내부 클래스에서 외부 클래스를 참조하고 싶을 때는 inner 키워드를 클래스 앞에 추가 하면 된다. (위의 예제에서는 inner class ButtonState가 된다)

 

class Outer {
  inner class Inner {
    fun getOuterReference(): Outer = this@Outer
  }
}

 

  inner 클래스에서 외부 클래스 참조에 접근하기 위해서는 this@Outer와 같은 방식으로 접근해야 한다.

 

 

sealed

interface Expr
class Num(val value: Int): Expr
class Sum(val left: Expr, val right: Expr): Expr

fun eval(e: Expr): Int =
  when (e) {
    is Num -> e.value
    is Sum -> eval(e.right) + eval(e.left)
    else -> throw IllegalArgumentException("Unknown expression")
  }

  when을 사용할 때 항상 마지막에 어떤 조건도 만족하지 않은 경우를 처리하는 else를 추가해줘야 했다. sealed 클래스를 이용해서 else를 생략할 수도 있다.

 

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.Sum -> eval(e.right) + eval(e.left)
  }

  sealed 클래스의 하위 클래스는 같은 파일에 있어야 한다. 상위 클래스에 sealed를 붙이면 하위 클래스 정의를 제한할 수 있고, 따라서 디폴트 분기가 필요없다. sealed 클래스는 open이 생략되어도 자동으로 open class가 된다. sealed 클래스의 새로운 하위 클래스가 추가되었을 때 when에 추가되지 않으면 컴파일 에러가 발생하기 때문에 안전하기도 하다.

 

 

 

참고

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함