1. 추상 클래스
추상 클래스(abstract class)는 상속을 통해 공통된 동작을 수행하도록 설계된 클래스입니다. 추상클래스는 객체로 직접 생성될 수 없으며 상속 받는 하위 클래스에서 구현해야할 추상 멤버(구현이 없는 메서드, 초기화되지 않은 프로퍼티)를 포함할 수 있습니다.
abstract class WithProperty {
abstract val x: Int
}
abstract class WithFunctions {
abstract fun f(): Int
abstract fun g(n: Double)
}
코틀린에서 추상클래스는 abstract 키워드를 사용해서 정의합니다.
코틀린에서는 초기화 코드가 없으면 해당 참조를 abstract로 선언해야하며 참조가 속한 클래스에도 abstract를 붙여야 합니다.
f(), g() 함수 처럼 정의가 없는 경우에도 abstract를 붙여야 합니다.
g() 함수의 경우처럼 함수의 반환 타입을 적지 않으면 코틀린은 반환 타입을 Unit으로 간주합니다.
2. 추상메서드와 인터페이스의 차이
인터페이스에 정의된 함수나 프로퍼티는 모두 기본적으로 추상 멤버로 추상클래스와 비슷한 면이 있습니다. 하지만 둘의 큰 차이점이 존재하는데. 추상클래스에서는 상태가 있지만 인터페이스에서는 상태가 없다는 점입니다.
상태 => 프로퍼티 안에 저장된 데이터
인터페이스도 프로퍼티를 선언할 수 있지만, 해당 데이터는 인터페이스를 구현하는 클래스에서만 저장될 수 있으며, 인터페이스 안에서 프로퍼티 값을 초기화하는 것이 불가능한 것을 볼 수 있습니다.
+ 추상클래스가 더 강력한 추상화를 보장하는데 인터페이스가 필요한 이유?
자바의 초기 설계자들은 다중 상속을 좋은 개념으로 보지 않았다
-> 다중상속은 복잡성을 키우고 불만을 유발하는 근원
-> 이 문제를 상태를 포함할 수 없는 인터페이스를 도입해 다중 상태 상속을 금지하는
대신에 다중 인터페이스 상속을 허용
특징 | 추상 클래스 | 인터페이스 |
객체 생성 여부 | 객체 생성 불가 | 객체 생성 불가 |
생성자 | 생성자를 가질 수 있다 | 생성자 가질 수 없다 |
다중 상속 | 단일 상속만 가능 | 다중 구현 가능 |
멤버 | 상태와 구현된 메서드 모두 포함 가능 | 구현된 메서드만 포함 가능 |
3. 업캐스트
업캐스트는 객체 참조를 받아서 그 객체의 기반 타입에 대한 참조처럼 취급하는 것을 의미합니다. 코틀린에서도 업캐스트를 통해 하위 클래스 객체를 상위타입으로 참조할 수 있으며 이를 통해 코드 유연성을 높일 수 있습니다.
3-1. 업캐스트 동작
interface Shape {
fun draw(): String
fun erase(): String
}
class Circle : Shape {
override fun draw() = "Circle.draw"
override fun erase() = "Circle.erase"
fun color() = "Circle.color"
}
class Square : Shape {
override fun draw() = "Square.draw"
override fun erase() = "Square.erase"
}
fun show(shape: Shape) {
println("만들어진 도형은 ${shape.draw()} 입니다.")
}
fun main() {
listOf(Circle(), Square())
.forEach(::show)
}
만들어진 도형은 Circle.draw 입니다.
만들어진 도형은 Square.draw 입니다.
1. Circle과 Square은 Shape를 상속받아 기반 클래스의 메서드를 구현하거나 재정의합니다.
2. Circle과 Square은 모두 Shape를 상속하므로 Show() 함수의 파라미터 타입인 Shape로 참조될 수 있습니다. 이를 일컬어 구체적인 타입이 기반 타입으로 업캐스트 되었다라고 합니다.
=> Shape 타입 인자로 전달할 때 각 객체의 구체 타입이 상속 계층 위에 있는 타입으로 변환된다. 업캐스트가 이뤄지면서 각 객체가 Circle, Square중 어느 타입인지에 대한 구체적 정보는 사라지고 Shape 객체로 취급된다.
2. 고유 멤버의 접근 제한
class Circle : Shape {
override fun draw() = "Circle.draw"
override fun erase() = "Circle.erase"
fun color() = "Circle.color"
}
fun main() {
val shape: Shape = Circle()
shape.color() //컴파일 에러
}
Circle클래스에만 정의된 color()는 기반 클래스인 Shape 인터페이스에 속해있지 않으므로 호출이 불가능합니다.
하위 클래스의 고유 멤버는 업캐스트 후 상위 클래스 타입으로 호출이 불가능하다.
하위 클래스의 멤버를 사용하려면 명시적으로 다운캐스트해서 참조 타입을 변경해줘야 합니다.
fun main() {
val shape: Shape = Circle()
if(shape is Circle) { //타입 확인 후 다운캐스트
shape.color()
}
}

'Kotlin 정리' 카테고리의 다른 글
[Kotlin 공부 10일차] 상속, 기반 클래스 초기화 (0) | 2024.12.20 |
---|---|
[Kotlin 공부 9일차] 인터페이스 (0) | 2024.12.17 |
[Kotlin 공부 8일차] 널이 될 수 있는 타입 (0) | 2024.12.12 |
[Kotlin 공부 7일차] 데이터 클래스, 구조 분해 선언 (0) | 2024.12.10 |
[Kotlin 공부 6일차] 오버로딩, when (0) | 2024.12.06 |