코틀린 베이직 KB 21 인터페이스 — 기본 구현과 프로퍼티를 담는다
코틀린 베이직 · 21/26

KB 21 인터페이스 — 기본 구현과 프로퍼티를 담는다

gabury1고친 사람 github-actions[bot]

0. 인터페이스에 프로퍼티가 있다

자바 인터페이스에 「이름」을 넣고 싶으면 getName() 추상 메서드를 하나 둡니다. 코틀린은 그 대신 val name 을 바로 씁니다.

Kotlin
interface Named {
    val name: String
    fun greet() = "hi, $name"
}

class User(override val name: String) : Named

fun main() {
    val u = User("kim")
    println(u.name)    // kim
    println(u.greet()) // hi, kim
}
  • interface Named { ... } — 인터페이스 선언입니다. 자바처럼 interface 키워드로 엽니다
  • val name: String — 프로퍼티 선언입니다. 프로퍼티는 필드와 게터를 한 이름으로 묶은 것이고, val 은 다시 대입할 수 없다는 표시입니다
  • fun greet() = "hi, $name" — 본문이 있는 함수입니다. = 뒤에 돌려줄 값을 바로 적었고, $name 은 문자열 안에 값을 끼우는 템플릿입니다
  • class User(...) : Named — 콜론 뒤에 구현할 인터페이스를 적습니다. 자바의 implements Named 입니다

코틀린 컴파일러 kotlinc 로 컴파일하고 kotlin 명령으로 돌렸습니다. -d out 은 결과물을 out 폴더에 쓰라는 옵션이고, 찍힌 값은 코드 오른쪽 주석에 붙였습니다. FirstKt 는 First.kt 파일 바로 아래 함수들을 담으려고 컴파일러가 지은 클래스 이름입니다.

터미널
$ kotlinc First.kt -d out
$ kotlin -cp out FirstKt

인터페이스에 적은 name 을 User 가 채웠고, 인터페이스에 적힌 greet() 이 그 name 을 읽었습니다.

같은 모양을 자바 인터페이스에 그대로 옮겨 봤습니다.

Java
public interface Named {
    String name;
}
터미널
$ javac -d jout Named.java
Named.java:2: error: = expected
    String name;
               ^
1 error

자바는 인터페이스에 값 없는 필드를 못 둡니다. 인터페이스의 필드는 전부 public static final 상수라서 값을 바로 줘야 합니다(자바 언어 명세 9.3). 구현 클래스마다 다른 이름을 갖게 하려면 결국 getName() 을 추상 메서드로 적어야 합니다.

그런데 코틀린 쪽도 값을 담은 것은 아닙니다. User 가 override val name 으로 값을 들고 있고, 인터페이스는 「이런 이름의 값이 있어야 한다」만 적었습니다. 이 편은 그 차이를 따라갑니다.

1절은 선언과 구현, 2절은 본문이 있는 함수(기본 구현), 3절은 프로퍼티가 값을 못 담는다는 것, 4절은 기본 구현이 둘 겹칠 때, 5절은 추상 클래스(직접 객체를 못 만들고 물려받아서만 쓰는 클래스)와의 대조입니다. 출력은 전부 Kotlin 2.4.10, JDK 21 에서 직접 돌린 결과입니다.

1. 구현하는 쪽은 override 를 빼먹을 수 없다

이 절에서는 인터페이스를 구현할 때 컴파일러가 무엇을 요구하는지 봅니다. 함수 둘을 적은 Shape 를 두고, 한 쪽은 구현을 빼먹고 한 쪽은 override 를 빼먹은 Square 를 만들었습니다.

Kotlin
interface Shape {
    fun area(): Double
    fun label(): String
}

class Square(val side: Double) : Shape {
    fun area(): Double = side * side
}
  • fun area(): Double — 본문이 없는 함수입니다. 자바 인터페이스의 추상 메서드와 같습니다
  • class Square(val side: Double) — 클래스 이름 뒤 괄호가 주생성자입니다. val 을 붙인 인자는 그대로 프로퍼티가 됩니다
  • : Shape — 인터페이스 이름 뒤에 괄호가 없습니다. 인터페이스에는 생성자가 없어서 부를 것이 없습니다
터미널
$ kotlinc Shapes.kt -d out
Shapes.kt:6:1: error: class 'Square' is not abstract and does not implement abstract member:
fun label(): String
class Square(val side: Double) : Shape {
^^^^^^^^^^^^
Shapes.kt:7:9: error: 'area' hides member of supertype 'Shape' and needs an 'override' modifier.
    fun area(): Double = side * side
        ^^^^

에러가 둘입니다. 첫째는 label() 을 구현하지 않았다는 것으로, 자바에서도 똑같이 막히는 에러입니다.

둘째가 코틀린에만 있는 에러입니다. area() 는 이름도 타입도 맞게 썼는데, override 를 안 붙였다고 막혔습니다. 자바의 @Override 는 붙이면 검사해 주는 선택 사항이지만, 코틀린의 override 는 키워드이고 빠지면 컴파일이 안 됩니다.

이 규칙이 쓸모 있는 때는 인터페이스가 바뀔 때입니다. 누가 Shape 에서 area() 를 지우면, 구현 쪽의 override fun area() 가 'area' overrides nothing. 에러를 냅니다. 자바에서 @Override 를 안 붙였다면 아무 에러 없이 쓰이지 않는 메서드 하나가 남습니다.

고친 모양은 이렇습니다.

Kotlin
class Square(val side: Double) : Shape {
    override fun area(): Double = side * side
    override fun label(): String = "square"
}

클래스 상속과 섞을 때는 콜론 뒤에 쉼표로 늘어놓습니다. class Dog : Animal(), Named, Shape 처럼 부모 클래스는 괄호(생성자 호출)가 붙고 인터페이스는 안 붙습니다. 괄호가 있는 쪽이 클래스라서, 자바처럼 extends 와 implements 를 따로 적지 않아도 둘이 갈립니다.

정리하면, 인터페이스를 구현할 때 코틀린은 빠진 멤버와 함께 override 가 빠진 멤버도 에러로 잡습니다.

2. 기본 구현 — 자바 default 메서드와 같은 쓰임

0절의 greet() 처럼 인터페이스 함수에 본문을 적으면, 구현 클래스가 따로 쓰지 않아도 그 본문이 쓰입니다. 이것을 기본 구현(default implementation)이라 부릅니다. 자바 8 의 default 메서드와 같은 쓰임인데, 코틀린은 default 키워드 없이 본문만 적습니다.

이 절에서는 기본 구현 둘을 가진 Greeter 를 두고, 한 구현은 그대로 쓰고 한 구현은 하나만 바꿉니다.

shout() 은 두 클래스 모두 손대지 않았습니다. 무엇이 찍힐지는 shout() 안의 greet() 이 누구의 것이냐에 달렸습니다.

Kotlin
interface Greeter {
    val name: String
    fun greet(): String = "hi, $name"
    fun shout(): String = greet().uppercase()
}

class Plain(override val name: String) : Greeter

class Polite(override val name: String) : Greeter {
    override fun greet(): String = "good day, $name"
}

fun main() {
    val gs: List<Greeter> = listOf(Plain("kim"), Polite("lee"))
    for (g in gs) {
        println(g.shout())
    }
}
터미널
$ kotlinc Greeter.kt -d out
$ kotlin -cp out GreeterKt
HI, KIM
GOOD DAY, LEE
  • uppercase() — 문자열을 대문자로 바꾼 새 문자열을 돌려줍니다. 자바의 toUpperCase() 입니다
  • listOf(...) — 읽기 전용 리스트를 만듭니다. List<Greeter> 는 자바의 List<Greeter> 와 같은 뜻입니다
  • for (g in gs) — 자바의 for (Greeter g : gs) 입니다

Polite 는 greet() 만 바꿨는데 shout() 결과도 바뀌었습니다. 기본 구현 안에서 부른 함수도 구현 클래스가 바꾼 쪽으로 갑니다. 자바 default 메서드와 같은 동작입니다.

이 모양을 흔히 쓰는 때는 「대부분 똑같고 몇 군데만 다른」 구현이 여럿일 때입니다. 공통 흐름은 기본 구현에 두고, 갈리는 한두 함수만 추상으로 남겨 구현 쪽이 채우게 합니다.

자바 클래스가 이 인터페이스를 구현하면

코틀린 인터페이스를 자바 클래스가 구현하는 일도 흔합니다. 이때 기본 구현이 자바에서도 기본 구현으로 보이는지, 자바 클래스가 greet()·shout() 을 안 쓰고 컴파일되나로 확인했습니다.

코틀린의 val name 은 자바에서 getName() 으로 보입니다. 그래서 자바 클래스는 그것 하나만 구현했습니다.

Java
public class JGreeter implements Greeter {
    public String getName() {
        return "park";
    }

    public static void main(String[] args) {
        JGreeter g = new JGreeter();
        System.out.println(g.greet());
        System.out.println(g.shout());
    }
}
터미널
$ javac -cp out -d jout JGreeter.java
$ java -cp out:jout:kotlin-stdlib.jar JGreeter
hi, park
HI, PARK
  • -cp out — 컴파일할 때 코틀린이 만든 Greeter 를 찾을 경로입니다
  • kotlin-stdlib.jar — 코틀린 표준 라이브러리입니다. 빼고 돌리면 shout() 에서 NoClassDefFoundError: kotlin/jvm/internal/Intrinsics 로 멈췄습니다

javac 는 greet()·shout() 이 없다고 불평하지 않았고, 실행하면 코틀린 쪽 기본 구현이 불렸습니다. 코틀린 2.4.10 의 기본 설정에서는 자바 클래스도 기본 구현을 물려받습니다(코틀린 문서).

정리하면, 인터페이스 함수에 본문을 적으면 그게 기본 구현이고, 구현 쪽은 바꾸고 싶은 것만 override 합니다. 자바에서 구현해도 같습니다.

3. 인터페이스의 프로퍼티는 값을 못 담는다

0절에서 인터페이스가 val name 을 적었지만 값은 User 가 들고 있었습니다. 이 절에서는 인터페이스 안에 값을 직접 넣으려 하면 어떻게 되는지 보고, 구현 쪽이 무엇을 줘야 하는지 봅니다.

먼저 막히는 두 가지입니다. 초깃값을 주는 것과, 게터 안에서 저장된 값을 읽는 것입니다.

Kotlin
interface Counter {
    val count: Int = 0
    val twice: Int
        get() = field * 2
}
  • val count: Int = 0 — 클래스라면 평범한 초깃값입니다
  • get() = field * 2 — 프로퍼티 아래에 get() 을 적으면 읽을 때마다 그 식을 계산합니다(커스텀 게터). field 는 그 프로퍼티가 값을 담아 두는 칸을 가리키는 이름입니다
터미널
$ kotlinc BadProps.kt -d out
BadProps.kt:2:22: error: property initializers in interfaces are prohibited.
    val count: Int = 0
                     ^
BadProps.kt:3:5: error: property in interface cannot have a backing field.
    val twice: Int
    ^^^^^^^^^^^^^^

두 에러가 같은 말을 합니다. 프로퍼티 값을 담아 두는 칸을 백킹 필드(backing field)라 부르는데, 인터페이스 프로퍼티에는 백킹 필드가 없습니다. 초깃값을 넣을 칸도, field 로 읽을 칸도 없습니다.

인터페이스는 여러 클래스가 함께 구현하는 약속이라, 객체 하나하나에 값을 둘 곳이 없습니다. 자바 인터페이스에 인스턴스 필드가 없는 것과 같은 까닭입니다.

그러면 구현 쪽이 무엇을 주나

백킹 필드 없이도 되는 것이 하나 있습니다. 매번 계산하는 커스텀 게터는 인터페이스에 적을 수 있습니다. 이 절의 두 번째 실험은 그것과, 구현 쪽이 값을 주는 두 방법을 한 파일에 담았습니다.

Kotlin
var seq = 0

fun nextId(): Int {
    seq += 1
    return seq
}

interface Entity {
    val id: Int
    val kind: String
        get() = "entity"
}

class Stored : Entity {
    override val id = nextId()
}

class Computed : Entity {
    override val id: Int
        get() = nextId()
    override val kind = "computed"
}

fun main() {
    val s = Stored()
    println(s.id)   // 1
    println(s.id)   // 1
    val c = Computed()
    println(c.id)   // 2
    println(c.id)   // 3
    println(s.kind) // entity
    println(c.kind) // computed
}
  • var seq = 0 — 파일 바로 아래 둔 변수입니다. var 는 다시 대입할 수 있는 변수입니다
  • nextId() — 부를 때마다 1 씩 늘린 번호를 돌려줍니다. 몇 번 불렸는지 보려고 넣었습니다
  • val kind ... get() = "entity" — 인터페이스에 적은 커스텀 게터입니다. 담는 칸 없이 매번 "entity" 를 돌려줍니다
터미널
$ kotlinc Props.kt -d out
$ kotlin -cp out PropsKt

같은 override val id 인데 Stored 는 1 을 두 번, Computed 는 2 와 3 을 찍었습니다. Stored 의 = nextId() 는 객체를 만들 때 한 번 불려 값을 칸에 넣고, Computed 의 get() = nextId() 는 읽을 때마다 불립니다.

구현 쪽이 줄 수 있는 것을 모으면 이렇습니다.

구현 쪽이 쓰는 모양 값을 어디 두나 nextId() 가 불리는 때
class User(override val name: String) 생성자 인자로 받아 백킹 필드에 없음(받은 값)
override val id = nextId() 백킹 필드에 객체를 만들 때 한 번
override val id: Int get() = nextId() 담지 않는다 읽을 때마다
아무것도 안 씀(인터페이스에 게터가 있을 때) 담지 않는다 인터페이스 게터가 읽을 때마다

조심할 곳은 세 번째 줄입니다. 호출부에서는 c.id 가 필드 읽기처럼 보여서, 매번 계산된다는 것이 안 보입니다. 계산이 무겁거나 부를 때마다 결과가 달라지는 것이라면 = 값 으로 한 번만 계산해 두는 편을 고릅니다.

Computed 의 override val kind = "computed" 처럼, 인터페이스에 게터가 있는 프로퍼티도 구현 쪽에서는 값을 담는 프로퍼티로 바꿀 수 있습니다. 담는 칸은 구현 클래스에 생기니 막히지 않습니다.

정리하면, 인터페이스 프로퍼티는 「이 이름으로 읽을 수 있어야 한다」까지만 정하고, 값을 담는 일은 언제나 구현 클래스가 합니다.

4. 기본 구현이 둘 겹칠 때 — super

클래스 하나가 인터페이스 여럿을 구현할 수 있다 보니, 두 인터페이스에 같은 이름의 기본 구현이 들어 있을 수 있습니다. 이 절에서는 걷는 쪽 Walker 와 헤엄치는 쪽 Swimmer 에 둘 다 move() 를 두고, 둘을 함께 구현하는 Duck 을 만듭니다.

Kotlin
interface Walker {
    fun move(): String = "walk"
}

interface Swimmer {
    fun move(): String = "swim"
}

class Duck : Walker, Swimmer
터미널
$ kotlinc Diamond.kt -d out
Diamond.kt:9:1: error: class 'Duck' must override 'move' because it inherits multiple interface methods for it.
class Duck : Walker, Swimmer
^^^^^^^^^^

Duck 이 move() 를 부르면 "walk" 와 "swim" 가운데 무엇을 돌려줘야 할까요. 컴파일러도 모릅니다.

flowchart TD
    D["Duck().move()"] --> Q{"어느 쪽 본문?"}
    Q --> W["Walker.move() = walk"]
    Q --> S["Swimmer.move() = swim"]

move 를 여럿 물려받으니 Duck 이 직접 override 하라는 에러입니다. 컴파일러는 둘 중 하나를 골라 주지 않습니다.

자바도 같습니다. 두 인터페이스에 default String move() 를 하나씩 두고 class Duck implements Walker, Swimmer 로 비워 두면, javac 가 error: types Walker and Swimmer are incompatible; 를 내고 다음 줄에 class Duck inherits unrelated defaults for move() from types Walker and Swimmer 를 붙입니다. 규칙은 같고 에러 문구만 다릅니다.

직접 쓰되, 물려받은 본문은 골라 부른다

override 를 쓰면서 한쪽이나 양쪽 본문을 다시 부를 수 있습니다. 그때 어느 인터페이스의 것인지를 super<이름> 으로 적습니다.

Kotlin
class Duck : Walker, Swimmer {
    override fun move(): String =
        super<Walker>.move() + " + " + super<Swimmer>.move()
}

class Penguin : Walker, Swimmer {
    override fun move(): String = super<Swimmer>.move()
}

fun main() {
    println(Duck().move())    // walk + swim
    println(Penguin().move()) // swim
}
  • super<Walker>.move() — Walker 에 적힌 move() 본문을 부릅니다. 자바의 Walker.super.move() 에 해당합니다
  • Duck() — 객체 생성입니다. 코틀린에는 new 가 없습니다
터미널
$ kotlinc Duck.kt -d out
$ kotlin -cp out DuckKt

Duck 은 둘을 이어 붙였고, Penguin 은 Swimmer 쪽만 골랐습니다. 꺾쇠 안의 이름이 곧 「어느 부모의 것인가」라서, 부모가 하나뿐이면 super.move() 로 줄여 씁니다.

정리하면, 같은 이름의 기본 구현을 둘 물려받으면 코틀린도 자바도 직접 override 하라고 막고, 코틀린은 super<A>.move() 로 원하는 쪽을 골라 부릅니다.

5. 추상 클래스와 언제 갈리나

인터페이스가 프로퍼티와 기본 구현까지 담으니, 추상 클래스와 무엇이 다른지가 궁금해집니다. 추상 클래스는 abstract class 로 선언하고 직접 객체를 못 만드는 클래스입니다. 이 절에서는 인터페이스에서 막히는 것 셋을 한 파일에 모아 컴파일했습니다.

Kotlin
interface Repo(val url: String)

interface Cache {
    protected fun evict()
    private fun log(msg: String) = println(msg)
}

open class Base
open class Other

class Both : Base(), Other()
  • interface Repo(val url: String) — 인터페이스에 생성자를 달아 봤습니다
  • protected — 하위 클래스에서만 보이게 하는 수식어입니다. private 은 선언한 곳 안에서만 보입니다
  • open class — 상속을 허용한 클래스입니다. 코틀린 클래스는 기본이 final 이라 붙여야 물려받을 수 있습니다
터미널
$ kotlinc Limits.kt -d out
Limits.kt:1:15: error: interfaces cannot have constructors.
interface Repo(val url: String)
              ^^^^^^^^^^^^^^^^^
Limits.kt:4:5: error: modifier 'protected' is not applicable inside 'interface'.
    protected fun evict()
    ^^^^^^^^^
Limits.kt:11:22: error: only one class can appear in a supertype list.
class Both : Base(), Other()
                     ^^^^^

에러는 셋이고, 막힌 것은 생성자, protected, 클래스 둘을 물려받기입니다. 5번째 줄의 private fun log 는 에러가 없습니다. 인터페이스 안에 본문 있는 private 함수는 둘 수 있습니다. 기본 구현끼리 나눠 쓰는 도우미 함수를 숨길 때 씁니다.

3절과 이 절의 결과를 추상 클래스와 맞대면 이렇습니다.

인터페이스 추상 클래스
한 클래스가 몇 개까지 물려받나 여럿 하나
생성자 ✗ interfaces cannot have constructors ✓
값을 담는 프로퍼티(백킹 필드) ✗ 구현 쪽이 담는다 ✓
본문 있는 함수 ✓ ✓
protected 멤버 ✗ ✓
private 멤버 ✓ 본문이 있어야 한다 ✓
수식어 없이 쓴 멤버 오버라이드할 수 있다 본문이 있으면 final

마지막 줄은 KB 14 상속 — 클래스는 기본이 final 이다의 기본 final 규칙과 이어집니다. 추상 클래스에서 본문 있는 함수를 하위 클래스가 바꾸게 하려면 open 을 붙여야 하지만, 인터페이스 멤버는 처음부터 열려 있습니다(코틀린 문서).

고르는 기준은 표의 위 세 줄에서 나옵니다. 객체마다 상태를 들고 있어야 하거나 생성자로 받을 것이 있으면 추상 클래스, 「이런 일을 할 수 있다」는 약속만 필요하거나 여러 개를 섞어 붙여야 하면 인터페이스입니다. 스프링에서 서비스 계약을 인터페이스로 두는 것처럼, 자바에서 쓰던 기준이 그대로 통합니다.

둘을 같이 쓰는 모양도 흔합니다. 약속은 인터페이스에 두고, 여러 구현이 공유할 상태와 생성자는 그 인터페이스를 구현한 추상 클래스에 둡니다.

정리하면, 인터페이스와 추상 클래스는 「본문을 담을 수 있나」로는 더 이상 갈리지 않고, 상태와 생성자를 가질 수 있나, 여럿을 물려받을 수 있나로 갈립니다.

6. 성능 — 인터페이스로 부르면 느린가

인터페이스 타입 변수로 함수를 부르면, 실행할 때 JVM(자바 가상 머신)이 그 객체의 실제 클래스를 보고 어느 구현을 부를지 찾아야 합니다. 2절의 for (g in gs) 에서 g.shout() 이 Plain 이냐 Polite 냐에 따라 갈린 것이 그 일입니다. 그래서 「인터페이스 호출은 느리다」는 말이 나옵니다.

실제로는 이 비용의 대부분을 JIT 컴파일러가 지웁니다. JIT 컴파일러는 자주 도는 코드를 실행 중에 기계어로 옮기는 JVM 부품입니다. OpenJDK 문서는 오버라이드할 수 있는 메서드의 호출(가상 호출)과 인터페이스 호출을 같은 무리로 묶어 이렇게 적습니다(HotSpot Performance Techniques).

호출 지점에서 본 실제 클래스 JIT 가 하는 일
로드된 구현이 하나뿐 일반 호출로 바꾼다
거의 늘 같은 한두 클래스 그 클래스인지 확인하고, 맞으면 본문을 그 대목에 펼쳐 넣는다(인라인)
여러 클래스가 섞인다 JVM 이 준비한 표를 거쳐 부른다. 위 두 줄의 지름길을 못 탄다

눈여겨볼 것은 이 표가 인터페이스냐 추상 클래스냐로 갈리지 않는다는 점입니다. open 클래스의 함수를 부를 때도 같은 규칙입니다. 갈리는 것은 한 호출 지점에 실제 클래스가 몇 개 섞이느냐입니다.

신경 쓸 때는 좁습니다. 아주 짧은 함수를 뜨거운 반복문 안에서 수백만 번 부르는데, 그 한 줄에 구현 클래스가 여럿 섞여 들어오는 경우입니다. 그 밖에는 인터페이스를 둘지 말지를 성능으로 고르지 말고 5절의 설계 기준으로 고르면 됩니다. 이 편에서는 따로 시간을 재지 않았습니다.

7. 한 장 요약

코틀린 인터페이스에는 추상 함수, 본문 있는 함수(기본 구현), 프로퍼티를 적는다. 구현 쪽은 override 를 반드시 붙이고, 빠진 멤버와 빠진 override 는 둘 다 컴파일 에러다. 인터페이스 프로퍼티는 백킹 필드가 없어서 값을 못 담는다 — 초깃값도 field 도 에러이고, 값은 구현 클래스가 생성자 인자나 = 값 으로 담거나 게터로 매번 계산한다. 같은 이름의 기본 구현을 둘 물려받으면 직접 override 하고 super<A> 로 골라 부른다. 추상 클래스와는 상태·생성자·여럿 물려받기로 갈린다.

주제 자바 코틀린 확인한 방법
구현 선언 implements Named : Named (괄호 없음) 컴파일
오버라이드 표시 @Override 는 선택 override 가 빠지면 needs an 'override' modifier kotlinc 에러
기본 구현 default 메서드 본문만 적는다 실행 출력
자바 클래스가 코틀린 기본 구현을 물려받나 — 물려받는다. greet() 없이 컴파일되고 실행된다 javac · 실행 출력
인터페이스의 값 public static final 상수만 초깃값 ✗ · field ✗ · 커스텀 게터 ✓ javac · kotlinc 에러
override val x = f() 와 get() = f() — 앞은 한 번, 뒤는 읽을 때마다 부른다 실행 출력
기본 구현 두 개가 겹칠 때 unrelated defaults 에러 · A.super.m() must override 에러 · super<A>.m() 양쪽 컴파일 에러 · 실행 출력
생성자 · protected · 클래스 둘 — 전부 에러. private 함수는 된다 kotlinc 에러
호출 비용 JIT 가 대부분 지운다 같다 OpenJDK 문서

관련 항목

인터페이스가 담을 수 있는 멤버

추상 메서드 · 기본 구현 · default 메서드 · 프로퍼티 · 커스텀 게터 · 백킹 필드 · private 메서드

인터페이스와 맞세워지는 상속 수단

추상 클래스 · 다중 상속 · 상속 · open · final · 믹스인

인터페이스를 구현할 때 쓰는 문법

override · @Override · super · 주생성자 · implements

기본 구현이 겹칠 때 생기는 문제

다이아몬드 문제 · 메서드 충돌 · 다중 인터페이스 구현

인터페이스 호출을 JVM 이 처리하는 방식

동적 디스패치 · 가상 메서드 · JIT 컴파일 · 인라인 · 인라인 캐시 · 클래스 계층 분석

코틀린 인터페이스를 자바와 잇는 것

인터페이스 · 자바 상호 운용 · kotlin-stdlib · 게터 · 필드 · 가상 머신

컴파일 결과를 확인하는 도구

kotlinc · javac · 컴파일러 · 컴파일 에러