포스트

Kotlin의 상속 다루기


추상 클래스


Animal이란 추상클래스를 구현한 Cat, Penguin

  • Animal <– Cat, penguin
  • Cat, penguinAnimal을 상속 받는 구조


Animal Class Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class JavaAnimal {
  
  protected final String species;   // 동물의 종류
  protected final int legCount;     // 동물의 다리 갯수

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

  abstract public void move();

  public String getSpecies() {
    return species;
  }

  public int getLegCount() {
    return legCount;
  }

}

move라는 추상 메서드를 가진 JavaAnimal 추상 클래스


Animal Class Kotlin Code

1
2
3
4
5
6
7
8
abstract class Animal (
    protected val species: String,  // 동물의 종류
    protected val legCount: Int,    // 동물의 다리 갯수
){

    abstract fun move()

}


Cat Class


Cat Class Java Code

1
2
3
4
5
6
7
8
9
10
11
12
public class JavaCat extends JavaAnimal {

  public JavaCat(String species) {
    super(species, 4);
  }

  @Override
  public void move() {
    System.out.println("고양이가 사뿐 사뿐 걸어가~");
  }

}


Cat Class Kotlin Code

1
2
3
4
5
6
7
8
9
10
11
12
// Cat Class 를 만듦
// Animal Class 의 생성자(constructor)를 불러 옮

class Cat (
    species: String
) : Animal(species, 4) {   // 상속받는 경우 한칸 띄우고 :을 찍어야 한다.

    // Kotlin 은 override 를 어노테이션이 아니라 지시어를 사용
    override fun move() {
        println("고양이가 움직인다.")
    }
}
  • extends를 사용하지 않고 :을 사용하여 상속
    • :은 항상 한칸 띄우고 작성
  • class를 상속받을 때 상위 클래스의 생성자를 바로 호출
  • override를 필수적으로 붙여야 하고 어노테이션이 아닌 지시어를 사용


Penguin Class


Penguin Class Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class JavaPenguin extends JavaAnimal implements JavaSwimable, JavaFlyable {

  private final int wingCount;

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

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

  @Override
  public int getLegCount() {
    return super.legCount + this.wingCount;
  }

  @Override
  public void act() {
    JavaSwimable.super.act();
    JavaFlyable.super.act();
  }

}
  • 생성자를 생성할 때 wingCountthis를 통해 값을 바로 삽입
  • AnimalgetLegCountOverride하고 wingCount를 더해서 반환


Penguin Class Kotlin Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Penguin (
    species: String
) : Animal(species, 2) {

    private val wingCount: Int = 2

    override fun move() {
        println("펭권이 움직입니다.")
    }

    // customGetter 사용
    override val legCount: Int
        get() = super.legCount + this.wingCount
}
  • Animalprotected val legCount: Int
    • protected open val legCount: Int처럼 open을 붙여 줘야 getter를 사용 가능
  • override키워드와 customGetter를 사용
  • Java와 같이 상위 클래스에 접근하는 키워드는 super


Java와 Kotlin의 공통점

  • 추상 클래스는 인스턴스화 할 수 없다.




인터페이스


Flyable과 Swimable을 구현한 Penguin

  • Flyable,Swimable(인터페이스) –> Penguin
    • PenguinFlyable,Swimable을 구현


Swimable interface

1
2
3
4
5
6
interface Swimable {

    fun act(){
        println("어푸어푸")
    }
}

Flyable interface

1
2
3
4
5
6
7
8
9
interface Flyable {

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

    // 추상 메서드의 경우
    fun fly()
}


인터페이스를 구현한 Penguin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Penguin (
    species: String
) : Animal(species, 2), Swimable, Flyable {

    private val wingCount: Int = 2

    override fun move() {
        println("펭권이 움직입니다.")
    }

    // customGetter 사용
    override val legCount: Int
        get() = super.legCount + this.wingCount

    override fun act() {
        super<Swimable>.act()
        super<Flyable>.act()
    }

    // 추상 메서드 구현
    override fun fly() {
        println("날 수 있다.")
    }
}
  • interface를 상속할 경우에도 :을 사용하기 때문에 Animal옆에 ,로 추가
  • super<interface>.methode()를 사용하여 호출
  • override를 추상 메서드를 구현할 수 있다.


Java, Kotlin의 인터페이스 공통점

  • 인터페이스를 인스턴스화 할 수 없다.
  • Kotlinbacking field가 없는 프로퍼티를 interface에 만들 수 있다.


ex). swimAbility을 변수를 만들고 Swimable을 구현하고 있는 Penguin에서 swimAbility구현

1
2
3
4
5
6
7
8
9
10
11
interface Swimable {

    // customGetter 를 이용해서 상속하고 있는 class 에서 만들어 줘야 함
    val swimAbility: Int

    fun act(){
        // Penguin 에서 customGetter 로 구현한 swimAbility 를 사용 가능
        println("swimAbility = $swimAbility")
        println("어푸어푸")
    }
}

Penguin에서 customGetter로 구현 or interface에서 직접 구현해도 된다.

  • interface에서 직접 구현하는 경우 default값이 된다.
    1
    2
    3
    4
    5
    6
    7
    8
    
      // Penguin에서 구현
      override val swimAbility: Int
          get() = 1
    
      // Swimable에서 직접 default 값으로 구현 가능
      val swimAbility: Int
          get() = 1
    




클래스를 상속할 때 주의할 점


Base Class와 Derived Class

  • Derived ClassBase Class를 상속 받고 있다.
  • Base Class를 다른 Class가 상속받을 수 있게 open해 준다.


Base Class

1
2
3
4
5
6
7
8
9
open class Base (
    open val number: Int = 100
){
    // 초기화
    init {
        println("Base Class")
        println("number = $number")
    }
}

Derived Class

1
2
3
4
5
6
7
8
9
class Derived(
    override val number: Int
) : Base(number){

    // 초기화
    init {
        println("Derived Class")
    }
}

출력

1
2
3
fun main() {
    Derived(200)
}
  • Base Class
    number = 0
    Derived Class

0이 출력되는 이유

  • 인텔리제이의 경고 :Accessing non-final property number in constructor
    • 상위 클래스의 생성자가 실행되는 동안 하위 클래스(Derived)의 프로퍼티에 값을 넣었다는 의미
    • 즉, 상위 클래스의 number를 호출하면 하위 클래스의 number를 가져온다.
    • 그런데 아직 상위 클래스의 생성자가 실행되는 단계라서 하위 클래스의 number의 값이 초기화되지 않음
    • 이 상태에서 하위 클래스의 number에 접근하여 Int의 기초 값인 0이 출력됨
  • 그래서 상위 클래스의 생성자 및 초기화 블럭에서는 하위 클래스의 필드에 접근하면 안된다.
  • 정확히는 final이 아닌 프로퍼티에는 접근하면 안된다.

상위 클래스를 설계할 경우

  • 생성자 및 초기화 블럭에 사용되는 프로퍼티는 open사용을 피해야 한다.


상속 관련 키워드 4가지 정리

  1. final : override를 할 수 없게 한다. default로 보이지 않는 값으로 존재 (기본 값이 final이라는 뜻)
  2. open : override를 열어 준다.
  3. abstract : 반드시 override해야 한다.
  4. override : 상위 타입을 override하고 있다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.