[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()
}
정리
핵심 요약
- 클래스: 주 생성자, init 블록
- 상속: open, override
- 인터페이스: 다중 구현 가능
- 데이터 클래스: equals, hashCode, copy 자동
- Sealed 클래스: 제한된 상속
- Object: 싱글톤, Companion Object