ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 항해일지 - 8. 인터페이스
    공부일기/자바 스터디 2021. 1. 8. 19:01

    목표

    자바의 인터페이스에 대해 학습하세요.

    학습할 것 (필수)

    • 인터페이스 정의하는 방법
    • 인터페이스 구현하는 방법
    • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
    • 인터페이스 상속
    • 인터페이스의 기본 메소드 (Default Method), 자바 8
    • 인터페이스의 static 메소드, 자바 8
    • 인터페이스의 private 메소드, 자바 9

    인터페이스란?

    인터페이스는 일종의 추상 클래스이다. 추상클래스처럼 추상메서드를 갖지만 추상 클래스보다 추상화 정도가 높아 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

    추상클래스를 '미완성 설계도'에 비유한다면, 인터페이스는 밑그림만 그려진 '기본 설계도'에 비유할 수 있다.

    추상클래스와 마찬가지로 불완전하기 때문에 그 자체로 사용되기 보단 다른 클래스를 작성하는데 도움을 줄 목적으로 작성된다.

     

    이미지 출처: https://www.notion.so/4b0cf3f6ff7549adb2951e27519fc0e6

    인터페이스 정의하는 방법

    인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드를 class가 아닌 interface를 사용한다는 점에서 차이가 있다. 그리고 인터페이스에도 클래스처럼 접근제어자로 public 또는 default만 사용할 수 있다.

     

    interface 인터페이스 이름 {
            public static final 타입  상수이름 = 값;
            public abstract 메서드이름(매개변수목록);
    }

    일반적인 클래스와 다르게 인터페이스의 멤버들은 다음과 같은 제약사항을 지닌다.

    • 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
    • 모든 메서드는 public abstract 이어야 하고, 이를 생략할 수 있다. 단, static 메서드와 default 메서드는 예외(JDK 1.8부터)

     

     

    인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있고, 편의상 생략하는 경우가 많다고 한다. 생략된 제어자는 컴파일 시 컴파일러가 자동적으로 추가해준다.

     

    예)

    interface PlayingCard {
    	public static final in SPADE = 4;
        final int DIAMOND = 3; // public static final int DIAMOND = 3;
        static int HEART = 2; // public static final int HEART = 2;
        int CLOVER = 1; // public static final int CLOVER = 1;
        
        public abstract String getCardNum();
        String getCardKind(); // public abstract String getCardKind();
    }

     

     

    인터페이스의 구현

    추상클래스와 마찬가지로 인터페이스도 그 자체로는 인스턴스를 생성할 수 없고, 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 만들어야 한다. 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않으나, extends 대신 implements키워드를 사용한다.

     

    class 클래스이름 implements 인터페이스이름 {
            // 인터페이스에 정의된 추상메서드를 모두 구현해야 한다.
    }
    class Fighter implements Fightable {
            public void move(int x, int y) {/* 메서드 구현 */}
            public void attack(Unit u) {/* 메서드 구현 */}
    }

     

    • 인터페이스의 메서드 중 일부만 구현하는 경우 : abstract를 붙여 추상클래스로 선언한다.
    abstract class Fighter implements Fightable {
            public void move(int x, int y) {/* 메서드 구현 */}
    }

     

     

    • 상속과 구현을 동시에하는 경우
    class Fighter extends Unit implements Fightable {
            public void move(int x, int y) {/* 메서드 구현 */}
            pulbic void attack(Unit u) {/* 메서드 구현 */}
    }

     

     

    인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

    자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능했듯이 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있고, 인터페이스 타입으로의 형변환도 가능하다.

     

    아래 예제를 통해 확인해보자

    예)

    public interface Animal {
        public abstract void cry();
    }
    public class Dog implements Animal{
        @Override
        public void cry() {
            System.out.println("멍멍!");
        }
    
        public void name() {
            System.out.println("doggy");
        }
    }
    public class Cat implements Animal{
        @Override
        public void cry() {
            System.out.println("야옹");
        }
    
        public void name() {
            System.out.println("meow");
        }
    }
    public class ReferenceSample {
        public static void main(String[] args) {
            Animal dog = new Dog();
            Animal cat = new Cat();
    
            dog.cry();
            cat.cry();
    
            // dog.name(); 사용 불가
            // cat.name(); 사용 불가
            ((Dog)dog).name(); // 캐스팅을 통해 사용 가능
            ((Cat)cat).name(); // 캐스팅을 통해 사용 가능
        }
    }

    인터페이스 Animal을 클래스 Dog와 Cat이 구현했을 때, 위와 같이 Dog이나, Cat 인스턴스를 Animal 타입의 참조변수로 참조하는 것이 가능하다.

     

     

    인터페이스 상속

    인터페이스는 인터페이스로부터만 상속받을 수 있고, 클래스와 달리 다중상속이 가능하다. 그리고 클래스 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다. 아래 예시에서 Fightable은 Movable과 Attackable로 부터 상속받은 두 개의 추상메서드 move와 attack을 멤버로 지닌다.

     

    예)

    interface Movable {
    	void move(int x, int y); // 지정된 위치(x, y)로 이동
    }
    
    interface Attackable {
    	void attack (Unit u); // 지정된 대상(u)를 공격
    }
    
    interface Fightable extends Movable, Attackable { }

     

     

    인터페이스의 기본 메소드 (Default Method), 자바 8

    자바 8 이전에는 인터페이스에 추상 메서드만 선언할 수 있었으나 자바 8부터 디폴트 메서드와 static 메서드를 추가할 수 있게 됐다.

    인터페이스에 메서드를 추가하는 경우, 추상 메서드를 추가하게 되는 것이고, 이 인터페이스를 구현한 기존 모든 클래스들에 새로 추가된 메서드를 구현해야 한다.

    아무리 좋은 설계를 통한 프로그래밍도 언젠가 한번쯤 인터페이스를 변경할 일이 생길 것이고, 이를 위해 디폴트 메서드가 나오게 됐다.

     

    디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다. 위의 문제점을 해결할 수 있다.

    디폴트 메서드는 앞에 키워드 default를 붙이며, 추상메서드와 달리 일반메서드처럼 몸통 '{ }'이 있어야 한다.디폴트메서드 역시 접근 제어자가 public 이며, 생략가능하다.

     

    예)

    interface MyInterface {
    	void method(); // 추상 메서드
        default void newMethod(){} // 디폴트 메서드
    }

    위와 같이 디폴트 메서드를 추가하는 경우, 기존의 MyInterface를 구현한 클래스를 일일이 변경하지 않아도 된다. 조상 클래스에 새로운 메서드를 추가한 것과 동일한 작용을 한다. 대신 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생하게 되면 이 충돌을 해결하는 규칙은 다음과 같다.

     

    1. 여러 인터페이스의 디폴트 메서드 간의 충돌
      - 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 한다.
    2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
      - 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

     

    인터페이스의 static 메소드, 자바 8

    static 메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 자바 8 버전 이전부터 인터페이스에 추가하지 못할 이유는 없었으나 규칙을 단순화 하기 ㄷ위해 인터페이스의 모든 메서드는 추상 메서드여야 한다는 규칙에 예외를 두지 않았다.

    그러나 자바 8부터는 static메서드를 사용할 수 있게 됐다. 위의 디폴트 메서드와 함께 예제를 통해 확인해보자.

     

     

    예)

    class DefaultAndStaticMethod {
    	public static void main(String[] args) {
        	Child c = new Child();
            c.method1();
            c.method2();
            MyInterface.staticMethod();
            MyInterface2.staticMethod();
        }
    }
    
    class Child extends Parent implements MyInterface, MyInterface2 {
    	public void method1() {
        	System.out.println("method1() in Child"); // 오버라이딩
        }
    }
    
    class Parent {
    	public void method2() {
        	System.out.println("method2() in Child");
        }
    }
    
    Interface MyInterface {
      default void method1() { // Child에서 오버라이딩
      	System.out.println("method1() in MyInterface");
      }
      
      default void method2() {
      	System.out.println("method2() in MyInterface");
      }
     
      static void staticMethod() {
      	System.out.println("staticMethod() in MyInterface");
      }
    }
    
    Interface MyInterface2 {
      default void method1() { // Child에서 오버라이딩
      	System.out.println("method1() in MyInterface");
      }
    
      static void staticMethod() {
      	System.out.println("staticMethod() in MyInterface");
      }
    }

    실행결과

    method1() in Child
    method2() in Parent
    staticMethod() in MyInterface
    staticMethod() in Myinterface2

     

     

    인터페이스의 private 메소드, 자바 9

    Java8의 디폴트 메서드 및 static 메서드의 기능에도 여전히 불편한 점은 존재했다.

    특정 기능을 처리하는 내부 method에 불과하지만 외부에 공개되는 public method로 만들어야 했고, 인터페이스를 구현하는 다른 인터페이스 혹은 클래스가 해당 method에 액세스하거나 상속할 수 있는 것을 원하지 않는 경우가 그 예다.

     

    이에 대응해 Java 9부터는 인터페이스에 private method 및 private static method를 이용할 수 있게 해 문제를 해결했다. 이에 따라 인터페이스에 대한 캡슐화를 유지할 수 있게 됐고, 인터페이스 내부의 코드 재사용성을 증가시켰다.

     

    아래 예를 통해 자세히 살펴보자예)

    public interface ExampleInterface {
        public abstract void method1();
    
        public default void method2() {
            method4();  // Private 메서드
            method5();  // Private static 메서드
            System.out.println("Default 메서드");
        }
    
        public static void method3() {
            method5(); // Static 메서드
            System.out.println("Static 메서드");
        }
    
        private void method4(){
            System.out.println("Private 메서드");
        }
    
        private static void method5(){
            System.out.println("Private Static 메서드");
        }
    }
    public class PrivateInterface implements ExampleInterface{
        @Override
        public void method1() {
            System.out.println("Abstract 메서드");
        }
    
        public static void main(String[] args) {
            ExampleInterface exampleInterface = new ExampleInterfaceSample();
            exampleInterface.method1();
            
            exampleInterface.method2();
            
            ExampleInterface.method3();
        }
    }

    실행결과를 각 메서드의 실행에 따라 구분지어 나타내면 다음과 같다.

    Abstract 메서드
    
    Private 메서드
    Private Static 메서드
    Default 메서드
    
    Private Static 메서드
    Static 메서드

     

     

     

     

     

    참고.

    Java의 정석

    sujl95.tistory.com/60

    grokonez.com/java/java-9-private-interface-method

     

Designed by Tistory.