kotlin in action을 보고 정리한 글입니다
코틀린의 class, interface는 자바와 차이점이 몇 가지 있다.
예를 들면 interface가 property declaration을 가질 수 있다거나
final과 public이 default라는 것이다.
코틀린에서 새롭게 등장하는 data class는 default로 가지고 있는 메소드가 있어서 기존 클래스 메소드를 일일이 정의해야 하는 번거로움을 피할 수 있게 해주기도 한다.
주요하게 봐야 하는 개념
- sealed modifier
interface는 abstract method, non-abstract method를 포함할 수 있으나 state는 포함할 수 없다.
interface Clickable {
fun click()
}
위의 코드에서 abstract method click을 정의했다.
영어로 declare과 implementation에 대해 혼란이 있을 것 같아 짚고 넘어가면,
위 처럼 interface를 만드는 것은 declare한 것이고, interface를 이용해 class를 만든 것을 implementation이라고 한다. 잘 구분하자
class Button: Clickable {
override fun click() = println("I was clicked")
}
>>> Button().click()
// I was clicked
코틀린은 자바나 자바스크립트와는 다르게 ( 인터페이스의 경우 타입스크립트) extends (implements) 대신 :
으로 간편하게 문법을 대체한다.
class는 여러 interface를 implements 할 수 있으며 extends는 최대 한개의 class를 대상으로 가능한 것은 자바와 동일하다.
override는 자바의 @Override annotation으로, 자바와는 다르게 kotlin에서는 override modifier를 사용하는 것이 필수이다. 코틀린은 자바에서 일어날 수 있는 의도치 않은 override를 방지하기 위해 이러한 assertion을 채택했다.
interface 메소드는 default implementation을 가질 수 있다. Java 8에서는 default
keyword를 사용해야 했으나 코틀린에서는 그러한 annotation이 없다.
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
여기서 click 메소드는 Regular method declaration
이라고 부르며
showOff 메소드는 method with a default implementation
이라고 부른다.
Clickable interface에 대한 implementation을 할 때, default implementation이 적용된 showOff는 제외하고 click에 대한 implementation만 필수적으로 수행하면 된다. 물론 showOff도 override가 가능하다
interface Focusable {
fun setFocus(b: Boolean) = println("I ${if (b) "god" else "lost"}focus.")
fun showOff() = println("I'm focusable!")
}
Button class에서 동일한 메소드에 대해 default implementation이 적용된 클래스와 적용되지 않은 클래스 두개를 implementation한다면 어떻게 될까? 이런 상황에서는 명시적으로 override를 해야 한다. 그렇지 않으면 다음과 같은 에러를 보여줄 것이다.
The class 'Button' must
override public open fun showOff() because it inherits
many implementations of it.
class Button: Clickable, Focusable {
override fun click() = println("I was clicked")
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
여기서 showOff 메소드는 두 interface에 동일하게 존재하기 때문에 명시적으로 override 해줘야 한다. 인터페이스에 적용한 default implementation을 상속받기 위해서는 super
키워드를 사용했다. 자바와 문법이 어떻게 다른지 보자
// java
Clickable.super.showOff()
// kotlin
super<Clickable>.showOff()
자바에서는 super class의 final 키워드가 붙은 method는 sub class에서 override할 수 없다. base class의 implementation을 변경했을 때 sub class에서 base class의 implementation을 잘못 추정하는 경우가 발생하는데, 이러한 base class를 fragile 하다고 말한다. super class에서 override 가능한 implementation에 대해 상세하게 명세해두지 않는다면, base class의 사소한 변경이 여러 코드에 걸쳐 side effect를 야기할 수 있다.
design and document for inheritance or else prohibit it
; Joshua Bloch - Effective Java의 저자
java에서 이러한 side effect를 최소화하기 위해서는 sub class에 override되기를 바라지 않는 메소드는 final modifier를 이용해 강제해야 한다.
kotlin에서는 이러한 sie effect를 최소화하기 위해 final modifier를 default로 적용한다.
override를 가능하게 하기 위해서는 open modifier를 명시적으로 사용해야 한다.
open class RichButton: Clickable {
fun disable() {}
open fun animate() {}
override fun click() {}
}
disable 을 제외한 animate, click은 다른 함수에서 override가 가능하다.
override 하는 함수가 다른 implementation에서 final이기 원한다면 명시적으로 지정해줘야 한다.
open class RichButton: Clickable {
final override fun click() {}
}
자바에서와 마찬가지로 코틀린에서 abstract class에 declare된 abstract function들은 항상 다른 class에 의해 implemenetation 되어야 하기 떄문에 open modifier를 붙이지 않아도 된다.
abstract class Animated {
abstract fun animate()
open fun stopAnimating() {}
fun animateTwice() {}
}
여기서 animate는 open modifier를 붙이지 않아도 되지만, animateTwice는 override의 대상이 아니다.
class 의 properties의 통제권에 대해 명세할 수 있는 modifier을 visibility modifier라고 하며 캡슐화를 위해 쓰인다.
코틀린의 default visibility modifier는 public이다.
코틀린에서는 internal 이라는 visibility modifier가 있는데 module 내부에서만 접근 가능하다.
internal open class TalkativeButton: Focusable {
private fun yell() = println("Hey!")
protected fun whisper() = println("Let's talk!")
}
fun TalkativeButton.giveSpeech() {
yell()
whisper()
}
TalkativeButton.giveSpeech는 public인데 TalkativeButton은 internal로 declaration이 되어있다. 코틀린은 less-visible type에 대해 참조하는 것이 금지되어 있어 internal class인 TalkativeButton을 reference type으로 사용해 public한 extension function을 만들 수 없다.
'public' member exposes its 'internal' receiver type TalkativeButton
이 문제를 해결하려면 giveSpeech를 internal로 만들거나 class를 public으로 만들어야 한다.
자바에서는 같은 패키지 내부에서 protected member에 접근할 수 있으나, 코틀린에서는 불가능하다. protected member는 class 내부 혹은 sub class에서만 접근 가능하다.
자바에서처럼 코틀린에서도 class 내부에 class를 declare 할 수 있다. 차이점은 코틀린의 nested class는 outer class instance에 default로 접근할 수 없다.
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
자바 스타일로 View를 implements하는 Button class를 implements 해보자
public class Button implements View {
@Override
public State getCurrentState() {
return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/}
public class ButtonState implements State { /*...*/ }
}
State interface를 이용해 Button class 내부에서 Button과 관련된 상태를 담을 수 있는 ButtonState를 implementation 했다. getCurrentState를 이용해 상태 instance를 반환하려고 했으나 java.io.NotSerializable- Exception: Button
에러가 나는 것을 발견할 수 있다. 자바에서는 class 내부에 다른 class를 정의할 때 default로 inner class가 되기 때문에 ButtonState class가 Button class에 대한 참조를 갖게 되어 이와 같은 에러가 나는 것이다.
코틀린에서는 어떨까?
class Button: View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) {/*...*/}
class ButtonState : State {/*...*/}
}
코틀린에서 nested class는 자바의 static nested class와 동일하다. inner class처럼 동작하기 위해서는 명시적으로 inner modifier를 붙여야 한다.
Kotlin in action p.76의 Figure 4.1
Nested class는 outer class의 reference를 갖지 않고 있고 inner class는 outer class의 reference를 가지고 있다.
코틀린에서 outer class에 대한 reference를 갖게 하고 싶다면 다음과 같이 implementation 한다.
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
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 statement를 사용할 때 default 옵션인 else를 함께 작성하는 것은 불편하다. 게다가 sub class를 만들었을 때 fun eval을 수정하지 않으면 예상치 못한 버그가 생길 수도 있다.
코틀린에서는 super class에 sealed modifier를 붙여서 compiler에게 sub class들이 생길 수 없다는 것을 알려줄 수 있다.
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
when statement 내부에서 모든 sealed class들의 sub class를 다룬다면 default branch를 만들지 않아도 된다. 참고로 sealed modifier는 open을 함축하기 때문에 open modifier를 붙이지 않아도 된다. sealed interface는 없다.
내부적으로, Expr class는 private construtctor로 구성되어 있어서 class 내부에서만 사용될 수 있다.
잘보고갑니다~!!