상속(Inheritance)이란?
상속이 뭘까요?
가장 먼저 떠오르는 것은, 부모로부터 돈 혹은 집안 가보 등 무엇인가 '물려받는 행위'가 떠오릅니다.
객체지향에서 말하는 상속은 일상 생활의 상속과 비슷하나 약간 다릅니다.
객체지향에서 상속이란, 클래스가 다른 클래스의 요소를 상속받는 것을 의미합니다.
사실 저는 이게 상속이라기 보단 확장에 가깝다고 생각합니다.
기반이 되는 클래스를 부모 클래스, 상위 클래스,
상속을 받는 클래스를 자식 클래스, 하위 클래스, 서브 클래스라 보통 이야기합니다.
부모 클래스를 A, 자식 클래스를 B라 할 때,
B는 A다 라는 관계가 성립해야 합니다.
아빠가 할아버지로부터 유산을 물려받았습니다.
그럼 할아버지는 아빠일까요? 조금 어색한 느낌이 듭니다.
하지만 확장으로 생각한다면,
- 동물 -> 개
- 동물 -> 고양이
- 동물 -> 새
기반 클래스가 되는 동물. 하위 클래스가 되는 개, 고양이, 새의 관계를 보면
개는 동물이다. 고양이는 동물이다. 새는 동물이다. B는 A다. 관계가 성립됩니다.
즉, 일반적으로 이야기하는 상속이라는 단어보다는, 확장에 가깝다는 생각이 듭니다.
자바의 상속 키워드가 extends 인 것을 생각하면, 아주 적절한 표현이라 생각됩니다.
정말 감탄을 숨길수가 없었어요.
이러한 객체지향의 상속 특성은 클래스의 요소를 물려받음으로써,
코드를 재사용하여 중복을 제거합니다.
이는 생산성을 높이고, 유지보수를 원활하게 합니다.
코틀린과 상속
open class Parent {
var a = 10
fun say() {
println(a)
}
}
class Child : Parent() // 상속받음
fun main() {
val child = Child()
child.say()
}
// 출력
>>> 10
코틀린에서 상속은 앞에 open 키워드를 붙여 상속가능한 클래스로 만들 수 있습니다.
즉, 별도로 명시하지 않으면 기본적으로 final 키워드가 붙는다고 볼 수 있죠.
하위클래스에서 extends 대신 ':' 키워드를 사용해 상속을 받을 수 있습니다.
상속을 사용하면 위 코드와 같이 상위 클래스의 멤버변수(프로퍼티)와 메소드 등을 사용할 수 있습니다.
코드의 재사용이 가능해지는 것이죠.
상속과 오버라이드 (Override)
// open을 붙여 상속가능한 클래스로 지정
open class Base {
// open을 붙여 오버라이딩 가능한 메소드로 지정
open fun attack() {
println("Base attack")
}
}
open class Warrior : Base() { // : 키워드로 상속
override fun attack() { // override 키워드로 메소드 오버라이딩
println("Warrior attack")
}
}
open class Mage : Base() {
override fun attack() {
println("Mage attack")
}
}
open class Archer : Base() {
override fun attack() {
println("Archer attack")
}
}
상속을 배울 때 무조건 한 번쯤은 나오는 예제, 하지만 아주 좋은 예제인 게임 캐릭터 전직입니다.
초등학교 시절, 메이플을 하던 기억을 떠올리며 코드를 끄적여 봤습니다.
오버라이드(Override)는 부모 클래스에서 정의한 멤버변수(프로퍼티)와 메소드를 재정의하는 행위입니다.
메소드를 오버라이딩 할 땐 앞에 override 키워드를 붙여 오버라이딩을 할 수 있습니다.
자바에서 @Override 어노테이션을 붙인 것과 비슷하나,
선택이였던 자바의 어노테이션에 비해 코틀린에서 override를 붙이는 것은 필수입니다.
open class Base {
open fun attack() {
println("Base attack")
}
}
open class Warrior : Base() {
override fun attack() {
println("Warrior attack")
}
}
open class Mage : Base() {
override fun attack() {
println("Mage attack")
}
}
open class Archer : Base() {
override fun attack() {
println("Archer attack")
}
}
fun main() {
val warrior = Warrior()
val mage = Mage()
val archer = Archer()
warrior.attack()
mage.attack()
archer.attack()
}
// 출력
>> Warrior attack
Mage attack
Archer attack
베이직 캐릭터로부터 상속받은 각각의 직업들이 공격을 한 모습입니다.
상위 클래스 접근. Super
open class Base {
open fun attack() {
println("Base attack")
}
}
open class Warrior : Base() {
override fun attack() {
super.attack() // 상위 클래스의 attack 메소드 접근
println("Warrior attack")
}
}
fun main() {
val warrior = Warrior()
warrior.attack()
}
// 출력
>> Base attack
Warrior attack
코틀린에서는 자바와 마찬가지로 super 키워드를 사용해
상위 클래스의 메소드를 실행시킬 수 있습니다.
warrior의 attack 뿐만 아닌, 부모 클래스인 base의 attack도 같이 나간 모습입니다.
명시적 캐스팅 as
open class Parent {
var a = 10
fun say() {
println(a)
}
}
class Child : Parent()
fun main() {
val child = Child() as Parent
val child = Child() as? Parent // null을 허용하는 객체의 캐스팅
}
코틀린에서는 as 키워드를 사용해 명시적으로 캐스팅을 할 수 있습니다
이는 자바에서 Parent child = new Child(); 와 비슷하게 쓰입니다.
물론 코틀린에서도 val child: Parent = Child() 과 같이 사용할 수도 있습니다
null을 허용할 수 있는 객체로 만들고 싶을 땐, as? 키워드를 사용합니다.
부록. 최상위 클래스 Any
class MyClass: Any // 모든 클래스는 Any를 상속받음
val a:Any = 3 // Int 역시 코틀린에서는 객체이기 때문에 Any를 상속받음
모든 클래스는 Any를 상속받습니다.
코틀린의 슈퍼 클래스이며
코틀린의 모든 클래스들의 부모 클래스입니다.
자바의 Object와 비슷하나, 엄밀히 말하면 다릅니다.
'코틀린 파헤치기 > 2부. 코틀린 객체지향' 카테고리의 다른 글
[코파기 2부] 6. 코틀린과 늦은 초기화 : lazy, lateinit (0) | 2023.03.15 |
---|---|
[코파기 2부] 5. 코틀린과 추상 클래스와 인터페이스 (+ 둘의 차이점) (3) | 2023.03.15 |
[코파기 2부] 4. 코틀린과 접근 제한자 (0) | 2023.03.15 |
[코파기 2부] 2. 코틀린과 프로퍼티와 setter/getter. 여기에 data class를 곁들인. (0) | 2023.03.15 |
[코파기 2부] 1. 코틀린과 클래스, 객체, 생성자 (0) | 2023.03.15 |