Home Kotlin - 상속
Post
Cancel

Kotlin - 상속

  • 추상 클래스
  • 인터페이스
  • 클래스 상속 시 주의할 부분
  • 상속 관련 지시어

상속

추상 클래스

Animal이라는 추상 클래스를 구현한 Cat, Penguin 클래스를 만들어보자.

자바 코드와 코틀린 코드를 비교해보자.

  • Animal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Animal {
	protected final String species;
	protected final int legCount;

	public Animal(String species, int legCount) {
		this.species = species;
		this.legCount = legCount;
	}

	abstract public void move();
	
	public String getSpecies() {
		return species;
	}

	public int getLegCount() {
		return legCount;
	}
}
1
2
3
4
5
6
7
abstract class Animal(
	protected val species: String,
	protected val legCount: Int,
) {

	abstract fun move()
}

클래스 자체의 문법을 제외하고 크게 다른 부분은 보이지 않는다.

  • Cat
1
2
3
4
5
6
7
8
9
10
public class Cat extends Animal {
	public Cat(String species) {
		super(species, 4);
	}

	@Override
	public void move() {
		System.out.println("고양이가 걸어다닙니다.");
	}
}
1
2
3
4
5
6
7
8
class Cat(
	species: String
) : Animal(species, 4) {
	
	override fun move() {
		println("고양이가 걸어다닙니다.")
	}
}
  • 주생성자를 통해 생성자를 대체했다.
  • extends 대신 :를 사용한다.
    • species: String과 다르게 컨벤션에 따라 상속을 표현하는 : 사용 시 괄호 옆에 띄어쓰기를 한다.
  • 상속받을 경우 상위 클래스의 생성자를 무조건 바로 호출해주어야 한다.
  • Override 애노테이션 대신 override를 필수적으로 붙여주어야 한다.

그렇다면 wingCount라는 필드가 존재하는 Penguin의 경우 어떨까?

  • Penguin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class Penguin extends Animal {
	private final int wingCount;

	public Penguin(String species) {
		super(species, 2);
		this.wingCount = 2;
	}

	@Override
	public void move() {
		System.out.println("펭귄이 움직인다.");
	}

	@Override
	public int getLegCount() {
		return super.legCount + this.wingCount;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class Penguin(
	species: String,
) : Animal(species, 2) {

	private val wingCount: Int = 2
	
	override fun move() {
		println("펭귄이 움직인다.")
	}

	override val legCount: Int
		get() = super.legCount + this.wingCount
}

코틀린에서 super.legCount를 사용할 때 에러가 발생한다.

상위 클래스의 프로퍼티에서 수정해주어야 한다.

1
2
3
4
class Animal(
	protected val species: String,
	protected open val legCount: Int
)
  • 추상 프로퍼티가 아니라면, 상속 시 open 키워드를 꼭 붙여야 한다.
  • 상위 클래스에 접근하는 키워드는 super로 동일하다.

자바와 코틀린 모두 추상클래스는 인스턴스화 할 수 없다.

인터페이스

Flyable과 Swimmable이라는 인터페이스를 펭귄이 구현하도록 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
public interface Swimmable {
	default void act() {
		System.out.println("어푸 어푸");
	}
}

public interface Flyable {
	default void act() {
		System.out.println("파닥 파닥");
	}
}

코틀린에서는 어떻게 할 수 있을까?

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Swimmable {
	fun act() {
		println("어푸 어푸")
	}
}

interface Flyable {
	fun act() {
		println("파닥 파닥")
	}

	fun fly() //추상 메서드
}

자바와 크게 다를게 없어보인다. defualt 메서드를 구현할 때 default 키워드 없이 구현할 수 있다는 차이 뿐이다. 이러한 인터페이스를 펭귄에서 어떻게 구현할까?

  • Penguin
1
2
3
4
5
6
7
8
public final class Penguin extends Animal implements Flyable, Swimmable {

	@Override
	public void act() {
		Swimmable.super.act();
		Flyable.super.act();
	}
}
1
2
3
4
5
6
7
8
9
class Penguin(
	species: String
) : Animal(species, 2), Swimable, Flyable {
	
	override fun act() {
		super<Swimmable>.act()
		super<Flyable>.act()
	}
}
  • 인터페이스의 상속도 :를 사용한다.
  • 중복되는 인터페이스를 특정할 때 super<>를 사용한다.
  • 인터페이스 역시 자바, 코틀린 모두 인스턴스화 할 수 없다.

코틀린에서는 backing field가 없는 프로퍼티를 인터페이스에 만들 수 있다.

1
2
3
4
5
6
7
8
9
interface Swimmalbe {
	val swimAbility: Int
	//...
}

class Penguin() {
	override val swimAbility: Int
		get() = 3
}

클래스 상속 시 주의할 점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class Base(
	open val number: Int = 100
) {
	init {
		println("Base Class")
		println(number)
	}
}

class Derived(
	override val number: Int
) : Base(number) {
	init {
		println("Derived Class")
	}
}
  • Base 클래스를 다른 클래스가 상속받을 수 있게 open으로 열어주었다.
    • 이 부분 말고 위의 예시들은 추상클래스, 인터페이스이다.
  • number라는 프로퍼티 또한 open으로 열어주었다.
  • 그런데 Derived(300)과 같이 Derived를 인스턴스화 하면 어떤 결과가 나올까?
1
2
3
Base Class
0
Derived Class
  • 왜 100도 300도 아닌 0이 들어갈까?
  • 보면 상위클래스의 init 블록이 먼저 실행되는 것을 볼 수 있다.
  • 그런데 아직 하위 클래스에 number라는 값은 초기화가 이루어 지지 않았는데 출력해야 한다.
  • 이런 상태에서 하위 클래스의 number를 출력하려고 하니 100도 300도 아닌 int의 기초 값인 0이 나오게 되는 것이다.
  • 따라서 상위 클래스의 constructor 및 init 블록에서는 하위 클래스의 프로퍼티에 접근하면 안된다.
    • 정확히는 final이 아닌 프로퍼티에 접근하면 안된다.

상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야 한다.

상속 관련 키워드 정리

  • final
    • override를 할 수 없게 한다.
    • 기본적으로 보이지 않게 존재한다. 따라서 상속받게 하려면 open을 붙여준다.
  • open
    • override를 열어준다.
  • abstract
    • 반드시 override 해야 한다.
  • override
    • 상위 타입을 override 하고 있다.

정리

  • 상속 또는 구현을 할 때 :을 사용한다.
  • 상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다.
  • override를 필수로 붙여야 한다.
  • 추상 멤버가 아니면 기본적으로 override가 불가능하다.
    • open을 사용해주어야 한다.
  • 상위 클래스의 생성자 또는 초기화 블록에서 open 프로퍼티를 사용하면 얘기치 못한 버그가 생길 수 있다.
This post is licensed under CC BY 4.0 by the author.