본문 바로가기
  • 개발하는 곰돌이
Programming Language/Kotlin & Java

Kotlin 기본 문법 7 : 추상 클래스/인터페이스와 클래스의 상속

by 개발하는 곰돌이 2022. 12. 13.

목차

    개요

    Kotlin도 Java와 마찬가지로 추상 클래스와 인터페이스가 존재하고 클래스에서 다른 클래스를 상속하거나 인터페이스를 구현할 수 있다. 이번 포스트에서는 Kotlin의 추상 클래스/인터페이스와 클래스의 상속에 대해 정리한다.


    Kotlin의 추상 클래스

    Kotlin의 추상 클래스는 Java와 마찬가지로 abstract class 키워드를 사용하여 작성한다. 추상 클래스를 작성하는 방법은 일반적인 클래스를 작성하는 것과 동일하게 작성하고 class 대신 abstract class를 사용하면 된다. 클래스를 작성하는 기본적인 방법을 제외하면 Java와 같다.

    // Java
    abstract class Animal {
        String name;
    
        public Animal(String name) {
            this.name = name;
        }
    }
    // Kotlin
    abstract class Animal(var name: String)

    추상 클래스 내부에는 추상 메소드를 작성할 수 있다. 이 역시 Java와 마찬가지로 abstract 키워드를 사용하며 메소드의 내용은 작성하지 않고 추상 클래스를 상속받는 자식 클래스에서 내용을 작성한다.

    // Java
    abstract class Animal {
        String name;
    
        public Animal(String name) {
            this.name = name;
        }
    
        protected abstract void eat();
    }
    // Kotlin
    abstract class Animal(var name: String) {
        protected abstract fun eat()
    }

    Kotlin도 Java와 마찬가지로 추상 클래스 단독으로 객체를 생성할 수 없다.

    fun main() {
        val dog = Animal("멍멍이")		// 불가능!
    }
    
    abstract class Animal(var name: String) {
        protected abstract fun eat()
    }

    Kotlin의 인터페이스

    Kotlin에서 인터페이스를 작성하는 방법 역시 Java와 동일하다. interface 키워드를 사용하여 인터페이스를 선언하고, 구현체에서 구현할 추상 메소드를 작성해주면 된다.

    // Java
    interface Walkable {
        void run();
    }
    // Kotlin
    interface Walkable {
        fun run()
    }

    Kotlin의 인터페이스에서도 Java처럼 디폴트 메소드를 작성할 수 있다. 이 때는 Java와 달리 default 키워드를 사용하지 않고 바로 메소드를 작성하면 된다.

    // Java
    interface Walkable {
        default void walk() {
            System.out.println("신나게 걸어가요");
        }
        void run();
    }
    // Kotlin
    interface Walkable {
        fun walk() = println("신나게 걸어가요")
        fun run()
    }

    Kotlin의 클래스 상속

    Kotlin에서 클래스를 상속받기 위해서는 Java와 약간 다른점이 있다. Java는 클래스를 작성해두기만 하면 접근 지정자의 범위 내에서 마음껏 상속할 수 있지만 Kotlin의 클래스는 기본적으로 final 속성이기 때문에 open 키워드를 사용하여 해당 클래스가 상속할 수 있게 열려있다고 명시된 클래스나 추상 클래스만 상속이 가능하다.

     

    또한 Kotlin에서는 extends 키워드를 사용하지 않고 콜론(:)을 사용하며, 부모 클래스의 이름이 아니라 부모 클래스의 생성자를 상속받아야 한다.

    // Java
    class Animal {
        String name;
    
        public Animal(String name) {
            this.name = name;
        }
    
        protected void eat(String food) {
            System.out.println(food + "를 맛있게 먹습니다");
        }
    }
    
    class Dog extends Animal {
        public Dog(String name) {
            super(name);
        }
    
        @Override
        protected void eat(String food) {
            System.out.println("강아지가 "+ food +"를 맛있게 먹습니다.");
        }
    }
    // Kotlin
    class Animal(var name: String) {
        protected fun eat(food: String) = println("${food}를 맛있게 먹습니다.")
    }
    
    class Dog(name: String) : Animal(name) {	// Animal이 open되지 않아 에러 발생!
        override fun eat(food: String) = println("강아지가 ${food}를 맛있게 먹습니다.") 
    }
    
    
    open class Animal(var name: String) {
        protected open fun eat(food: String) = println("${food}를 맛있게 먹습니다.")
    }
    
    class Dog(name: String) : Animal(name) {	// OK
        override fun eat(food: String) = println("강아지가 ${food}를 맛있게 먹습니다.") 
    }

    위 코드를 보면 Kotlin과 Java는 클래스를 상속 받으면서 메소드를 오버라이드하는 것도 약간 다르다. Java에서는 @Override 어노테이션을 사용하여 메소드를 오버라이드 했다는 것을 나타내고 이를 생략할 수도 있었지만, Kotlin에서는 override라는 키워드로 메소드를 오버라이드 했다는 것을 나타내고 이 키워드는 생략할 수 없다.

     

    Kotlin도 Java와 마찬가지로 클래스의 상속은 한 개의 클래스만 가능하다.


    Kotlin의 인터페이스 구현

    Kotlin의 인터페이스 구현은 상속과 비슷하다. 클래스를 상속받을 때처럼 콜론(:)을 사용하여 인터페이스를 구현하며 각 클래스와 인터페이스는 콤마(,)로 구분한다. 그 외의 사항은 Java와 동일하다.

    // Java
    class Dog extends Animal implements Walkable {
        public Dog(String name) {
            super(name);
        }
    
        @Override
        protected void eat(String food) {
            System.out.println("강아지가 "+ food +"를 맛있게 먹습니다.");
        }
    
        @Override
        public void run() {
            System.out.println("강아지가 즐겁게 뛰어갑니다.");
        }
    }
    // Kotlin
    class Dog(name: String) : Animal(name), Walkable {
        override fun eat(food: String) = println("강아지가 ${food}를 맛있게 먹습니다.")
        override fun run() = println("강아지가 즐겁게 뛰어갑니다.")
    }

    Kotlin에서 클래스를 선언할 때 콜론 뒤에 오는 식별자가 클래스인지 인터페이스인지 구분하는 방법은 간단하다. 클래스는 반드시 생성자를 상속받아야 하기 때문에 괄호와 생성자의 파라미터가 있다면 클래스, 인터페이스는 생성자를 가질 수 없기 때문에 단순히 식별자만 적혀있다면 인터페이스라고 구분할 수 있다.

     

    또한 Kotlin에서도 Java와 마찬가지로 인터페이스 단독으로 객체를 생성할 수 없다.


    마무리

    Kotlin에서 추상 클래스와 인터페이스의 차이는 Kotlin과 Java의 기본적인 문법 차이를 제외하면 어느정도 동일하다는 것을 알 수 있다. 클래스를 상속받으려면 명시적으로 클래스를 열어주거나 상속받을 부모 클래스를 추상 클래스로 작성해야 한다는 점과 자식 클래스에서 구현하거나 오버라이드하는 메소드는 반드시 오버라이드한 메소드라고 명시해야 하는 점만 주의하면 좋을 것이다.

     

    다음 포스트에서는 Kotlin의 클래스에서 정적 필드와 메소드를 다루는 방법 및 Kotlin에서의 싱글톤에 대해 정리할 것이다.

     

    댓글 피드백은 언제나 환영합니다.

     

    댓글