Kotlin은 객체 지향 프로그래밍을 위한 풍부한 기능을 제공합니다. 이 포스트에서는 다양한 클래스 유형과 객체에 대해 알아보겠습니다.

클래스 선언

기본 클래스

// 파라미터 없는 경우
class Greeter() {
    fun greet() {
    }
}

// read only, mutable 파라미터
class Greeter(val name: String, var age: Int) {
    fun greet() {
        println("Hello, ${name}")
    }
}

// body가 없는 경우 중괄호 생략 가능
class A(a: Int)

// visibility modifier와 annotation이 있는 생성자
class Customer public @Inject constructor(name: String) { ... }

상속

기본적으로 Kotlin의 모든 클래스는 final입니다. 상속을 허용하려면 open 키워드를 사용해야 합니다.

open class A  // 상속 가능

class B : A() {
    override fun foo(i: Int) { ... }
}

// 파라미터가 있는 클래스 상속
class Rectangle(
    var height: Double,
    var length: Double
) : Shape(listOf(height, length, height, length)) { ... }

추상 클래스

추상 클래스는 open 키워드가 필요 없습니다.

abstract class Shape(val sides: List<Double>) {
    val perimeter: Double get() = sides.sum()
    abstract fun calculateArea(): Double
}

// 비추상 멤버를 추상으로 오버라이드
open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

생성자

// Secondary constructor
class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

// Primary constructor가 있는 경우 delegation 필수
class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

// super 생성자 호출
class MyView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

// private constructor
class C private constructor(a: Int) { ... }

// private property in constructor
class ParameterizedClass<A>(private val value: A) {
    fun getValue(): A = value
}

초기화 블록

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }

    val customerKey = name.toUpperCase()
}

중첩 클래스

// Static nested class (기본)
class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}
val demo = Outer.Nested().foo()  // == 2

// Inner class (외부 클래스 참조 가능)
class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}
val demo = Outer().Inner().foo()  // == 1

메서드 오버라이딩

open class Base {
    open fun v() {}
    fun nv() {}  // final by default
}

class Derived() : Base() {
    override fun v() {}

    // 추가 오버라이드 방지
    final override fun v() {}
}

다중 상속에서의 충돌 해결

인터페이스와 클래스에서 같은 시그니처의 메서드가 있을 경우:

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") }  // interface members are 'open' by default
    fun b() { print("b") }
}

class C() : A(), B {
    // 컴파일러가 f() 오버라이드를 요구함
    override fun f() {
        super<A>.f()  // A.f() 호출
        super<B>.f()  // B.f() 호출
    }
}

프로퍼티 오버라이딩

Kotlin에서는 프로퍼티도 오버라이드할 수 있습니다. valvar로 오버라이드할 수 있지만, 그 반대는 불가능합니다.

open class Foo {
    open val x: Int get() { ... }
}

class Bar1 : Foo() {
    override var x: Int = ...  // val -> var 가능
}

// Primary constructor에서 오버라이드
interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

this와 super 레이블

class A {
    inner class B {
        fun Int.foo() {
            val a = this@A  // A's this
            val b = this@B  // B's this
            val c = this    // foo()'s receiver, an Int
        }
    }
}

class Bar : Foo() {
    override fun f() { /* ... */ }

    inner class Baz {
        fun g() {
            super@Bar.f()  // Foo의 f() 호출
        }
    }
}

인터페이스

선언과 구현

interface RectangleProperties {
    val isSquare: Boolean
}

// 메서드 기본 구현 가능
interface B {
    fun f() { print("B") }
    fun b() { print("b") }
}

// 구현
class Rectangle : Shape(), RectangleProperties {
    override val isSquare: Boolean get() = length == height
}

Data Class

Data class는 데이터를 보관하기 위한 클래스로, 다음 함수들을 자동 생성합니다:

  • equals() / hashCode()
  • toString() - “User(name=John, age=42)” 형식
  • componentN() 함수들
  • copy() 함수
data class User(val name: String, val age: Int)

// copy 사용
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Destructuring Declarations

componentN() 함수가 있는 클래스에서 사용 가능합니다.

val (name, age) = person
// 아래와 동일
val name = person.component1()
val age = person.component2()

// for 문에서
for ((a, b) in collection) { ... }
for ((key, value) in map) { ... }

// 사용하지 않는 변수
val (_, status) = getResult()

여러 값 반환하기

data class Result(val result: Int, val status: Status)

fun function(...): Result {
    // computations
    return Result(result, status)
}

val (result, status) = function(...)

Sealed Class

Sealed class는 같은 파일 내에서만 상속이 가능합니다. 이를 통해 제한된 클래스 계층을 정의할 수 있습니다.

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

when과 함께 사용

Sealed class의 모든 하위 클래스가 처리되면 else 절이 필요 없습니다.

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // else 절이 필요 없음!
}

Object

Object Expression (익명 클래스)

Java의 anonymous inner class와 유사합니다.

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }
    override fun mouseEntered(e: MouseEvent) { ... }
})

// 여러 인터페이스/클래스 상속
open class A(x: Int) {
    public open val y: Int = x
}

interface B { ... }

val ab: A = object : A(1), B {
    override val y = 15
}

간단한 Object

타입 없이 간단한 객체를 생성할 수 있습니다.

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

Object Declaration (Singleton)

Kotlin에서 싱글톤을 선언하는 가장 쉬운 방법입니다.

object Resource {
    val name = "Name"
}

// 사용
val resourceName = Resource.name
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) { ... }

    val allDataProviders: Collection<DataProvider>
        get() = ...
}

DataProviderManager.registerDataProvider(...)

Companion Object

클래스 내부에 static 멤버를 정의하는 방법입니다.

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()

// 이름 생략 가능
class MyClass {
    companion object { }
}

val x = MyClass.Companion

Companion Object와 인터페이스

Companion object도 인터페이스를 구현할 수 있습니다.

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

표준 라이브러리의 데이터 클래스

Kotlin 표준 라이브러리에서 제공하는 데이터 클래스들:

Pair  // 두 값을 담는 클래스
Triple  // 세 값을 담는 클래스

val pair = Pair("key", "value")
val (key, value) = pair

다음 단계

클래스와 객체에 대해 알아보았습니다. 다음으로 함수와 람다에 대해 알아보세요.