[Kotlin] 클래스, 객체, 인터페이스 1

코틀린 인터페이스

코틀린 인터페이스는 자바8과 비슷하게 추상 메소드 뿐만 아니라 구현이 있는 메소드도 정의할 수 있다. 다만 인터페이스에는 아무런 상태(필드)도 들어갈 수 없다.

1
2
3
4
5
6
7
8
interface Clickable {
fun click()
fun showOff() = println("I'm clickable")
}

class Button : Clickable {
override fun click() = println("I' was clicked")
}
  • 콜론(:)을 붙여 클래스 확장과 인터페이스를 구현 모두를 처리한다.
  • override변경자는 @Override 애노테이션과 동일하게 오버라이드 표시를 하지만 오버라이드 시 반드시 사용해야 한다.
  • showOff와 같이 디폴트 함수를 제공한다.

인터페이스는 자바와 동일하게 하나의 클래스가 여러 인터페이스를 구현할 수 있는데 동일한 인터페이스의 디폴트 함수가 있을 경우 코틀린에서는 컴파일 에러로 사용자에게 하위 클래스에 직접 구현하게 강제한다.

1
2
3
4
5
6
7
8
9
10
interfacle Focusable {
fun showOff() = println("I'm focusable")
}

class Button: Clickable, Focusable {
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
  • Clickable, Focusable의 showOff함수가 동일하므로 오버라이드 하였다.
  • 상위 타입 호출 시 자바와는 다르게 super<>를 사용한다. (자바는 Clickable.super.showOff()처럼 호출한다)

기본적으로 final

코틀린에서는 자바와는 다르게 기본적으로 아무것도 명시하지 않을 시 모든 클래스와 함수는 final이다. 그 이유는 취약한 기반 클래스(fragile base class)라는 문제를 기반하기 때문이다. 이 문제는 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스를 변경함으로써 깨져버린 경우에 생긴다. 즉 Effective Java에서처럼 상속을 위한 설계와 문서를 갖추거나, 그럴수 없다면 상속을 금지하라라는 철학을 따라 기본적으로 모두 final이다.

open

어떤 클래스의 상속을 허용하려면 open변경자를 붙여야 한다. 함수도 마찬가지이다.

1
2
3
4
5
open class RightButton : Clickable {
fun disable()
open fun animate()
override fun click()
}
  • disable함수는 오버라이드 불가이다.
  • animate함수는 열려있으므로 오버라이드 가능이다.
  • click함수는 오버라이드 함수므로 기본적으로 열려있다.
  • RightButton클래스는 열려 있으므로 상속이 가능하다.

abstract

추상 클래스를 정의할 땐 abstract 변경자를 사용한다. 추상 클래스 및 추상 멤버는 inteface와 동일하게 기본적으로 모두 열려있다. 그러므로 open 변경자를 명시할 필요가 없다.

1
2
3
4
5
abstract class Animated {
abstract fun animate()
open fun stopAnimating()
fun animateTwice()
}
  • abstract 클래스이므로 상속이 가능하다.
  • animate함수는 추상함수이므로 오버라이드가 가능하다.
  • stopAnimating함수는 비추상 함수이지만 open이므로 오버라이드가 가능하다
  • animateTwice함수는 비추상 함수이므로 오버라이드가 불가능하다.

가시성 변경자 : 기본적으로 공개

  • 아무 변경자도 없는 경우 선언은 모두 public이다.
  • internal 변경자는 모듈 내부에서만 볼 수 있음을 의미한다.
  • 가시성은 public > internal > protected > private 순서이다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    internal open class TalkativeButton : Fucosable {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk!")
    }

    fun TalkativeButton.giveSpeech() {
    yell()
    whisper()
    }
    • giveSpeech 함수는 확장 함수로 public이 기본이므로 internal TalkativeButton 수신 타입을 확장 할 수 없다.
    • yell함수는 private이므로 public 함수에서 접근이 불가능하다.
    • whisper함수는 protected이므로 public함수에서 접근이 불가능하다.

내부(inner) 클래스와 중첩(nested) 클래스 : 기본은 중첩

자바에서는 static 키워드를 사용하지 않고 클래스 내부에 클래스를 선언하면 기본적으로 내부(inner) 클래스로 선언된다. 내부 클래스는 외부 클래스를 모르게 참조하고 있어 만약 직렬화 시 원하는대로 작동되지 않을 수가 있다.

코틀린에서는 자바와는 다르게 내부에 클래스를 선언하게 되면 중첩(nested)클래스로 선언된다. 자바에서 static으로 선언한 내부 클래스와 똑같이 말이다. 만약 내부 클래스로 선언하려면 inner변경자를 붙여야 하며 내부에서 외부 클래스를 접근하려면 this@클래스명으로 접근해야 한다.

1
2
3
4
5
class Outer {
inner class Inner {
fun getOuterReference() : Outer = this@Outer
}
}

봉인된 클래스 : sealed

인터페이스로 선언한 클래스를 상속받은 클래스를 when구문으로 타입 검사를 할 경우 항상 else구문이 강제되어야 한다. 만약 인터페이스를 상속한 클래스를 해당 when구문에 추가하지 않는다면 else구문이 실행되므로 원하지 않는 에러가 발생할 수 있다. 코틀린에서는 이런 문제점을 해결하기 위해 sealed라는 변경자를 지원한다. 이 sealed 변경자는 클래스 계층 정의 시 계층 확장을 제한에 둔다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// interface로 상속받은 클래스 별 실행
interface Expr
class Num(val value: Int): Expr
class Sum(val left: Int, val right: Int): Expr

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

// sealed클래스로 선언된 클래스 별 실행
sealed class Expr {
class Num(val value: Int): Expr()
class Sum(val left: Int, val right: Int): Expr()
}

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

sealed 클래스 내부에 선언한 클래스들은 Expr 클래스를 상속받고 있어 when 구문에서 타입 검사가 이뤄지며 코틀린에서는 만약 Expr클래스를 상속받은 다른 클래스가 생기면 컴파일 에러로 사용자에게 알려준다. 또한 sealed 클래스는 상속이 가능하므로 open 클래스이며 기본으로 open 변경자를 갖고 있다.

출처 : Kotlin in Acation

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×