🌳 상속
📖 상속이란?
**상속(Inheritance)**은 기존 클래스의 기능을 물려받아 새로운 클래스를 만드는 것입니다. 코드 재사용과 확장에 유용합니다.
💡 기본 상속
open 키워드
Kotlin 클래스는 기본적으로 final입니다. 상속하려면 open 키워드 필요!
// open - 상속 가능
open class Animal(val name: String) {
open fun sound() {
println("동물 소리")
}
}
// : 로 상속
class Dog(name: String) : Animal(name) {
override fun sound() {
println("멍멍!")
}
}
fun main() {
val dog = Dog("바둑이")
println(dog.name) // 바둑이
dog.sound() // 멍멍!
}
상속 규칙
open class Animal(val name: String)
// ✅ open 클래스는 상속 가능
class Dog(name: String) : Animal(name)
// ❌ 일반 클래스는 상속 불가
class Cat(val name: String) // final (기본값)
// class Kitten : Cat("야옹이") // 오류!
🔧 메서드 오버라이딩
기본 오버라이딩
open class Animal(val name: String) {
open fun eat() {
println("$name가 먹습니다")
}
open fun sleep() {
println("$name가 잡니다")
}
}
class Dog(name: String) : Animal(name) {
// 메서드 재정의
override fun eat() {
println("$name가 사료를 먹습니다")
}
// 추가 메서드
fun bark() {
println("멍멍!")
}
}
fun main() {
val dog = Dog("바둑이")
dog.eat() // 바둑이가 사료를 먹습니다
dog.sleep() // 바둑이가 잡니다 (부모 메서드)
dog.bark() // 멍멍!
}
super 키워드
부모 클래스의 메서드 호출:
open class Animal(val name: String) {
open fun eat() {
println("$name가 먹습니다")
}
}
class Dog(name: String) : Animal(name) {
override fun eat() {
super.eat() // 부모 메서드 호출
println("꼬리를 흔듭니다")
}
}
fun main() {
val dog = Dog("바둑이")
dog.eat()
// 바둑이가 먹습니다
// 꼬리를 흔듭니다
}
🎯 프로퍼티 오버라이딩
프로퍼티 재정의
open class Animal {
open val type: String = "동물"
}
class Dog : Animal() {
override val type: String = "개"
}
class Cat : Animal() {
override val type: String = "고양이"
}
fun main() {
val dog = Dog()
val cat = Cat()
println(dog.type) // 개
println(cat.type) // 고양이
}
생성자에서 오버라이드
open class Animal(open val type: String)
class Dog(override val type: String = "개") : Animal(type)
fun main() {
val dog = Dog()
println(dog.type) // 개
}
🎨 추상 클래스
abstract 키워드
// 추상 클래스 (인스턴스 생성 불가)
abstract class Shape {
abstract val name: String
abstract fun area(): Double
// 일반 메서드도 가능
fun describe() {
println("도형: $name, 면적: ${area()}")
}
}
class Circle(val radius: Double) : Shape() {
override val name: String = "원"
override fun area(): Double {
return Math.PI * radius * radius
}
}
class Rectangle(val width: Double, val height: Double) : Shape() {
override val name: String = "직사각형"
override fun area(): Double {
return width * height
}
}
fun main() {
val circle = Circle(5.0)
val rectangle = Rectangle(4.0, 6.0)
circle.describe() // 도형: 원, 면적: 78.54
rectangle.describe() // 도형: 직사각형, 면적: 24.0
}
🎯 실전 예제
동물원
open class Animal(val name: String, val age: Int) {
open fun sound() {
println("...")
}
fun info() {
println("이름: $name, 나이: ${age}살")
}
}
class Dog(name: String, age: Int) : Animal(name, age) {
override fun sound() {
println("멍멍!")
}
}
class Cat(name: String, age: Int) : Animal(name, age) {
override fun sound() {
println("야옹~")
}
}
class Bird(name: String, age: Int) : Animal(name, age) {
override fun sound() {
println("짹짹!")
}
}
fun main() {
val animals: List<Animal> = listOf(
Dog("바둑이", 3),
Cat("나비", 2),
Bird("삐약이", 1)
)
for (animal in animals) {
animal.info()
animal.sound()
println()
}
}
직원 관리
open class Employee(
val name: String,
val baseSalary: Int
) {
open fun calculateSalary(): Int = baseSalary
open fun introduce() {
println("직원: $name")
}
}
class Manager(
name: String,
baseSalary: Int,
private val bonus: Int
) : Employee(name, baseSalary) {
override fun calculateSalary(): Int {
return baseSalary + bonus
}
override fun introduce() {
println("관리자: $name")
}
}
class Developer(
name: String,
baseSalary: Int,
private val skillBonus: Int
) : Employee(name, baseSalary) {
override fun calculateSalary(): Int {
return baseSalary + skillBonus
}
override fun introduce() {
println("개발자: $name")
}
}
fun main() {
val employees = listOf(
Manager("김팀장", 5000000, 1000000),
Developer("이개발", 4000000, 500000),
Employee("박사원", 3000000)
)
for (employee in employees) {
employee.introduce()
println("급여: ${employee.calculateSalary()}원\n")
}
}
게임 캐릭터
abstract class Character(val name: String, var hp: Int) {
abstract fun attack(): Int
open fun takeDamage(damage: Int) {
hp -= damage
println("$name가 ${damage}의 피해를 받았습니다. (HP: $hp)")
if (hp <= 0) {
println("$name가 쓰러졌습니다!")
}
}
}
class Warrior(name: String, hp: Int) : Character(name, hp) {
override fun attack(): Int {
println("$name의 검 공격!")
return 30
}
override fun takeDamage(damage: Int) {
val reducedDamage = damage - 5 // 방어력
super.takeDamage(reducedDamage)
}
}
class Mage(name: String, hp: Int) : Character(name, hp) {
override fun attack(): Int {
println("$name의 마법 공격!")
return 50
}
}
fun main() {
val warrior = Warrior("전사", 100)
val mage = Mage("마법사", 80)
// 전투
val damage1 = mage.attack()
warrior.takeDamage(damage1)
val damage2 = warrior.attack()
mage.takeDamage(damage2)
}
🤔 자주 묻는 질문
Q1. open과 abstract의 차이는?
A: open은 선택, abstract는 필수!
// open - 오버라이드 선택
open class Animal {
open fun sound() {
println("...") // 기본 구현 있음
}
}
class Dog : Animal() {
// 오버라이드 선택 (안 해도 됨)
}
// abstract - 오버라이드 필수
abstract class Shape {
abstract fun area(): Double // 구현 없음
}
class Circle : Shape() {
override fun area(): Double = 10.0 // 필수!
}
Q2. 언제 상속을 쓰나요?
A: "is-a" 관계일 때!
// ✅ Dog은 Animal이다 (is-a)
open class Animal
class Dog : Animal()
// ❌ Car는 Engine이 아니다 (has-a)
class Engine
class Car : Engine() // 잘못된 설계!
// ✅ Car는 Engine을 가진다 (has-a) → 구성
class Car(val engine: Engine)
Q3. 다중 상속은?
A: 클래스는 하나만, 인터페이스는 여러 개!
open class Animal
open class Robot
// ❌ 다중 상속 불가
// class Cyborg : Animal(), Robot()
// ✅ 인터페이스는 여러 개 가능
interface Flyable
interface Swimmable
class Duck : Animal(), Flyable, Swimmable
🎬 마치며
상속으로 코드를 효율적으로 재사용하세요!
핵심 정리:
✅ open으로 상속 허용
✅ override로 메서드 재정의
✅ super로 부모 메서드 호출
✅ abstract로 추상화
✅ is-a 관계에만 사용
다음 단계: 인터페이스에서 계약을 정의하는 방법을 알아보세요!