티스토리 뷰

스프링, 자바

자바의 인터페이스

killog 2021. 1. 3. 21:43
반응형

목표

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

학습할 것 (필수)

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

자바에서 .class 파일을 만들 수 있는 것에는 클래스만 있는 것이 아니다. interface 와 abstract 클래스 등을 만들 수 있다.

intelly j new java class 생성을 클릭하면 이렇게 선택 항목을 볼 수 있다.

인터페이스 도입

인터페이스란, 위키 백과에 따르면,

인터페이스(interface)는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면이다.

라고 소개하고 있다. 즉, 두 장치 사이 상호작용을 목적으로한 시스템이라는 말이다.

이러한 소개는 해당 메소드를 사용하는 사용자 입장에서, (내부 구현에 관계없이) 원하는 메소드를 호출하고, 그 리턴 값을 제대로 받으면 된다는 말과 유사하다.

인터페이스와 abstract 클래스를 사용하는 이유

  • 설계시 선언해두면, 개발할 때 기능을 구현하는 데에만 집중할 수 있다. 상속이 상위 클래스의 기능을 하위 클래스가 물려 받는 것이라고 한다면, 인터페이스는 하위 클래스에 특정 메소드가 반드시 존재하게 강제한다.
  • 개발자의 역량에 따른 메소드의 이름과 매개 변수 선언에 격차를 줄일 수 있다.
  • 공통적인 인터페이스와 abstract 클래스를 선언해 놓으면, 선언과 구현을 구분할 수 있다.

인터페이스 정의하는 방법

  • 인터페이스 선언부에 public interface 로 시작한다.
  • interface 내부에 선언된 메소드들은 몸통이 있으면 안된다. ( = {} 중괄호 안에 한줄의 코드도 있으면 안된다. )
    • 예외 사항 : default function (자바 8 이상)
  • 추상 메소드와 상수를 정의할 수 있다.
package me.whiteship.tv;

public interface TV {
  public int MAX_VOLUME = 10;
  public int MIN_VOLUME = 10;

  public void turnOn();

  public void turnOff();

  public void changeVolume(int volume);

  public void changeChannel(int channel);
}

딱 컴파일 해서 바이트 코드를 살펴보면, 인터페이스에 선언한 변수와 메소드가 상수, 추상메소드로 자동 변경된 것을 확인 할 수 있다.

// class version 58.0 (58)
// access flags 0x601
public abstract interface me/whiteship/tv/TV {

  // compiled from: TV.java

  // access flags 0x19
  public final static I MAX_VOLUME = 10

  // access flags 0x19
  public final static I MIN_VOLUME = 10

  // access flags 0x401
  public abstract turnOn()V

  // access flags 0x401
  public abstract turnOff()V

  // access flags 0x401
  public abstract changeVolume(I)V

  // access flags 0x401
  public abstract changeChannel(I)V
}

Q. static 이 뭐였지?

정적(static) 은 고정된이라는 의미를 가지고 있다. static 키워드를 붙이면 자바는 객체에 소속되지 않고, 클래스에 고정되어, 메모리 할당을 딱 한번만 하게 되기 때문에 메모리 사용에 이점을 볼 수 있게된다. 보통 변수의 static 키워드는 프로그래밍 시 메모리의 효율보다는 두번째 처럼 공유하기 위한 용도로 훨씬 더 많이 사용하게 된다.

https://coding-factory.tistory.com/524

Q. final 이 뭐였지?

final 필드는 초기값이 저장되면, 최종적인 값이 되어 프로그램 실행 도중에 수정할 수 없습니다.

예) public final static int MAX_VOLUME = 10

주로, final 타입 이름 = 초기값, 혹은 생성자를 통해 주는 방법이 있습니다. 객체 변수에 final 로 선언하면, 그 변수에 다른 참조값을 지정할 수 없습니다. 메소드의 경우 오버라이드가 불가능합니다.

Q. abstract 이 뭐였지?

  • 클래스가 추상이면 인스턴스화 할 수 없다. abstract 키워드는 Java에서 추상화를 달성하는 데 사용된다.

Q. privateinterface 를 쓰는 경우는 없을까?

이..있는 것 같다. 주로 나오는 검색이 인터페이스의 private 메소드 가 나오는데, 최상 interface 는 private 이 될 수 없다.

하지만, 클래스 선언으로 둘러싸인 경우 private 이나 protected 가 가능한 것으로 보인다.

public class ProcessController {
    private interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary) Native.loadLibrary( "c", CLibrary.class );
        int getpid();
    }

    public static int getPid() {
        return CLibrary.INSTANCE.getpid();
    }
}

protected 및 private 액세스 한정자는 선언이 클래스 선언 (§8.5.1)으로 직접 둘러싸인 멤버 인터페이스에만 적용됩니다.
( Java 언어 사양, 섹션 9.1.1 : "인터페이스 수정" :)

https://stackoverflow.com/questions/4573713/design-decisions-why-and-when-to-make-an-interface-private

Q. 과연 같은 메소드를 가진 인터페이스 구현 클래스와 클래스는 바이트 코드가 다를까?

처음에는 대표 예제와 같은 것을 사용하려고 했지만, return 이 명시적으로 있는 것 보다는 없는 것이 비교에 도움이 될 것 같아 새로 예제를 진행하였다.

인터페이스의 경우는

public interface A {
  public void example();
}

클래스의 경우는

public class B {
  public void example() {}
}

인터페이스구현 클래스의 경우는

public class C implements A {
  @Override
  public void example() {}
}

를 예제로 사용하였다.

A. 인터페이스

// class version 58.0 (58)
// access flags 0x601
public abstract interface me/whiteship/ByteCodeDiff/A {

  // compiled from: A.java

  // access flags 0x401
  public abstract example()V
}

B. 동일 메소드 클래스 파일

public class me/whiteship/ByteCodeDiff/B {

  // compiled from: B.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lme/whiteship/ByteCodeDiff/B; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public example()V
   L0
    LINENUMBER 4 L0
    RETURN
   L1
    LOCALVARIABLE this Lme/whiteship/ByteCodeDiff/B; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1
}

C. 인터페이스 구현 클래스

public class me/whiteship/ByteCodeDiff/C implements me/whiteship/ByteCodeDiff/A {

  // compiled from: C.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lme/whiteship/ByteCodeDiff/C; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public example()V
   L0
    LINENUMBER 5 L0
    RETURN
   L1
    LOCALVARIABLE this Lme/whiteship/ByteCodeDiff/C; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1
}

같은 결론이 나왔다. void 를 통해 return 을 묵시적으로 생략했지만, 컴파일 과정에서 RETURN 이라 들어가는 것을 볼 수 있다.

이를 통해, 동일해 보이는 클래스와 인터페이스 구현 클래스는 같다란 결론을 얻을 수 있다. (아니면 댓글로 알려주세요.)


인터페이스 구현하는 방법

  • implements 라는 예약어를 쓴 후에 인터페이스를 나열하며 된다.

  • 클래스는 상속과 달리 여러개의 인터페이스를 implements 할 수 있다.

  • 인터페이스가 가지고 있는 메소드를 하나라도 구현하지 않으면, 해당 클래스는 추상 클래스가 된다. ( 추상 클래스는 인스턴스를 만들 수 없다. 즉, 메모리에 할당되어 실제 사용될 수 없다.)

  • 참조변수의 타입으로 인터페이스를 사용할 수 있다. 이 경우 인터페이스가 가지고 있는 메소드만 사용할 수 있다.

  • 만약 TV인터페이스를 구현하는 LcdTV를 만들었다면 위의 코드에서 new LedTV부분만 new LcdTV로 변경해도 똑같이 프로그램이 동작할 것다. 동일한 인터페이스를 구현한다는 것은 클래스 사용법이 같다.

예시코드

package me.whiteship.tv;

public class UHDTV implements TV {

  @Override
  public void turnOn() {
    System.out.println("tv를 켭니다.");
  }

  @Override
  public void turnOff() {
    System.out.println("tv를 끕니다.");
  }

  @Override
  public void changeVolume(int volume) {
    System.out.println(volume + "로 볼륨을 조정합니다.");
  }

  @Override
  public void changeChannel(int channel) {
    System.out.println(channel + "로 채널을 조정합니다.");
  }
}
package me.whiteship.tv;

public class TVExam {
  public static void main(String[] args) {
    TV tv = new UHDTV(); // 참조변수의 타입으로 인터페이스를 사용할 수 있다.
    tv.turnOn();
    tv.changeVolume(23);
    tv.changeChannel(11);
    tv.turnOff();
  }
}

실행결과
인터페이스를 실행하려 했을때, 안된다.


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

이펙티브 자바의 예를 들어 사용해보자.

이펙티브 자바에서는 인터페이스를 자료형으로 쓰는 습관을 들이면 프로그램은 훨씬 유용해진다고 이야기한다.

(이펙티브 자바 64.) 객체는 인터페이스를 사용해 참조하라.
적당한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
( 객체의 실제 클래스를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다. )
(이펙티브 자바 51.) 매개변수 타입으로는 클래스가 인터페이스를 활용하라.

_Item 51_에 제네릭 타입으로 클래스가 아니라 인터페이스를 사용하라고 설명 되어있다.
이 말의 의미는 객체는 클래스가 아닌 인터페이스로 참조하라 라는 의미로 확장할 수 있다.
적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언해야 한다.

다음과 같은 예가 있다 하자.

//좋은 예. 인터페이스를 타입으로 사용했다.
Set<Son> sonSet = new LinkedHashSet<>();

//나쁜 예. 클래스를 타입으로 사용했다.
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

나중에, 구현 클래스를 교체하고자 한다면, 위의 좋은 예의 경우, 그저 새 클래스의 생성자를 호출해주기만 하면 된다.

Set<Son> sonSet = new TreeSet<>();

다른 코드는 바꾸지 않고, 손쉽게 코푼 걸 확인할 수 있다. 주변 코드도 옛클래스의 존재를 몰랐기 때문에, 영향을 받는 코드가 없다. ( 단, 인터페이스 이상의 기능을 제공했을 경우, 새로운 클래스고 같은 기능을 해야한다. )

하지만, 나쁜 예의 경우,LinkedHashSetHashSet으로 변환하면, LinkedHashSet 과 달리 HashSet은 반복자의 순회 순서를 보장하지 않기 떄문에, 문제가 될 수 있다.

출처 JavaCodeExamples, collections frame work 의 인터페이스를 확인 가능함.


Interface 상속

  • extends 라는 예약어를 쓴 후에 인터페이스를 쓰면 된다.

  • 클래스는 상속과 달리 여러개의 인터페이스를 상속할 수 있다. ( 다중 상속 지원 )

  • 필드가 기본적으로 static 이기 때문에 구현체를 따라가지 않는다.

package me.whiteship.interfaceEx;

interface A {
  int ex = 10;
}

interface B extends A {
  int ex = 20;
}

class C implements B {
  int ex = 30;
}

package me.whiteship.interfaceEx;

public class InterfaceEx {

  public static void main(String[] args) {
    A a = new C();
    System.out.println(a.ex); // 10
    B b = new C();
    System.out.println(b.ex); // 20
  }
}

(sas-study.tistory.com/66)

자식부모가능여부비고
일반 클래스 일반 클래스o 
일반 클래스추상 클래스o 
일반 클래스인터페이스o 
추상 클래스일반 클래스o문법은 되지만, 이치에 맞지 않아 사용 안하는것 권장
인터페이스일반클래스x일반클래스 구현 멤버를 인터페이스가 받을 수 없다.
추상클래스인터페이스o 
추상클래스추상클래스o 
인터페이스추상클래스x추상클래스 구현 멤버를 인터페이스가 받을 수 없다.
인터페이스인터페이스o 

Q1.자바는 클래스 상속에 대해서 다중 상속을 허용하지 않지만, 인터페이스에 한해서는 다중상속을 허용한다.(별표**)

A1.죽음의 다이아몬드(다중 상속의 다이아몬드)문제에 자유롭기 때문인 것으로 보인다.(구현이 안되어있기 때문에)

Q2. default method 의 도입으로 죽음의 다이아몬드에서 과연 자유로운가?

stackoverflow.com/questions/16764791/how-does-java-8-new-default-interface-model-works-incl-diamond-multiple-inhe

A2. 가장 구체적인 버전을 따라가는 것으로 보인다. 자세히는 모르겠다.

package me.whiteship.DiamondInterface;

public class DiamondInterfaceExam {
  public static void main(String[] args) {
    C c = new D();
    c.m();
  }

  interface A {
    default void m() {
      System.out.println("A's m method");
    }
  }

  interface B extends A {
    default void m() {
      System.out.println("B's m method");
    }
  }

  interface C extends A {}

  static class D implements B, C {}
}

diamond interface ex

여기서 인터페이스 B와 C 에 동시에 같은 default 메소드를 만들면 에러가 발생한다.

package me.whiteship.DiamondInterface;

public class DiamondInterfaceExam {
  public static void main(String[] args) {
    C c = new D();
    c.m();
  }

  interface A {
    default void m() {
      System.out.println("A's m method");
    }
  }

  interface B extends A {
    default void m() {
      System.out.println("B's m method");
    }
  }

  interface C extends A {
    default void m() {
      System.out.println("C's m method");
    }
  }

  static class D implements B, C {}
}

빨간 D
에러 코드
슈퍼 메소드를 사용하는 방법


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

자바 8이 등장하면서 새로 생긴 interface 정의이다. 이 기능은 이전 인터페이스를 사용하여 Java 8의 람다 표현식 기능을 활용할 수 있도록 이전 버전과의 호환성을 위해 추가되었다.

  • 인터페이스가 default키워드로 선언되면 메소드가 구현될 수 있다. 또한 이를 구현하는 클래스는 default메소드를 오버라이딩 할 수 있다.

  • 자바 Collection interface 에 보면 이런식으로 있음을 확인가능하다. 인터페이스를 구현한 이후, 수정과정에서 인터페이스 모든 구현체에게 광역으로 함수를 만들어주고 싶을 때 사용가능하다.( 단, 모든 구현체가 원하는 값을 return 하게 보장하기 위해 @implSpec 자바 독 태그를 사용해 문서화 해줘야한다.)

  • 구현체에서 오버라이딩, 재정의 가능하다.

  • 제약 조건: Object 가 제공하는 기능(equals, hasCode)는 기본 메소드로 제공할 수 없다. ( 구현체가 재정의 해야함)

  • (다이아몬드 문제) 충돌하는 default 메소드의 경우에는 직접 오버라이드 해줘야한다.

      /**
       * @implSpec
       * The default implementation calls the generator function with zero
       * and then passes the resulting array to {@link #toArray(Object[]) toArray(T[])}.
       */
      default <T> T[] toArray(IntFunction<T[]> generator) {
            return toArray(generator.apply(0));
        }
    public interface Calculator {
        public int plus(int i, int j);
        public int multiple(int i, int j);
        default int exec(int i, int j){      //default로 선언함으로 메소드를 구현할 수 있다.
            return i + j;
        }
    }

    //Calculator인터페이스를 구현한 MyCalculator클래스
    public class MyCalculator implements Calculator {

        @Override
        public int plus(int i, int j) {
            return i + j;
        }

        @Override
        public int multiple(int i, int j) {
            return i * j;
        }
    }

    public class MyCalculatorExam {
        public static void main(String[] args){
            Calculator cal = new MyCalculator();
            int value = cal.exec(5, 10);
            System.out.println(value);
        }
    }

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

static 메소드

해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때, 인터페이스에 스태틱 메소드를 제공할 수 있다.

( 헬퍼 함수 용으로 쓰이는 것으로 추정 중)

    public interface Calculator {
        public int plus(int i, int j);
        public int multiple(int i, int j);
        default int exec(int i, int j){
            return i + j;
        }
        public static void explain(){   //static 메소드 
          System.out.println("interface Caculater. 우리는 plus, multiple, exec 함수를 제공함.");   
        }
    }
       //Calculator인터페이스를 구현한 MyCalculator클래스
    public class MyCalculator implements Calculator {

        @Override
        public int plus(int i, int j) {
            return i + j;
        }

        @Override
        public int multiple(int i, int j) {
            return i * j;
        }
    }

    //인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출해야한다.  

    public class MyCalculatorExam {
        public static void main(String[] args){
            Calculator cal = new MyCalculator();
            int value = cal.exec(5, 10);
            System.out.println(value);

            Calculator.explain();  //static메소드 호출 
            //interface Caculater. 우리는 plus, multiple, exec 함수를 제공함.

        }
    }

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

인터페이스에 default, static 메소드가 생긴 이후, 이러한 메소드들의 로직을 공통화하고, 재사용하기 위해 생긴 메소드이다.

private 메소드고 static, default 메소드와 같이 구현부를 가져야하는 제약을 가진다.

(https://lob-dev.tistory.com/entry/Live-StudyWeek-08-인터페이스 참고)

    public interface Calculator {
        public int plus(int i, int j);
        public int multiple(int i, int j);
        default int exec(int i, int j){
            return i + j;
        }
        public static void explain(){   //static 메소드 
          System.out.println(callClassName()+"우리는 plus, multiple, exec 함수를 제공함.");   
        }
        private static String callClassName(){   //static 메소드 
          return "interface Caculater:";   
        }
    }

함수형 인터페이스와 람다 표현식

함수형 인터페이스(Functional Interace)

  • 추상 메소드를 딱 하나만 가지고 있는 인터페이스
  • SAM(Single Abstrace Method) 인터페이스
  • @FunctionalInterface 어노테이션을 가지고 있는 인터페이스

람다 표현식(Lambda Expressions)

  • 함수형 인터페이스의 인스턴스르 만드는 방법으로 쓰일 수 있다.
  • 코드를 줄일 수 있ㄷ.
  • 메서드 매개변수, 리턴타입, 변수로 만들어 사용할 수 있다.

자바에서 함수형 프로그래밍

  • 함수를 First class Object 로 사용할 수 있다.
  • 순수함수(Pure Function)
    • 사이드 이펙트 만들 수 없다.( 함수 밖에 있는 값을 변경하지 못한다. )
    • 상태가 없다.( 함수 밖에 정의되는)
    • 상태를 넣으면, 순수함수로 보기는 어렵다. ( lambda 안됨)
  • 고차함수(High Order Function)
    • 함수가 하수를 매개변수로 받을 수 있고, 함수를 리턴할 수도 있다.
  • 불변성
package me.whiteship.FunctionalInterfaceEx;

@FunctionalInterface // 함수형 인터페이스 컴파일
public interface RunSomething {
  static void explain() {
    System.out.println(interfaceName() + " Interface");
  }

  private static String interfaceName() {
    return "RunSomething";
  }

  default void printFunction() {
    System.out.println("explain, printFunction, doIt");
  }

  int doIt(int num);
}
package me.whiteship.FunctionalInterfaceEx;

public class Foo {
  public static void main(String[] args) {
    int ex = 30;
    // 익명 내부 클래스 anonymous inner class
    RunSomething runSomething =
        new RunSomething() {
          @Override
          public int doIt(int num) {
            System.out.println("do it!");
            return num + 10;
          }
        };
    RunSomething runSomethingArrow =
        (num) -> {
          System.out.println("do it is same");
          return num + 10;
        }; // 여러줄일때는 중괄호 써야한다.
    System.out.println(runSomething.doIt(ex)); // do it!
    System.out.println(runSomethingArrow.doIt(ex)); // do it is same
    // 메서드 파라미터, 변수, 리턴 등에 할당하는 함수형 인터페이스의 일등객체로 사용할 수 있다.
  }
}

Q. 그러면 추상클래스는 없어도 되는가?

추상클래스는 상태를 가질 수 있지만, 인터페이스는 상태가 아닌, 상수만 가질 수 있다.
상태를 가질 경우, 추상클래스를 써야한다.
또한, 중복 default method 가 생길 수 있기 때문에, 굳이 인터페이스 (자바 8이상) 로 노력하는 것보다
추상클래스로 쓸 수 있는게 아직 존재한다.


강한 결합 느슨한 결합

https://ahnyezi.github.io/java/javastudy-8-interface/


Tip: intellyj 에서 byte code 보는 법

(출처: 백기선 라이브 스터디)

  • Bytecode Viewer 플러그인을 설치한다. 설명에서 나와있다 싶이, 바이트 코드를 보려면 파일을 선택한 후 View | Show Bytecode 를 수행하면 된다.

intelly j bytecode viewer

  • 빌드(build) 한다.

intelly j build 버튼 show

  • shift +shift 키를 이용해 show bytecode 액션을 수행한다.

show byte code

  • 결과물

( 액션이 안보인다면? 플러그인이 설치가 제대로 안됐거나, build 가 안된 것이다.)

intellyJ 메소드 추출 단축키 : alt + command + N


출처

자바의 신
백기선 더 자바8
이펙티브 자바
https://lob-dev.tistory.com/entry/Live-StudyWeek-08-인터페이스
ktko.tistory.com/entry/Effective-Java-52-객체를-참조할-때는-그-인터페이스를-사용하라
https://sas-study.tistory.com/66
프로그래머스 자바 입문
sunghs.tistory.com/111
stackoverflow.com/questions/19998454/when-to-use-java-8-interface-default-method-vs-abstract-method
jamcode.tistory.com/66
www.javatpoint.com/abstract-keyword-in-java
wikidocs.net/217
coding-factory.tistory.com/
stackoverflow.com/questions/19998454/when-to-use-java-8-interface-default-method-vs-abstract-method
https://docs.oracle.com/javase/tutorial/java/concepts/interface.html
백기선 온라인 자바 스터디
flyburi.com/605
https://opentutorials.org/module/2495/14142
https://programmers.co.kr/learn/courses/5/lessons/241
https://ahnyezi.github.io/java/javastudy-8-interface/

반응형

'스프링, 자바' 카테고리의 다른 글

자바의 스레드  (0) 2021.01.16
자바 예외 처리  (0) 2021.01.16
7주차 과제: 패키지  (0) 2020.12.27
자바 상속  (2) 2020.12.26
java 의 collection  (0) 2020.12.24
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함