[2026] Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스

[2026] Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스

이 글의 핵심

Kotlin 클래스와 객체: 클래스, 상속, 인터페이스. 클래스 기본·프로퍼티.

들어가며

클래스는 객체의 설계도에 해당하고, data class·sealed class 등으로 용도에 맞는 뼈대를 짧게 쓸 수 있습니다. 생성자·프로퍼티 문법이 Java보다 단순한 편입니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. 클래스 기본

클래스 정의

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Person {
    var name: String = ""
    var age: Int = 0
    
    fun introduce() {
        println("안녕하세요, $name입니다. ${age}세입니다.")
    }
}
val person = Person()
person.name = "홍길동"
person.age = 25
person.introduce()

주 생성자

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Person(val name: String, var age: Int) {
    fun introduce() {
        println("안녕하세요, $name입니다. ${age}세입니다.")
    }
}
val person = Person("홍길동", 25)

init 블록

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Person(val name: String, var age: Int) {
    init {
        println("Person 객체 생성: $name")
        require(age >= 0) { "나이는 0 이상이어야 합니다" }
    }
}

부 생성자

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Person(val name: String) {
    var age: Int = 0
    
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}
val person1 = Person("홍길동")
val person2 = Person("김철수", 30)

2. 프로퍼티

getter/setter

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Person(val name: String) {
    var age: Int = 0
        get() = field
        set(value) {
            if (value >= 0) {
                field = value
            }
        }
    
    val isAdult: Boolean
        get() = age >= 18
}

지연 초기화

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class MyClass {
    lateinit var name: String
    
    fun init() {
        name = "홍길동"
    }
    
    fun isInitialized() = ::name.isInitialized
}

3. 상속

open 클래스

Kotlin 클래스는 기본적으로 final이므로 상속하려면 open 키워드가 필요합니다: 다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// open: 상속 가능한 클래스
// Kotlin은 기본적으로 모든 클래스가 final (상속 불가)
// 상속을 허용하려면 명시적으로 open 선언
open class Animal(val name: String) {
    // open: 오버라이딩 가능한 메서드
    // 메서드도 기본적으로 final
    // 자식 클래스에서 재정의하려면 open 필요
    open fun makeSound() {
        println("동물 소리")
    }
    
    // open이 없는 메서드는 final (오버라이딩 불가)
    fun sleep() {
        println("$name이(가) 잠을 잡니다.")
    }
}
// Dog 클래스: Animal을 상속
class Dog(name: String) : Animal(name) {
    // : Animal(name) : 부모 생성자 호출
    // name을 부모 클래스에 전달
    
    // override: 부모 메서드 재정의
    override fun makeSound() {
        println("멍멍!")
    }
    
    // Dog만의 메서드
    fun fetch() {
        println("$name이(가) 공을 가져옵니다.")
    }
}
// Cat 클래스: Animal을 상속
class Cat(name: String) : Animal(name) {
    override fun makeSound() {
        println("야옹!")
    }
    
    fun scratch() {
        println("$name이(가) 할퀴기를 합니다.")
    }
}
// 사용 예제
fun main() {
    val dog = Dog("바둑이")
    dog.makeSound()  // 멍멍! (오버라이딩)
    dog.sleep()      // 바둑이이(가) 잠을 잡니다. (상속)
    dog.fetch()      // 바둑이이(가) 공을 가져옵니다. (Dog 고유)
    
    val cat = Cat("나비")
    cat.makeSound()  // 야옹! (오버라이딩)
    cat.sleep()      // 나비이(가) 잠을 잡니다. (상속)
    cat.scratch()    // 나비이(가) 할퀴기를 합니다. (Cat 고유)
    
    // 다형성
    val animals: List<Animal> = listOf(
        Dog("멍멍이"),
        Cat("야옹이")
    )
    
    for (animal in animals) {
        animal.makeSound()  // 각 객체의 실제 타입에 따라 호출
        // 멍멍!
        // 야옹!
    }
}

open vs final 비교: 다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ final 클래스 (기본)
class FinalClass {
    fun method() {}
}
// class SubClass : FinalClass()  // 컴파일 에러!
// This type is final, so it cannot be inherited from
// ✅ open 클래스
open class OpenClass {
    open fun method() {}
}
class SubClass : OpenClass() {
    override fun method() {}  // OK
}

왜 기본이 final인가:

  • 안전성: 의도하지 않은 상속 방지
  • 성능: final 메서드는 최적화 가능
  • 명확성: 상속 가능 여부가 명시적

abstract 클래스

다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

abstract class Shape {
    abstract fun area(): Double
    abstract fun perimeter(): Double
    
    fun describe() {
        println("넓이: ${area()}, 둘레: ${perimeter()}")
    }
}
class Circle(val radius: Double) : Shape() {
    override fun area() = Math.PI * radius * radius
    override fun perimeter() = 2 * Math.PI * radius
}
class Rectangle(val width: Double, val height: Double) : Shape() {
    override fun area() = width * height
    override fun perimeter() = 2 * (width + height)
}

4. 인터페이스

기본 인터페이스

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

interface Drawable {
    fun draw()
    fun erase() {
        println("지우기")  // 기본 구현
    }
}
class Circle : Drawable {
    override fun draw() {
        println("원 그리기")
    }
}

다중 인터페이스

다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

interface Clickable {
    fun click()
}
interface Focusable {
    fun focus()
}
class Button : Clickable, Focusable {
    override fun click() {
        println("버튼 클릭")
    }
    
    override fun focus() {
        println("버튼 포커스")
    }
}

5. 데이터 클래스

기본 사용

아래 코드는 kotlin를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

data class User(
    val name: String,
    val age: Int,
    val email: String
)
val user1 = User("홍길동", 25, "hong@example.com")
val user2 = User("홍길동", 25, "hong@example.com")
println(user1 == user2)  // true (equals 자동 생성)
println(user1)  // User(name=홍길동, age=25, email=hong@example.com)

copy 메서드

아래 코드는 kotlin를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

val user1 = User("홍길동", 25, "hong@example.com")
val user2 = user1.copy(age = 26)
println(user1)  // age=25
println(user2)  // age=26

구조 분해

val user = User("홍길동", 25, "hong@example.com")
val (name, age, email) = user
println("이름: $name, 나이: $age")

6. Sealed 클래스

아래 코드는 kotlin를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val message: String) : Result()
    object Loading : Result()
}
fun handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("성공: ${result.data}")
        is Result.Error -> println("에러: ${result.message}")
        is Result.Loading -> println("로딩 중...")
    }
}

7. Object 선언

싱글톤

다음은 kotlin를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

object Database {
    private var connection: String? = null
    
    fun connect() {
        connection = "Connected"
        println("데이터베이스 연결됨")
    }
    
    fun disconnect() {
        connection = null
        println("데이터베이스 연결 해제됨")
    }
}
Database.connect()

Companion Object

아래 코드는 kotlin를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class User(val name: String) {
    companion object {
        const val MAX_AGE = 150
        
        fun create(name: String): User {
            return User(name)
        }
    }
}
val user = User.create("홍길동")
println(User.MAX_AGE)

8. 실전 예제

예제: 쇼핑 시스템

다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

data class Product(
    val id: String,
    val name: String,
    val price: Int
)
data class CartItem(
    val product: Product,
    var quantity: Int
)
class ShoppingCart {
    private val items = mutableListOf<CartItem>()
    
    fun addItem(product: Product, quantity: Int = 1) {
        val existing = items.find { it.product.id == product.id }
        if (existing != null) {
            existing.quantity += quantity
        } else {
            items.add(CartItem(product, quantity))
        }
    }
    
    fun removeItem(productId: String) {
        items.removeIf { it.product.id == productId }
    }
    
    fun getTotalPrice(): Int {
        return items.sumOf { it.product.price * it.quantity }
    }
    
    fun printCart() {
        println("=== 장바구니 ===")
        items.forEach {
            println("${it.product.name} x${it.quantity} = ${it.product.price * it.quantity}원")
        }
        println("총액: ${getTotalPrice()}원")
    }
}
fun main() {
    val cart = ShoppingCart()
    
    cart.addItem(Product("P001", "노트북", 1000000))
    cart.addItem(Product("P002", "마우스", 30000), 2)
    
    cart.printCart()
}

정리

핵심 요약

  1. 클래스: 주 생성자, init 블록
  2. 상속: open, override
  3. 인터페이스: 다중 구현 가능
  4. 데이터 클래스: equals, hashCode, copy 자동
  5. Sealed 클래스: 제한된 상속
  6. Object: 싱글톤, Companion Object

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3