백기선님 자바스터디 8주차 : 인터페이스

bongf·2021년 9월 1일
0

Java강의

목록 보기
11/18

학습 1) 인터페이스 정의하는 방법

인터페이스란

  • 일종의 계약과 같다.
    • 코드의 group이 있다고 할 때 각각의 그룹은 다른 그룹의 코드가 어떻게 작성되었는지 몰라도 상호작용 하면서 코드를 작성할 줄 알아야 하는데 이 때 필요한 것이 interface
    • A그룹과 B그룹이 소통해야 한다면 B그룹 표준의 인터페이스를 만들 것. 그럼 B그룹은 A메소드의 어떤 메소드를 사용하면 되는지 알 수 있다.
    • 윈도우가 어떻게 짜여 있는지 모르고 우리가 사용할 수 있는 것처럼 (UI) (겉껍질)
  • (프로그래밍 관점) 추상 메서드의 집합 (자바의정석)
    • 일종의 추상 클래스와 갖지만 추상 클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없다.
    • 오직 추상메서드와 상수만을 멤버로 가질 수 있다.
      • JDK 1.8부터 디폴트 메서드와 static메서드 가질 수 있다.
    • 추상 클래스가 미완성 설계도라면 인터페이스는 밑그림만 있는 기본 설계도

인터페이스 정의

inteface 인터페이스이름 {
	public static final 타입 상수이름 =;
    public abstract 메서드이름(매개변수목록); 
}
  • 키워드 interface
  • 모든 멤버변수는 public static final (상수)
    • 모두가 그렇게 때문에 생략 가능하다
  • 모든 메서드는 public
    • 모두가 그렇기 때문에 생략 가능하다
    • static, 디폴트 메서드만 메소드의 바디를 가질 수 있다. 나머지는 abstarct.
interface 인터페이스이름 {
	public static final int one = 1; 
    final int two = 2; // public static final int two = 2;
    static int three = 3; // public static final int three = 3;
    int four = 4; // public static final int four = 4;
	
    public abstract String getSomething();
    String getOther(); //  public abstract String getOther();
}

  • 인터페이스는 인스턴스화 될 수 없다.
    • 인터페이스를 구현한 클래스의 인스턴스를 사용가능

인터페이스 사용이유

  • 개발시간 단축
    • 인터페이스를 정해두고 해당 인터페이스를 사용해서 프로그램을 개발하는 동시에 인터페이스에 관한 구체적인 코드 개발 동시 가능
  • 표준화 가능
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
  • 독립적인 프로그래밍이 가능
    • 클래스 선언과 구현을 분리해서 실제 구현에 독립적인 프로그램 작성 가능 (인터페이스라는 막을 두고 간접관계 형성)

학습 2) 인터페이스 구현하는 방법

  • implements를 사용하여 인터페이스에 정의된 추상메서드를 완성하면 인스턴스를 생성할 수 있다.
    • 인터페이스의 메서드 일부만 구현하면 abstract으로 추상 클래스로 선언해야 한다.
public interface Car {
    void run();
    void stop();
}

public class BungBung implements Car{

    @Override
    public void run() {
        System.out.println("brrrrr");
    }

    @Override
    public void stop() {
        System.out.println("Kkik!");
    }
}

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

인터페이스 타입의 참조변수로 인터페이스 구현체 참조하기

 Car car = new BungBung(); // class BungBung implements Car
  • 인터페이스 타입의 참조변수로 이 인터페이스의 구현체를 참조할 수 있다.
    • 이럴 때는 인터페이스에 명시된 메서드만 사용할 수 있다.
    • Car에는 없는 methodA가 BungBung에 작성되어있을 경우 위에서 Car 타입의 참조변수 car은 methodA를 사용할 수 없다.

인터페이스 타입의 매개변수로 갖는 메서드

  • 해당 메서드 호출시 해당 인터페이스의 구현체를 사용
public class BungBung implements Car{

    @Override
    public void run() {
        System.out.println("brrrrr");
    }

    @Override
    public void stop() {
        System.out.println("Kkik!");
    }

    public void greet(Car car) {
        System.out.println("hello");
    }
}
public class InterfacePractice {
    public static void main(String[] args) {
        Car car = new BungBung();
        BungBung bung = new BungBung();
        bung.greet(car);
    }
}

리턴타입을 인터페이스로 갖는 메서드

  • 해당 인터페이스의 구현체를 return 해야 한다.
public class BungBung implements Car{

    @Override
    public void run() {
        System.out.println("brrrrr");
    }

    @Override
    public void stop() {
        System.out.println("Kkik!");
    }
    
    public Car copy() {
        return new BungBung();
    }
}

학습 4) 인터페이스 상속

  • 인터페이스는 인터페이스로부터만 상속 가능.
  • 다중 상속 가능
    • 인터페이스는 다른 인터페이스를 extends (상속) 할 수 있다. (클래스는 하나의 클래스만 extends 할 수 있는데 반해)
public interface GroupedInterface extends Interface1, Interface2, Interface3 {

}

인터페이스 상속 예

  • 인터페이스를 이미 다른 클래스에서 구현하여 사용하고 있었을 때, 해당 인터페이스에 다른 메서드를 추가하고 싶을 때
  • 해당 인터페이스에 추상메서드를 또 추가할 경우 기존에 그 인터페이스를 구현하여 사용하던 클래스들을 모두 에러가 날 것이다. 추가된 메서드를 구현하지 않은 클래스가 되니까.
  • 그 때 사용할 수 있는 것이 상속
public interface DoItPlus extends DoIt {

   boolean didItWork(int i, double x, String s);
   
}
  • 이제 각각의 사용한 클래스에 대해서 원래의 DoIt 클래스를 구현해서 그대로 사용할지 DoItPlus를 구현해서 사용할지 선택해서 사용할 수 있다.

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

  • 위에서 (인터페이스 상속 예) 접했던 문제를 디폴트 메서드를 통해 해결할 수 있다. (static 메소드도 가능)
    • 인터페이스가 있고, 이 인터페이스를 구현한 클래스들이 사용중인데, 인터페이스에 새로운 메소드를 추가하고 싶을 때
    • 만약에 디폴트 메소드가 없다면 인터페이스에 메서드가 추가되면 구현 클래스들은 모두다 이를 구현해야 한다
public interface DoIt {

   void doSomething(int i, double x);
   int doSomethingElse(String s);
   default boolean didItWork(int i, double x, String s) {
       // Method body 
   }
   
}
  • 인터페이스 DoIt을 구현한 클래스들에게는 어떤 코드 변화도 필요하지 않게 된다.

디폴트 메소드 활용

    1. 단순상속 2. override 3. abstract으로 재선언
  • 만약 특정 클래스에서 해당 메소드를 다르게 구현하고 싶다면 @Override 하면 된다.
public interface Car {
    void run();
    void stop();
    default void refuel() {
        System.out.println("yummy");
    }
}
public class BungBung implements Car{

    @Override
    public void run() {
        System.out.println("brrrrr");
    }

    @Override
    public void stop() {
        System.out.println("Kkik!");
    }

    @Override
    public void refuel() {
        System.out.println("yummy yummy!");
    }
}
  • 이 디폴트 메소드를 다시 abstarct으로 만들 수 있다.
public interface NewCar extends Car{
    @Override
    void refuel();
}

인터페이스 메소드의 우선순위

  • 디폴트 메서드를 활용하다 보면 상속하는 클래스와 동일명의 메소드이거나 인터페이스간 메소드 명이 같아 충돌할 때가 있다. 이 때 적용되는 우선순위는 아래와 같다.

규칙 1 : 인터페이스의 디폴트 메소드보다 인스턴스 메소드가 우선한다. (클래스우선)

public class OverridingPriority {
    public static void main(String... args) {
        Pegasus myApp = new Pegasus();
        System.out.println(myApp.identifyMyself()); // "I am a horse."
    }
}

class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
interface Flyer {
    default public String identifyMyself() {
        return "I am able to fly.";
    }
}
interface Mythical {
    default public String identifyMyself() {
        return "I am a mythical creature.";
    }
}

class Pegasus extends Horse implements Flyer, Mythical {
    
}

규칙2. 다른 후보에 의해 오버라이딩된 메소드는 무시한다. (더 구체적인 메소드 우선)

한 예를 들면, 한 클래스가 여러 인터페이스를 implements 했고, 그 인터페이스 목록들에 같은 메소드가 있을 때 그 중 누군가가 한 인터페이스를 오버라이딩을 했다면 그것을 따른다. (더 구체화된 메소드를 따른다)

public interface Animal {
    default public String identifyMyself() {
        return "I am an animal.";
    }
}
public interface EggLayer extends Animal {
    default public String identifyMyself() {
        return "I am able to lay eggs.";
    }
}
public interface FireBreather extends Animal { }

public class Dragon implements EggLayer, FireBreather {
    public static void main (String... args) {
        Dragon myApp = new Dragon();
        System.out.println(myApp.identifyMyself()); //I am able to lay eggs.
    }
}

지금 여기서 Droagon은 EggLayer와 FireBreather를 implements 했는데 이 두 후보 중에서 EggLayer가 해당 메소드를 오버라이딩 했으므로 FireBreather의 메소드는 무시된다.

아래와 같은 경우를 보면,

interface MyInterfaceA {
    default void print() {
        System.out.println("Print in MyInterfaceA");
    }
}

interface MyInterfaceB extends MyInterfaceA {
    default void print() {
        System.out.println("Print in MyInterfaceB");
    }
}
class MyClass1 implements MyInterfaceA {
}
class MyClass2 extends MyClass1 implements MyInterfaceA, MyInterfaceB {
}

public class OverridingPriority {
    public static void main(String... args) {
        MyClass1 myClass1 = new MyClass1();
        myClass1.print(); // Print in MyInterfaceA
        MyClass2 myClass2 = new MyClass2();
        myClass2.print(); // Print in MyInterfaceB
    }
}

myClass2.print() 할 때 어떤 메소드를 호출할지를 결정해야한다. 규칙1에 따라 클래스가 우선시 되서 MyClass1의 메소드가 가장 우선시 되야 할 것이다. 하지만 MyClass1는 해당 메소드는 구현하고 있지 않아 규칙1이 적용될 수 없다.

규칙2에 따라 더 구체적인 디폴트 메소드가 우선시되므로 (MyInterfaceA 보다 MyInterfaceB가 더 구체적이라 이와 같은 결과가 나왔다.

두 개 이상의 독립적으로 정의된 독립된 디폴트 메서드가 충돌하거나(case1), 디폴트 메서드와 추상 메서드가 충돌되는 경우에는 컴파일 에러가 발생한다. (case2)

  • (case1)
public interface OperateCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public interface FlyCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
  • (case2)
interface MyInterfaceA {
    default void print() {
        System.out.println("Print in MyInterfaceA");
    }
}

class MyClass2 implements MyInterfaceA {
}

위에서는 컴파일에러가 안난다. 디폴트 메서드가 있으니까

interface MyInterfaceA {
    default void print() {
        System.out.println("Print in MyInterfaceA");
    }
}

abstract class MyClass1 {
    public abstract void print();
}

class MyClass2 extends MyClass1 implements MyInterfaceA {
} // 컴파일 에러 

하지만 위처럼 되면 MyInterfaceA에서 메소드를 구현해 줌에도 불구하고 컴파일 에러가 난다. 추상메소드와 디폴트 클래스가 충돌하기 때문. MyClass2에서 다시 오버라이딩 해줘야 한다.

그런데 MyInterfaceA에 있는 것을 굳이 반복해서 쓸 필요가 없다. super 를 사용하면 된다.

interface MyInterfaceA {
    default void print() {
        System.out.println("Print in MyInterfaceA");
    }
}

abstract class MyClass1 {
    public abstract void print();
}

class MyClass2 extends MyClass1 implements MyInterfaceA {
    public void print() {
        MyInterfaceA.super.print();  // Print in MyInterfaceA
    }
}
  • super 키워드 사용법이 더 궁금하면 여기

요약

  • 규칙 1. 클래스가 항상 우선
  • 규칙 2. 더 구체적인 디폴트를 제공하는 인터페이스가 우선.
  • 해결방법 (백기선님 스터디 추가)

학습 6) 인터페이스의 static 메소드, 자바 8

  • static 메소드도 디폴트 메소드처럼 메소드 바디를 가질 수 있다.
    • 각 객체보다는 클래스 관련된 메소드다. static이니까.
  • static 메소드이므로 인스턴스 생성 없이 바로 사용가능하다.
public interface Car {
    void run();
    void stop();
    default void refuel() {
        System.out.println("yummy");
    }
    static void introduce() {
        System.out.println("Car");
    }
}
public class InterfacePractice {
    public static void main(String[] args) {
       Car.introduce(); //Car

    }
}

학습 7) 인터페이스의 private 메소드, 자바 9

  • 인터페이스 내부에서만 사용될 수 있다.
  • 메소드 바디를 가져야 한다( abstract 불가)
  • 코드의 재사용성
  • private static은 - non static, static 메소드에서 사용 가능, private non static은 non static 메소드에서 사용가능
public interface Temp {

    public abstract void mul(int a, int b);

    public default void add(int a, int b) {
        // private method inside default method
        sub(a, b);

        // static method inside other non-static method
        div(a, b);
        System.out.print("Answer by Default method = ");
        System.out.println(a + b);
    }

    public static void mod(int a, int b) {
        div(a, b); // static method inside other static method
        System.out.print("Answer by Static method = ");
        System.out.println(a % b);
    }

    private void sub(int a, int b) {
        System.out.print("Answer by Private method = ");
        System.out.println(a - b);
    }

    private static void div(int a, int b) {
        System.out.print("Answer by Private static method = ");
        System.out.println(a / b);
    }
}

static, default 메서드 차이

  • static은 오버라이드 가능, default는 불가능

더 알아볼 것

정리 잘 된 글


출처

profile
spring, java학습

0개의 댓글