ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] interface와 abstract 차이점과 java8의 interface, 다중상속
    IT/JAVA | Spring 2022. 7. 9. 16:01

     

     

    interface 위키백과

     

    인터페이스 (자바) - 위키백과, 우리 모두의 백과사전

     

    ko.wikipedia.org

    인터페이스(interface)는 자바 프로그래밍 언어에서 클래스들이 구현해야 하는 동작을 지정하는 데 사용되는 추상 자료형이다. 이들은 프로토콜과 비슷하다. 인터페이스는 interface라는 키워드를 사용하여 선언하며, 메소드 시그너처와 상수 선언(static과 final이 둘 다 선언되는 변수 선언)만을 포함할 수 있다. 자바 8 미만의 모든 버전을 기준으로 인터페이스의 모든 메서드는 구현체(메서드 바디)를 포함하고 있지 않다. 자바 8부터, default와 static 메서드는 interface 정의에 구현체를 가지고 있을 수 있다. [1]

     

    abstract 해시넷

     

    추상클래스 - 해시넷

    추상클래스(abstract class)는 1개 이상의 추상메소드를 멤버로 가지고 있는 클래스를 말한다. 추상클래스는 추상메소드를 포함하고 있다는 점을 제외하면 일반 클래스와 모든 점이 동일하며 생성

    wiki.hash.kr

    추상클래스(abstract class)는 1개 이상의 추상메소드를 멤버로 가지고 있는 클래스를 말한다. 추상클래스는 추상메소드를 포함하고 있다는 점을 제외하면 일반 클래스와 모든 점이 동일하며 생성자와 필드메소드도 포함할 수 있다

     

    interface abstract
    implements extends
    구현부 없음 ( java 8 부터 구현가능 ) 구현부가 있는 메소드와 abstract method가 혼용
    구현부가 없는 메서드 필수 구현강제 ( java 8부터 상황에 따라 다름 ) abstract method만 구현 강제
    다중 상속 가능 ( java 8부터 상황에 따라 다름 ) 다중 상속 불가

    간단하게 interface와 abstract class를 정해보았다.

    interface를 보면 java 8부터 들어온 기능으로 인해 다양한 부분에서 변화가 생겼다.

     

    우리가 흔히 즐기는 게임을 예로 간단한 코드를 작성했다.

     

    interface

    interface Fps {
            public void run();
        }
    
    public class Battlegrounds implements Fps{
        @Override
        public void run() {
            System.out.println("배틀그라운드 RUN");
        }
    }
    
    public class Overwatch implements Fps {
        @Override
        public void run() {
            System.out.println("오버워치 RUN");
        }
    }

    우리는 fps라는 interface를 만든 후 run()이라는 추상 메서드를 생성했다.

    fps interface를 구현하고 있는 모든 class는 interface의 모든 추상 메소드를 오버라이딩해야 한다.

    하지만 java 8에선 어떨까 default methodstatic method를 interface에 작성할 수 있다.

     

    interface ( java 8 )

    interface Fps {
        public void run();
        
        default void shot() {
            System.out.println("탕!");
        }
    
        static void exit() {
            System.out.println("탈주!");
        }
    }
    
    public class Battlegrounds implements Fps{
        @Override
        public void run() {
            System.out.println("배틀그라운드 RUN");
        }
    }
    
    public class OverWatch implements Fps {
        @Override
        public void run() {
            System.out.println("오버워치 RUN");
        }
    }

    위 코드를 보면 interface의 default, static method를 interface에 구현하였고 에러가 발생하지 않는다.

     

    실제 사용 예제를 보자

    interface Fps {
        public void run();
        default void shot() {
            System.out.println("탕!");
        }
    
        static void exit() {
            System.out.println("탈주!");
        }
    }
    
    public class Battlegrounds implements Fps {
        @Override
        public void run() {
            System.out.println("배틀그라운드 RUN");
        }
    }
    
    public class Overwatch implements Fps {
        @Override
        public void run() {
            System.out.println("오버워치 RUN");
        }
    }
    
    public class Game {
        public void main() {
            Fps battlegrounds = new Battlegrounds();
            battlegrounds.run();
            battlegrounds.shot();
    //            battlegrounds.exit();  <--- error
            Fps.exit();
            Fps overwatch = new Overwatch();
            overwatch.run();
            overwatch.shot();
    //            overwatch.exit();     <--- error
            Fps.exit();
        }
    }

    interface는 spring solid원칙에 의하면 추상화와 다형성에 이점을 가지며 interface설계 원칙에 따라 의도한 바를 구현해야 한다.

    여기서 java 8부터 interface에 default method와 static method를 구현할 수 있게 되었다.

    해당 소스에 Fps interface에는 run() 추상 메서드, shot() default method, exit() static method가 있다.

    Fps를 구현하고 있는 Battlegrounds class와 Overwatch class는 추상메소드로 선언한 run()만 구현하고 있다.

     

    그렇다면 default method와 static method를 보자

    public static void main(String[] args) {
            Game game = new Game();
            game.main();
        }
    
    interface Fps {
        public void run();
        default void shot() {
            System.out.println("탕!");
        }
    
        static void exit() {
            System.out.println("탈주!");
        }
    }
    
    public static class Battlegrounds implements Fps {
        @Override
        public void run() {
            System.out.println("배틀그라운드 RUN");
        }
    
        @Override
        public void shot() {
            System.out.println("배틀그라운드 샷!!");
        }
    
        @Override
        public void exit() {
            System.out.println("배틀그라운드 탈주!!");
        }
    }
    
    public static class Overwatch implements Fps {
        @Override
        public void run() {
            System.out.println("오버워치 RUN");
        }
    
        @Override
        public void shot() {
            System.out.println("오버워치 샷!!");
        }
    
        @Override
        public void exit() {
            System.out.println("오버워치 탈주!!");
        }
    }
    
    public static class Game {
        public void main() {
            Fps battlegrounds = new Battlegrounds();
            battlegrounds.run();
            battlegrounds.shot();
    //            battlegrounds.exit();  <--- error
            Fps.exit();
    
            Fps overwatch = new Overwatch();
            overwatch.run();
            overwatch.shot();
    //            overwatch.exit();     <--- error
            Fps.exit();
        }
    }

    위와 같이 Test를 진행해 보면 어떻게 될까?

    2군데에서 컴파일 에러를 만나게 될 것이다.

     

    default method는 인터페이스를 구현하는 구현체에서 공통의 로직을 사용할 수 있도록 하였다.

    default method 역시 구현체에서 오버라이드 하여 수정이 가능하다.

     

    static method는 interface를 통하여 간단한 유틸성 인터페이스를 만들 수 있다.

    static method는 오버라이드 하여 수정할 수 없다.

    static method는 컴파일 시점에 정해지지만 override method는 런타임 시점에 정해진다.

    상속 자체가 성립할 수 없다.

    그리하여 static method는 Fps를 구현한 Battlegourds, Overwatch class가 아닌 Fps interface에서 바로 사용한다.

    Fps.exit(); <---- static method는 구현체가 아닌 interface

     

    여기까지는 큰 문제가 없다.

     

    하지만 interface는 다중 상속이 가능하고 abstract class는 다중 상속이 불가능하다고 알고 있다.

    java는 기본적으로 다중 상속을 지원하지 않는다.

     

    우선 다중상속을 지원하지 않는 이유를 알아보자

    다중상속을 지원하지 않는 이유는 간단하다.

    다이아몬드 문제라는 것 때문에 막아놨다.

    같은 메서드를 가지고 있는 두 부모클래스를 상속받은 자식 클래스에서 두 부모클래스 중 어떤 부모 클래스의 메서드를 상속받을 것인가?

    사람은 알고 있다. 왜냐면 머리속으로 의도를 가지고 코딩했을것이기에 사람은 알고있다.

    하지만 JVM입장에선 알 수 없다. 그러기에 원천적으로 막아왔다.

    class FpsGame{
        public void run(){
            System.out.println("Fps Run!!");
        }
    }
    
    class AosGame{
        public void run(){
            System.out.println("Aos Run!!");
        }
    }
    
    class GameRun extends FpsGame,AosGame { <-- 컴파일 에러
        public void main(String[] args) {
            GameRun gameRun = new GameRun();
            gameRun.run();
        }
    }

    JVM은 부모클래스인 FpsGame, AosGame 중 어떤 부모의 run() method를 상속받을지 판단할 수 없다.

     

    그렇다면 interface는 어떻게 다중 상속이 가능했을까?

    interface는 추상메서드만을 가지고 있고 추상메서드를 구현하고 있는 구현체가 있다.

    그렇기에 부모가 누구인지는 중요하지 않게 된다.

    이제까지 봐왔던 코드들을 보면 Fps interface를 구현하고 있는 Battlegrounds, Overwatch class로 인스턴스를 생성하여 사용한다. 

     

    그렇다면 interface에 같은 이름의 default, static method가 있다면 다중 상속이 가능할까?

    interface Sports {
        public void run();
    
        default void shot() {
            System.out.println("슛!!!!!!!");
        }
    
        static void exit() {
            System.out.println("경기종료!!!!!");
        }
    }
    
    interface Fps {
        public void run();
    
        default void shot() {
            System.out.println("탕!");
        }
    
        static void exit() {
            System.out.println("탈주!");
        }
    }
    
    public static class Batllegrounds implements Fps,Sports {
        @Override
        public void run() {
            System.out.println("배틀그라운드 RUN");
        }
    }
    
    public static class Overwatch implements Fps,Sports {
        @Override
        public void run() {
            System.out.println("오버워치 RUN");
        }
    
        @Override
        public void shot() {
            System.out.println("오버워치 샷!!");
        }
    }

    위 코드를 보면서 어떻게 동작할지 예상이 가능한가?

    위 코드에서 유의해야 하는 세 가지는 아래와 같다.

    1. Fps interface와 Sports interface에 같은 이름의 추상메서드인 run()이 있다.

    2. Fps interface와 Sports interface에 같은 이름의 default method인 shot()이 있다.

    3. Fps interface와 Sports interface에 같은 이름의 static method인 exit()이 있다.

    자 위 세 가지가 어떻게 동작할지 생각해보자

     

    1. Fps interface와 Sports interface에 같은 이름의 추상메서드인 run()이 있다.

     - 블로그 위 글에서 힌트가 있다.

    interface는 다중 상속을 지원한다. 그 이유는 추상메서드를 구현하는 구현체가 별도로 존재하기 때문에 다이아몬드 문제가 발생하지 않는다.

    어떤 interface를 구현하던 실제 코드는 구현체에 있다.

    그리하여 같은 이름의 추상메서드는 문제없이 컴파일되고 실행된다.

     

    2. Fps interface와 Sports interface에 같은 이름의 default method인 shot()이 있다.

     - 위 코드에서 Battlegrounds class는 컴파일 에러가 발생하고 Overwatch class는 발생하지 않는다.

    에러 메시지는 아래와 같다.

    blog.Batllegrounds inherits unrelated defaults for shot() from types blog.Fps and blog.Sports

    다이아몬드 문제가 발생했다. 어떤 default method인지 jvm에서 확인할 수 없다.

    Overwatch class는 shot() method를 오버라이딩하여 재정의 했다. 그래서 어느 부모의 메서드를 상속받는지는 중요하지 않다.

    그래서 컴파일 에러가 발생하지 않는다.

     

    3. Fps interface와 Sports interface에 같은 이름의 static method인 exit()이 있다.

     - static method이다. 사실상 구현체와는 관련이 없으며 interface.method()로 사용하는 걸 보면 알 수 있듯 문제가 없다.

     

    여기서 우리가 생각해 봐야 하는 부분은 2번이다.

    2번 때문에 상단의 표에서 java 8부터 상황에 따라 다른 다중 상속이라는 말을 한 것이다.

    같은 이름의 default method가 존재하는 interface는 다중 상속이 불가하다. 단 구현체에서 오버라이딩하여 재정의하는 경우는 가능하다.

     

    긴 글을 주저리 써 내려갔다. 아래에서 정리해보자.

    interface ( java 8 이상 )
    다중상속이 가능하나 같은 이름의 default method가 있다면 구현체에서 오버라이딩 해야한다.
    static method는 다중상속과 상관없다. interface.method()로 호출한다.
    abstract method는 이름이 중복되어도 상관 없다. 실제 코드는 구현체에 있다.
    추상메서드의 구현을 강제하며 default method는 선택에 따른다.
    abstract class
    구현체가 없는 abstract method는 구현을 강제하지만 그 외엔 선택에 따른다.
    다이아몬드 문제로 인하여 java는 다중 상속이 불가능하다.

     

    댓글

Designed by Tistory.