개발/Java, Kotlin

[WhiteShip][8주차] 인터페이스

Kangjieun11 2022. 11. 10. 01:58
728x90

 

 

 

 

WhiteShip Java Live Study 의 커리큘럼을 따라 개인적으로 공부한 내용입니다.

 

 

 


Codestates BE 42 : Study, Commercise 01 Java

 

Thanks to

" Codestates_SEB_BE_42 : Commercise "

강지은, 전진우

 22.11.09

 

 

 


 

 

8주차 : 인터페이스 

 

(코드스테이츠 백엔드 부트캠프 - TIL - 14일차에 존재하는 내용은 추가하지 않았습니다.)

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법 

 

목차

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

 


 

 

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

 

 

 

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

 

인터페이스 레퍼런스를 통해 구현체를 사용한다는 의미

interface Player {
   public void introduction(String name);
}
public class MyPlayer implements Player {
   @Override
   public void introduction(String name) {
      System.out.println("Hello my name is "+name);
   }
}
public class DevAndy {
   MyPlayer player = new MyPlayer();
   player.introduction("Andy");
   
   Player player02 = player;
   player02.introduction("Steve Jobs");
}

Hello my name is Andy
Hello my name is Steve Jobs

 

 

인터페이스인 Player의 인스턴스 player02를 선언하면서 참조변수로 인터페이스 구현체인 MyPlayer의 인스턴스를 참조했다.

 

즉   인터페이스 레퍼런스를 통한 구현체  (player02)

인터페이스 구현클래스(MyPlayer : player)로 타입 변환이 가능하다는 뜻인듯.

 

 

 

 

인터페이스 상속을 통한 상,하위 인터페이스 존재시 

→ 하위 인터페이스로 타입 변환하면 상,하위 구현 메소드 모두 사용 가능

→ 상위 인터페이스로 타입이 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능

 

 

 

또다른 예제

인터페이스 타입으로 객체를 생성할 수 있다. 

해당 객체에 구현 클래스로 인스턴스화 할 수 없다. 

study는 인터페이스타입이고, 객체 study를 생성했지만, 인스턴스화 된것은 아니기 때문에

say() 구현클래스 메서드 사용 불가한 듯

 

 

 

 


 

인터페이스 상속

 

 

✅ 인터페이스 상속

인터페이스 사이에서도 상속을 쓸 수 있다.

  • extends 키워드 사용 
  • 하위 인터페이스는, 상위 인터페이스의 메서드를 모두 구현해야한다. 
  • 인터페이스는 다중 상속이 가능하다
  • 구현 코드의 상속이 아니기 때문에 타입 상속이라고 한다. 

 

public interface 하위인터페이스 extends 상위인터페이스1{

}

 

하위 인터페이스를 구현하는 클래스

하위인터페이스의 메소드상위 인터페이스까지의 모든 추상 메소드에 대해

실제 메소드를 갖고 있어야 한다. 

→ 구현 클래스로부터 객체 생성 후 다음과 같이 하위/상위 인터페이스 타입으로 변환이 가능함.

하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new  구현클래스(...);

 

 

 

✔ 빠른 이해 돕기 

 
(인터페이스 상속에 대하여)



stack overflow 중...

"A를 구현하지 않고 B를 구현하지 않으려면 B가 A를 확장하도록 만들 수 있습니다. "


개가 아니어도 생명체가 되는 것은 가능하지만, 생명체가 되지 않고 개가 되는것은 불가능함

-- 짖을 수 있다면, 먹을수도 있지만, 먹을수 있다고 짖을 수 있는것은 아님



위의 예시가 너무 좋은 설명이라서 가져와봤다.
그리고 위의 예시를 보면, 인터페이스 상속에 대해 다음 3가지를 생각해볼 수 있다. 


1) interface는 형용사적(사물의 형태나 성질)인 의미를 갖고있는 것임을 기억하자. 
즉 성질/특징으로 묶어줄 때 사용하고 구현체가 아니다.
추상적인 특징을 이야기하는 메서드만 존재할 뿐이다.

2)  하위 인터페이스 : A,    상위인터페이스 : B 라고 했을 때

인터페이스 상속은 A가 되면 당연히 B도 된다는 의미

3) 객체의 상속에서도 IS-A 관계는 성립했다. 이건 명제에 있어 필요조건과 충분 조건의 관계로 나타낼 수 있다. 

A → B가 참(True)일 때, 명제 B가 참이 되기 위해 명제 A가 참일 필요가 있음  
즉 A는 B가 성립하기 위한 필요조건이다. 

대우 true ? 
~B → ~A : True 

하위 인터페이스를 필요조건 A ,  상위 인터페이스를 충분조건 B 에 대입하면 된다.  


이제 슬슬 이해가 될 것 같다.
직접 인터페이스 상속을 사용하는 예를 생각해보자.


((under water))
<물에서 살지 않는> <물에서 사는> 두 성질이 있고,
<발로 걸어다니는> <날개로 하늘을 나는> <헤엄치는> 세개의 성질이 있다고 가정하자
 
<발로 걸어다니는>특징을 가졌다면<물에서 살지 않는>게 자연스럽다.              
<날개로 하늘을 나는>특징을 가졌다면, <물에서 살지 않는>게 자연스럽다.             

그러나 <물에서 살지 않는>다고 해서 무조건 <날개로 하늘을 나는> 것은 아니다.   ❌


이걸 interface로 구현해보면,

public class interface_extends{

    public static void main(String args[]){
      Person p = new Person("jieun",24);
        System.out.println(p.name);
        System.out.println(p.age);
        System.out.println(p.getClass()+ "는 "+ p.where()+"에 삽니다.");
        System.out.println(p.getClass()+"의 다리 개수는 : "+p.getNumberOfLeg());
    }
}


interface LiveInWater{
   String where();
}
interface NotLiveInWater{
   String where();
}

interface Swim extends LiveInWater{
   boolean isMammalia();   
}
interface Walk extends NotLiveInWater{
   int getNumberOfLeg();
   
}
interface Fly extends NotLiveInWater{
   int getSpeed();
}

class Person implements Walk{
   String name;
    int age;
    
    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

   @Override
   public String where(){
      return "Land";
   }
   @Override
   public int getNumberOfLeg(){
      return 2;
   }
   
}
노트북이 인텔리제이만 틀면 죽어가고 vscode는 환경설정하려는데 느려서 슬프지만 아이패드로 돌려버림..



만약 하위인터페이스나 상위 인터페이스의 추상메서드가 구현이 한개라도 안되면 다음과 같은 에러가 난다. 

compiler.java:8: error: cannot find symbol
        System.out.println(p.getClass()+ "? "+ p.where()+"? ???.");
                                                ^
  symbol:   method where()
  location: variable p of type Person
compiler.java:32: error: Person is not abstract and does not override abstract method where() in NotLiveInWater
class Person implements Walk{
^
2 errors

// Person은 추상이 아니며 NotLiveInWater의 추상 메서드 where()를 재정의하지 않습니다.

 

 

 

 

 

 

⏺ 왜 인터페이스가 인터페이스를 상속하는 구조를 사용할 까? (WHY)

 

https://stackoverflow.com/questions/13437131/java-why-interface-extends-interface

 

Q. 둘의 차이는 무엇일까? 실제로 코드상에선 아무런 차이가 없어보인다. 

interface A{
    public void method1();
}
interface B extends A{
    public void method2();
}
class C implements B{
    @Override public void method1(){}
    @Override public void method2(){}
}
interface A{
    public void method1();
}
interface B{
    public void method2();     
}
class C implements A, B{
    @Override public void method1(){}
    @Override public void method2(){}
}

 

A.  한 가지 큰 차이점이 있다.

 

첫 번째 예시는  인터페이스 B가 A를 extends (확장) 하기 때문에

앞으로 B를 구현하는 모든 클래스는 A를 자동으로 구현한다.

 

→ 컴파일러에게   "A가 된다는 것" 은  B가 된다는 뜻으로 알려주는것.    " 모든 B는 A이다 !" 

 

 

그런데 아래 코드를 보자.

class C implements B {
    ... you implement all of the methods you need...
    ...blah...
    ...blah...
}
A myNewA = new C();

 

두 번째 예에서는 A와 B의 관계가 없어서   위의 코드가 동작하지 않는다.

 

C가 B를 구현했는데, A 참조변수에 C인스턴스를 할당하려고 하면, 

모든 B는 A이다! 라고 컴파일러에게 말하지 않았기 때문이다.

 

 

 ㅏ

class ChevyVolt implements Vehicle, Drivable {}
class FordEscort implements Vehicle, Drivable {}
class ToyotaPrius implements Vehicle, Drivable {}
 
class ChevyVolt implements Vehicle {}
class FordEscort implements Vehicle {}
class ToyotaPrius implements Vehicle {}
interface Vehicle extends Drivable {}

 

위의 예제를 A, B, C에 대입하면

 

Drivable  ==  A 

Vehicle   ==  B 

class ChevyVolt     == C
class FordEscort    == C
class ToyotaPrius   == C

 

A myNewA = new C();  //여기에 대입하면, (상속 없이는 안되는 코드 )
Drivable  Toyota  = new ToyotaPrius();     // 가 불가능하게된다.


//Vehicle   Toyota  = new ToyotaPrius();  얘는 가능함  ..

 

(Vehicle) 탈것이라면 당연히 (Drivable)운전할 수있다는 참인 명제를

상속 관계를 안 넣어주면,

자동차는 탈것을 구현했지만  운전할수있다는 성질로 묶어줄 수 없어지는 것...

 

참인 명제에서 오류가 생긴다. 

 

 

 

 


 

 

✅ 인터페이스 다중 상속

public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 {...}

 

 

상위 인터페이스에 있는 메서드 중

메서드명/파라미터 형식이 같지만 리턴타입이 다른 메서드가 있다면,

둘중 어떤 것을 상속받느냐에 따라 규칙이 달라져 다중 상속이 불가능하다. 

 

 

두개 인터페이스를 구현시, 메소드 시그니쳐가 같은 케이스에서는 어떻게 해야하는지?

중복되는 인터페이스의 추상 메소드를 재정의 하여 사용할수 있음

 

 

++ 보니까 default만 문제가 생기는 것 같음
default 메서드는 인터페이스에 구현되고, 구현클래스에서 오버라이딩이 가능한(필수x) 유일한 메서드라서...!

- 기본 메서드는 어차피 구현클래스에서 오버라이딩을 필수로 해줘야 하기 때문에 메서드 시그니처가 같아도 오버라이딩된 메서드만 호출해 사용하는 것 같다. 

 

아래는 예제

→    preJoin 메소드를 재정의 하지 않으면 컴파일 에러가 발생

→  ".super.메소드"  사용

public interface JoinMember {

    default void preJoin(){
        System.out.println("pre member");
    }
}
public interface JoinGroup {

     default void preJoin(){
        System.out.println("pre group");
    }
}
public class Member implements JoinMember, JoinGroup{

    @Override
    public void preJoin() {
        JoinMember.super.preJoin();
        JoinGroup.super.preJoin();
    }
}

 

 

 

+ static 메소드면 어떻게 할까?

애초에 override가 안되기 때문에 

인터페이스이름.preJoin(); 으로 사용할 수 밖에 없음.

 

 


 

인터페이스의 Method

인터페이스의 메서드는 public abstract 여야 하며 이를 생략할 수 있다. 단, static 메서드와 디폴트 메서드는 예외(JDK 1.8)

  • 자바 8 이전의 인터페이스는 추상 메서드만 가질수 있었으나 자바8 이후
  • default method 와 statatic method 를 정의할 수 있도록 변경되었다.

 


 

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

 

// static 메서드 : public 은 생략가능
public static void staticMethod(){
    System.out.println("this is Static Method ");
};
// static 메서드 : 내부에서 private static 메서드 사용
public static void staticMethodUsingPrivateMethod(){
    System.out.println("this is Static Method with Private Method");
    System.out.println(getPrivateStatic());
};

//----------------------------------------------------
// main 에서 호출 시

// paper.isThisStaticPaper(); // static 메서드이므로 불가능
PrintAble.staticMethod(); // static Method 호출
PrintAble.staticMethodUsingPrivateMethod(); // static Method 호출 : 내부에서 private static 메서드 호출

 

  • static 메서드는 인스턴스와 관계 없는 독립적인 메서드
  • 인터페이스의 static 메서드 는 접근제어자가 항상 public 이며 생략 가능
  • 당연히 static 메서드는 다른 static 메서드에서 사용될 수 있지만 static 이 아닌 메서드는 다른 static 메서드에서 사용될 수 없다.

 

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

 

// default 메서드 : public 은 생략가능
public default void doNotAnythig(){

};

// default 메서드 : 내부에서 private 메서드 사용
public default void doSomeThing(){
    System.out.println("doSomeThing 1 !!! -> " + getPrivateNotStatic());
}

//----------------------------------------------------
// main 에서 호출 시

paper.doNotAnythig(); // default Method 호출 : 내용 없음
paper.doSomeThing(); // default Method 호출 : 내부에서 private 메서드 호출

 

  • 인터페이스 내부에 메서드가 추가되는 변경사항이 생긴다면 해당 인터페이스를 상속받아 구현하는 구현체는 이 메서드를 구현해줘야하는 치명적인 불편사항이 존재
  • 이를 해결하기 위한 개념으로 자바8 부터 도입되었다.
  • 인터페이스에 default method 로 작성된 메서드는 구현체에서 반드시 구현하지 않아도(구현체를 변경하지 않아도) 동작에 문제되지 않는다.
  • default 반환형 메서드명(){} 형식으로 작성하며 메서드 바디가 있어야한다.
    • default 메서드는 접근제어자가 publilc 이며 생략이 가능함
  • 주의
    • 새로 추가된 default 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있다.
    • 이러한 두가지 상황에 대한 충돌을 해결하는 규칙이 있다.
      1. 여러 인터페이스의 default 메서드간의 충돌이 일어나는 경우
        • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
      2. 인터페이스의 default 메서드와 조상 클래스의 메서드 간의 충돌이 일어나는 경우
        • 조상클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.
    • 단순하게는 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 하는 방법이 있다.

 


 

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

// private 메서드
private String getPrivateNotStatic(){
    return "using Private Method Not Static";
}
// private static 메서드
private static String getPrivateStatic(){
    return "using Private Method Static";
}

//----------------------------------------------------
// main 에서 호출 시

// paper.getPrivateNotStatic(); // private 메서드 외부 호출 불가능
  • 인터페이스 내부에서만 쓰이고 외부에서는 쓰일 수 없는 메서드
    • 인터페이스 내부에서만 쓰고 싶은데 기존의 메서드는 public 이던 문제점을 개선하기위해 추가되었다. 또한 private 메서드를 사용해 인터페이스 내부에서 반복적인 코드를 줄이는것이 가능
    • default method, static method, private method 에서만 사용 가능하다고 볼 수 있다.
  • 당연히 static 메서드는 다른 static 메서드에서 사용될 수 있지만 static 이 아닌 메서드는 다른 static 메서에서 사용될 수 없다.
  • private 반환형 메서드명(){} 형식으로 작성하며 메서드 바디를 가진다.

 


 

 

💻전체 코드

public class ClassTest {
    interface PrintAble{

        // public 과 abstract 는 생략가능
        // static 메서드와 default 메서드를 제외한 모든 메서드에 public abstract 기본으로 추가
        public abstract String getTitle();
        int getPageNum();

        // static 메서드 : public 은 생략가능
        public static void staticMethod(){
            System.out.println("this is Static Method ");
        };
        // static 메서드 : 내부에서 private static 메서드 사용
        public static void staticMethodUsingPrivateMethod(){
            System.out.println("this is Static Method with Private Method");
            System.out.println(getPrivateStatic());
        };

        // default 메서드 : public 은 생략가능
        public default void doNotAnythig(){

        };

        // default 메서드 : 내부에서 private 메서드 사용
        public default void doSomeThing(){
            System.out.println("doSomeThing 1 !!! -> " + getPrivateNotStatic());
        }

        // private 메서드
        private String getPrivateNotStatic(){
            return "using Private Method Not Static";
        }
        // private static 메서드
        private static String getPrivateStatic(){
            return "using Private Method Static";
        }
    }

    // PrintAble 를 구현한 Paper 클래스
    static class Paper implements PrintAble{
        @Override
        public String getTitle() {
            return "연간보고서";
        }

        @Override
        public int getPageNum() {
            return 600;
        }
    }

    // main
    public static void main(String[] args) {
        Paper paper = new Paper();
        System.out.println("1) 인터페이스의 메서드를 구현한 메서드 호출");
        System.out.println(paper.getTitle());
        System.out.println(paper.getPageNum());
        System.out.println();

        System.out.println("2) default 메서드 호출");
        paper.doNotAnythig(); // default Method 호출 : 내용 없음
        paper.doSomeThing(); // default Method 호출 : 내부에서 private 메서드 호출
        System.out.println();

        System.out.println("3) static 메서드 호출");
        // paper.isThisStaticPaper(); // static 메서드이므로 불가능
        PrintAble.staticMethod(); // static Method 호출
        PrintAble.staticMethodUsingPrivateMethod(); // static Method 호출 : 내부에서 private static 메서드 호출
        System.out.println();

        System.out.println("4) private 메서드 호출");
        // paper.getPrivateNotStatic(); // private 메서드 외부 호출 불가능
    }
}

 

  


 

references