안드로이드 스터디

코틀린 기본 문법 정리 - 상속, 인터페이스

mky 2025. 11. 1. 02:13

1. 클래스 간 상속

  • ":"을 써서 상속받는다
  • 부모 클래스가 생성자 매개변수를 가지고 있다면, 자식 클래스가 상속할 때 그 매개변수를 반드시 전달해야 한다. 매개변수가 없다면 그냥 () 쓰면 된다.
  • 부모 클래스 쪽에 open 키워드를 앞에 붙여줘야 한다.
// 부모 클래스
open class Animal(val name: String, val age: Int) {
    fun introduce() {
        println("저는 $age살 $name입니다.")
   }
}

그 뒤 자식 클래스를 작성해 보자

// 자식 클래스
class Dog(name: String, age: Int) : Animal(name, age) {
    fun bark() {
        println("멍멍!")
    }
}

이걸 메인함수에서 실행해 보자

fun main() {
    val myDog = Dog("초코", 3)
    myDog.introduce() // 저는 3살 초코입니다.
    myDog.bark()      // 멍멍!
}

이런 식으로 클래스의 상속을 구현한다.

 

 

2. 인터페이스

  • 자바는 implements를 쓰지만 코틀린은 : 을 쓴다
  • 코틀린의 인터페이스 안에는 추상 메서드 뿐 아니라 구현이 있는 메서드도 정의할 수 있다
  • 자바에선 프로퍼티 선언이 불가능하지만 코틀린은 프로퍼티 선언이 가능하다
  • 클래스 상속 시에는 ()을 쓰지만 인터페이스는 안 쓴다
  • 인터페이스의 메서드 구현 시에는 override키워드를 꼭 붙여야 한다!!

우선 인터페이스를 선언해 보자.

interface Soccer {
    fun kick()
}

그 다음으로 이 인터페이스를 구현해 보자

class Player() : Soccer {
    override fun kick() {
        println("공을 찬다.")
    }
}

 

또한 인터페이스에 구현이 있는 메서드도 다음과 같이 정의할 수 있다.

interface Soccer {
    fun kick()
    fun throwIn() = println("공을 양손으로 던진다.")
    fun tackle() {
        println("공을 뺏기 위해 다리를 뻗는다.")
    }
}

인터페이스에 구현부가 있는 메서드는 다음 코드처럼 굳이 오버라이드하여 구현하지 않아도 된다.

interface Soccer {
    fun kick()
    fun throwIn() = println("공을 양손으로 던진다.")
    fun tackle() {
        println("공을 뺏기 위해 다리를 뻗는다.")
    }
}

class Player() : Soccer {
    override fun kick() {
        println("공을 찬다.")
    }
}

 

이번엔 코틀린의 프로퍼티 선언에 대해 알아보자

interface Soccer {
    val ball: Ball
    fun kick()
    fun throwIn() = println("공을 양손으로 던진다.")
    fun tackle() = println("공을 뺏기 위해 다리를 뻗는다.")
}

class Ball() {
    var posX: Int = 0
    var posY: Int = 0
}

이런 식으로 인터페이스 안에 프로퍼티를 선언할 수 있다. 하지만 인터페이스 안에서 초기화까진 불가능하다. 즉 필드는 가질 수 없다.

// Soccer 인터페이스 구현 클래스
class Player : Soccer {
    override val ball = Ball() //Ball 객체를 하나 만들어서 ball 프로퍼티로 설정

    override fun kick() {
        ball.posX += 10
        ball.posY += 5
        println("공을 찼습니다! 위치: (${ball.posX}, ${ball.posY})")
    }
}

// main 함수에서 테스트
fun main() {
    val player = Player()
    player.kick()       // 공을 찼습니다! 위치: (10, 5)
    player.throwIn()    // 공을 양손으로 던진다.
    player.tackle()     // 공을 뺏기 위해 다리를 뻗는다.
}

이렇게 구현하면 된다.

class Player(override val ball: Ball) : Soccer {
    override fun kick() {
        println("공을 찬다.")
    }
}

fun main() {
    val sharedBall = Ball()
    val player = Player(sharedBall)

    println(player.ball.posX) // 0
    player.kick()             // 공을 찬다.
}

이런 식으로 생성자 매개변수에서 바로 받아서 구현하는 것도 가능하다.(이게 더 중요한듯..)

 

 

3. 여러 인터페이스 동시 구현

코틀린도 자바와 마찬가지로 클래스는 하나의 상위 클래스만 상속 받을 수 있으며 여러 상위 인터페이스를 개수 제한 없이 구현할 수 있다.

우선 이렇게 두 인터페이스를 만들어 보자

interface Soccer {
    val ball: Ball
    fun kick() = println("공을 찬다.")
    fun throwIn() = println("공을 양손으로 던진다.")
    fun tackle()
}

interface Taekwondo {
    val state: State
    fun kick() = println("발차기를 한다.")
    fun guard()
}

 

그다음 이 인터페이스를 동시 구현해 보자

class Player(override val ball: Ball, override val state: State) : Soccer, Taekwondo {
    override fun tackle() {
        println("공을 뺏기 위해 다리를 뻗는다.")
    }
    
    override fun guard() {
        println("몸을 방어한다.")
    }
}

이렇게 했더니 오류가 난다. 그 이유는 kick이 인터페이스 단계에서 구현 되어있긴 하지만 두 인터페이스에 같은 이름의 kick메서드가 있기 때문이다. 이런 경우 kick 메서드의 구현을 강제한다.

class Player(override val ball: Ball, override val state: State) : Soccer, Taekwondo {
    override fun tackle() {
        println("공을 뺏기 위해 다리를 뻗는다.")
    }
    
    override fun guard() {
        println("몸을 방어한다.")
    
    override fun kick() {
        println("찬다.")
    }
}

이렇게 kick을 구현해주면 오류가 사라진다.

 

그렇다면 super키워드를 사용하여 상위 타입의 메서드를 사용하고 싶다면 어떻게 해야할까?

super와 꺽쇠(<>)를 이용해 사요할 수 있다.

override fun kick() {
    super<Soccer>.kick()
    super<Taekwondo>.kick()
}

 이렇게 하면 된다.