자바-8(인터페이스)

dragonappear·2021년 3월 18일
0

Java

목록 보기
8/22

# 학습

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

인터페이스 정의하는 방법

인터페이스란?

인터페이스란 객체와 객체 사이에서 일어나는 상호 작용의 매개로 쓰인다.

서로 이어주는 다리 역할과 프로젝트의 설계도라 생각할수 있다.

모든 기능을 추상화로 정의한 상태로 선언만 한다.

인터페이스의 선언은 예약어로 class 대신 interface 를 사용하며, 접근 제어자로는 public 또는 default를 사용한다.

interface InterfaceName{

	// code

}

또한, implements 키워드를 통해 일반 클래스에서 인터페이스를 구현할수 있다.

Java 8 이전까지는 상수와 추상 메소드만 선언가능하였지만,

Java 8 이후부터는 default method and static method가 추가 되었다.

public interface 인터페이스명{
	// 상수
    타입 상수명 = 값;
    
    // 추상 메소드
    타입 메소드명(매개변수,...);
    
    // default 메소드
    default 타입 메소드명(매개변수,...){
    	// 구현부
    }
    
    // 정적 메소드
    static 타입 메소드명(매개변수 , ...){
    	// 구현부
    }
}
  1. 상수(절대적)
    인터페이스에서 값을 정해주어 값을 변경할수없고 제공해주는 값을 참고만 할수있다.

  2. 추상메소드(강제적)
    메소드의 가이드만 제공되고 오버라이딩해서 재구현하여 사용해야한다.

  3. 디폴트 메소드(선택적)
    인터페이스에서 기본적으로 제공해주지만, 구현내용을 원하지 않는다면 오버라이딩해서 재구현해서 사용가능하다.

  4. 스태틱 메소드(절대적)
    인터페이스에서 제공해주는 것으로 무조건 사용해야한다.

인터페이스는 추상 클래스와 같이 추상 메소드를 가지므로 추상 클래스와 매우 흡사하다.

인터페이스는 추상 클래스와 같이 인스턴스를 생성할 수 없고, 상속받은 클래스에서 구현한 뒤 자기 클래스를 인스턴스화 하여 사용한다.

추상클래스와 인터페이스의 차이점

  1. 추상 클래스는 일반 메소드와 추상 메소드 둘다가질 수 있다.
    -> 인터페이스는 오로지 추상 메소드와 상수만을 가진다.(구현 로직을 작성할 수 없다.) But, java 8부터는 default method 와 static method를 작성할 수 있다.

  2. 인터페이스 내에 존재하는 메소드는 무조건 public abstract로 선언되며, 이를 생략할 수 있다.

  3. 인터페이스 내에 존재하는 변수는 무조건 public static final로 선언되며, 이를 생략할수있다.

  • 추상클래스

  • 인터페이스


링크텍스트

public interface InterfaceName{

	private int a = 1;
    // interface의 제약 조건을 따르지 않았기 때문에 오류가 발생한다.
    
    public int b = 2;
    // 컴파일러가 자동적으로 public static final b=2으로 추가해준다.
    
    static int c= 3;
    // 컴파일러가 자동적으로 public static final c=3으로 추가해준다.

	int d = 4;
    // 컴파일러가 자동적으로 public static final 3=4으로 추가해준다.
}
  • 추상클래스는 "a is b: ~는 ~이다" 의 개념이다.

  • 인터페이스는 "a has b: ~는 ~를 할수있다" 의 개념이다.

ex) Son은 사람 Person 이면서 코딩(Coding)을 할 수있다.
-> class Son extends Person implements Coding

인터페이스의 사용 이유

  1. 개발 기간을 단축시킬수있다.
    인터페이스를 사용하면 다른 개발자들이 각각의 부분을 완성할 때가지 기다리지 않고 서로 규약만 정해두어 각자의 부분만 따로 나눠서 작성된코드를 컴파일 할수있다.

  2. 클래스간 결합도를 낮출수있다.
    코드의 종속성을 줄이고 유지보수성을 높이도록 해준다.

  3. 표준화가 가능하다.
    클래스의 기본틀을 제공하여 개발자들에게 정형화된 개발을 요구할수있다.

-> 자바의 다형성을 극대화하여 코드의 수정을 줄이고 유지보수성을 높인다.


인터페이스 구현하는 방법

개발코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.

객체는 인터페이스에서 정의된 추상메소드와 동일한 메소드이름,매개타입,리턴타입을 가진 메소드를 가지고있어야한다.

이러한 객체를 인터페이스의 구현(implements)객체라고 하며, 구현 객체를 생성하는 클래스를 구현 클래스라 하한다.

구현 클래스

보통의 클래스와 동일한데, 인터페이스 타입으로 사용 할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스명을 명시해줘야한다.

public ImplementClass implements InterfaceName{

	// 인터페이스에 선언된 추상메소드의 실체 메소드 선언
    
}

그리고 인터페이스에 선언된 추상 메소드의 실체 메소드를 선언해야한다.

예제코드:

  1. 인터페이스
package week8;

public interface RemoteControl {
    int MAX_VOLUME = 100;
    int MIN_VOLUME = 0;

    void turnOn();
    void turnOff();
    void setVolume(int volume);

    default void setMute(boolean mute){

    };
}
  1. 구현클래스
package week8;

public class TV implements RemoteControl{
    private int volume;

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

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

    @Override
    public void setVolume(int volume){
        if (volume > RemoteControl.MAX_VOLUME){
            this.volume=MAX_VOLUME;
        }
        else if(volume<RemoteControl.MIN_VOLUME){
            this.volume=MIN_VOLUME;
        }
        else{
            this.volume=volume;
        }
    }

}
package week8;

public class Audio implements RemoteControl{
    private int volume;

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

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

    @Override
    public void setVolume(int volume){
        if (volume > RemoteControl.MAX_VOLUME){
            this.volume=MAX_VOLUME;
        }
        else if(volume<RemoteControl.MIN_VOLUME){
            this.volume=MIN_VOLUME;
        }
        else{
            this.volume=volume;
        }
    }

    @Override
    public void setMute(boolean mute){
        if(mute){
            System.out.println("Audio를 무음으로 변경합니다.");
        }
        else{
            System.out.println("Audio를 유음으로 변경합니다.");
        }
    }

}

익명구현객체

구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용할 수 있기때문에 편리하지만,일회성의 구현 객체를 만들기 위해 소스파일을 만들고 클래스를 선언하는 것은 비효율적일 수 있다.

자바는 익명구현객체를 사용하여 소스 파일을 만들지않고도 구현객체를 만들수있는 방법을 제공한다.

인터페이스 변수 = new 인터페이스(){
	// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
package week8;

public class App {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl() {
            @Override
            public void turnOn() {
                
            }

            @Override
            public void turnOff() {

            }

            @Override
            public void setVolume(int volume) {

            }
        };
    }
}

다중 인터페이스 구현클래스

객체는 다수의 인터페이스 타입으로 사용할수있다.

인터페이스 A 와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면, 객체는 이 두 인터페이스를 모두 구현해야한다.

  • 다중인터페이스를 구현할경우
    구현클래스는 모든 인터페이스의 추상메소드에 대해 실체 메소드를 작성해야한다.
  1. 인터페이스 코드:
package week8;

public interface RemoteControl {
    int MAX_VOLUME = 100;
    int MIN_VOLUME = 0;

    void turnOn();
    void turnOff();
    void setVolume(int volume);

    default void setMute(boolean mute){

    };
}

+

package week8;

public interface Searchable {
    void search(String url);
}
  1. 구현클래스
package week8;

public class SmartTV implements RemoteControl,Searchable{
    private int volume;

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

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

    @Override
    public void setVolume(int volume){
        if (volume > RemoteControl.MAX_VOLUME){
            this.volume=MAX_VOLUME;
        }
        else if(volume<RemoteControl.MIN_VOLUME){
            this.volume=MIN_VOLUME;
        }
        else{
            this.volume=volume;
        }
    }

    @Override
    public void search(String url){
        System.out.println(url+"의 결과를 검색합니다.");
    }
}

주의사항:

컴파일러가 혼동할 수 있는 경우(예: 리턴타입만 다른경우) 는 에러가 발생한다

interface Dancable {
    void perform();
}

interface Flyable {
    boolean perform();
}

interface Perfomable extends Flyable,Dancable {
} //'perform()' in 'Dancable' clashes with 'perform()' in 'Flyable'; methods have unrelated return types

인터페이스를 활용한 다형성

다형성은 조상 타입의 참조변수가 자손타입의 인스턴스를 가리키는 형태를 말한다.

다형성의 예시:

Tv t = new SmartTv();
// 부모타입(Tv)으로 자손타입(SmartTv) 인스턴스를 가리킴

인터페이스도 다형성이 성립하기 때문에, 인터페이스 타입으로 자손타입의 인스턴스를 가리킬 수 있다.

interface Fightable{
	void move(int x,int y);
    void attack(Fightable f);
}

class Fighter implements Fightable{
	//추상클래스를 구현한 메서드
    public void move(int x, int y){}
    public void attack(Fightable f){}
    
    // 자손 클래스에서 추가한 인스턴스 메서드
    public void sill(){}
}
  1. 인터페이스 타입으로 자손 타입의 인스턴스를 가리킬수 있다.
    • 부모타입(Fightable)의 참조변수를 사용하면, 자손 타입(Fighter)에 얼마나 많은 멤버가 있든 부모의 멤버만 사용가능
// 인터페이스 타입으로 구현클래스 인스턴스를 가리킴
Fightable f = new Fighter();

f.move(100,200);
f.attack(new Fighter());

// 자손클래스에만 존재하는 메서드 호출시 컴파일러 에러
// f.skill()
  1. 매개변수 타입을 인터페이스로 둘수도있다.
    • 이럴경우, 해당 인터페이스를 구현한 클래스 인스턴스만 들어올수있다.
interface Fightable{
	// Fightable 인터페이스 구현클래스 인스턴스만 들어올 수 있음
    void attack(Fightable f);
}
  1. 인터페이스를 메서드 리턴 타입으로 지정할수있다.
    • 메서드의 반환타입이 인터페이스라면, 해당 인터페이스를 구현한 클래스 타입의 인스턴스를 반환한다.
    • 코드의 유연성을 높이기 위해서 사용한다
      - Fightable을 구현한 다른 클래스가 있다면, new 뒷부분만 수정해서 같은 코드를 사용할수있음.
class Fighter implements Fightable{
	@Override
    public void attack(Fightable f){};


class B{
	public Fightable testMethod(){
    	Figther f =new Fighter(); // 인터페이스 구현클래스의 인스턴스
        return f; // return (Fightable) f ; 자동으로 부모로 형변환(업캐스팅)
    }
}

class C{
	void Test(){
    B b = new B();
    Fightable f = b.testMethod(); // 인터페이스(부모) 타입으로 저장
    }
}
}

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

인터페이스의 상속

인터페이스의 상속 구조에서는

  • 서브인터페이스는 슈퍼 인터페이스의 메소드까지 모두 구현해야한다.

  • 인터페이스 레퍼런스는 인터페이스를 구현한 클래스의 인스턴스를 가리킬수있고, 해당 인터페이스에 선언된 메서드(슈퍼 인스턴스 메서드 포함)만 호출할 수 있다.

인터페이스 다중상속

인터페이스는 클래스와 달리 다중상속이 가능하다.

인터페이스의 메서드는 추상 메서드로써 구현하기전의 메서드이기 때문에 어떤 인터페이스의 메서드를 상속받아도 같기 때문이다.

But, 상위 인터페이스에 있는 메서드 중에서 메서드 명과 파라미터 형식은 같지만 리턴타입이 다른 메서드가 있다면, 둘중 어떤것을 상속받느냐에 따라 규칙이 달라지기 때문에 다중 상속이 불가능하다.

위와 같은 구조에서는 A와 B2를 확장하는 인터페이스 C2를 만드려고 할때 IDE에서는 아래와 같이 컴파일이 될 수 없는 에러를 노출한다.

Q. 두 개의 인터페이스를 구현하는 경우 메소드 시그니쳐가 같은 케이스에선느 어떻게 해야하나?

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

  1. 인터페이스 코드:
package week8;

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

public interface JoinMember {
    default void preJoin(){
        System.out.println("pre member");
    }
}
  1. 구현클래스 코드:
package week8;

public class Member implements JoinMember,JoinGroup{
    @Override
    public void preJoin(){
        JoinMember.super.preJoin();
        JoinGroup.super.preJoin();
    }
}

-> preJoin 메서드를 재정의 하지 않으면 컴파일 에러가 발생한다.
-> static 메서드가 아님으로 참조할수있는 방법이 ".super.메서드" 이다.

그렇다면 static메소드는? => static 메소드는 override가 안된다.

package week8;

public interface StaticJoinMember {
    static void preJoin(){
        System.out.println("Static pre Join Member");
    }
}
package week8;

public interface JoinMember extends StaticJoinMember {
    default void preJoin(){
        System.out.println("pre member");
    }
    
    default void afterJoin(){
        System.out.println("after member");
    }
}
  • 여기서 preJoin은 override가 아니다

  • 전혀 다른 영역에 위치하고 있는것이다.

즉, StaticJoinMember의 preJoin은 아래와 같이 사용하는 것밖에 없다.

StaticJoinMemober.preJoin();

Q. 부모 인터페이스에 같은 프로필로 추상메소드와 default메소드가 있다면?

interface Dancable {
    void fly();
}
interface Flyable {
    default void fly() {
    }
}

interface Child extends Dancable, Flyable {
// Child inherits abstract and default for fly() from types Flyable and Dancable
}

강제로 override하여 해결한다.

interface Child extends Dancable, Flyable {
	@Override
    default void fly(){
    }
}

인터페이스 상속과 인터페이스 레퍼런스의 예제코드

Bar.java 인터페이스는 Foo.java 인터페이스를 상속한 인터페이스로 정의한다.

package week8;

public interface Foo {
    void fooMethod();
}
package week8;

public interface Bar extends Foo{
    void barMethod();
}

Bar.java 인터페이스의 구현체로 DefaultFoo.java를 구현한다.

  • barMethod() 는 Bar 인터페이스의 추상 메소드를 구현

  • fooMethod()는 Bar 인터페이스가 상속하고 있는 Foo인터페이스의 추상메소드를 구현

  • testMethod_1,testMethod_2는 DefaultFoo 구현체 자체의 메소드

package week8;

public class DefaultFoo implements Bar{
    @Override
    public void barMethod(){
        System.out.println("bar method - by DefaultFoo");
    }

    @Override
    public void fooMethod(){
        System.out.println("foo method - by DefaultFoo");
    }

    public void testMethod_1(){
        System.out.println(" testMethod_1 ");
    }

    public void testMethod_2(){
        System.out.println(" testMethod _ 2 ");
    }
}

Use DefaultFoo.java

  • 클래스타입 레퍼런스와
  • 인스턴스 레퍼런스 두가지를 활용해본다.
package week8;

public class App {
    public static void main(String[] args) {
        System.out.println("hi");

        DefaultFoo defaultFoo = new DefaultFoo();
        defaultFoo.barMethod();
        defaultFoo.fooMethod();
        defaultFoo.testMethod_1();
        defaultFoo.testMethod_2();

        Bar bar = defaultFoo;
        bar.barMethod();
        bar.fooMethod();
    }
}

출력:

hi
bar method - by DefaultFoo
foo method - by DefaultFoo
 testMethod_1 
 testMethod _ 2 
bar method - by DefaultFoo
foo method - by DefaultFoo

Process finished with exit code 0
  • 클래스 타입 래퍼런스는 해당 클래스에 정의된 메서드를 호출할수있다.
DefaultFoo defaultFoo = new DefaultFoo();
        defaultFoo.barMethod();
        defaultFoo.fooMethod();
        defaultFoo.testMethod_1();
        defaultFoo.testMethod_2();
  • 인스턴스 레퍼런스는 해당 인터페이스를 구현한 클래스 인스턴스를 가리킬 수 있고 해당 인터페이스에 선언된 메서드만 호출할 수 있다.
Bar bar = defaultFoo;
bar.barMethod();
bar.fooMethod();

다중 인터페이스에서의 인터페이스 레퍼런스

다중 인터페이스를 통해 클래스는 여러개의 규칙을 이행할수있다.

한 클래스가 여러개의 인터페이스를 구현했다면, 각 인터페이스로 구분해서 그 객체를 사용할 수 있게 된다.

즉, 구현체를 어떤 인터페이스 레퍼런스에 담느냐에 따라 사용할 때 따르는 규칙이 달라진다.

bar.java 인터페이스와 baz.java 인터페이스를 다중으로 구현한 클래스

package week8;

public class DefaultFoo implements Bar,Baz{
    @Override
    public void barMethod(){
        System.out.println("bar method - by DefaultFoo");
    }

    @Override
    public void fooMethod(){
        System.out.println("foo method - by DefaultFoo");
    }

    @Override
    public void bazMethod(){
        System.out.println("baz method - by DefaultFoo");

    }
}
package week8;

public class App {
    public static void main(String[] args) {
        System.out.println("hi");

        DefaultFoo defaultFoo = new DefaultFoo();

        defaultFoo.fooMethod();
        defaultFoo.barMethod();
        defaultFoo.bazMethod();

        Bar bar = defaultFoo;
        bar.fooMethod();
        bar.barMethod();

        Baz baz = defaultFoo;
        baz.bazMethod();
    }
}

출력:

hi
foo method - by DefaultFoo
bar method - by DefaultFoo
baz method - by DefaultFoo
foo method - by DefaultFoo
bar method - by DefaultFoo
baz method - by DefaultFoo
  • DefaultFoo는 Bar인터페이스와 Baz 인터페이스를 구현하였음으로 위 예제를 보듯이
  • Bar 라는 인터페이스 레퍼런스에 담을수도있고
  • Baz 라는 인터페이스 레퍼런스에 담을수도 있다.

강한결합과 느슷한 결합

강한 결합과 느슨한 결합에 대한 이해가 선행되어야 인터페이스를 사용하는 이유를 이해할 수 있을것이다.


링크텍스트

  1. 강한결합
  • A는 B에 의존하고 있다.(A가 B를 사용)
  • 이 때, A가 C를 사용하게 하려면?
  • A는 B를 의존하고 있는 코드를 C를 의존하게 끔 변경해야 한다.(강한결합)
  1. 약한결합
  • A는 I 인터페이스에 의존하고 있고, I 인터페이스를 구현한 B를 사용한다.
  • 이 때, A가 C를 사용하게 하려면?
  • A는 I 인터페이스에 의존하고 있기 때문에, I인터페이스를 구현한 C를 사용한다면 따로 코드를 변경하지 않아도 된다.(느슨한 결합)

강한결합: 빠르지만 변경에 불리

직접적인 관계의 두 클래스

class A{
	public void methodA(B b){ 
    	b.methodB(); // B를 사용(A와 B의 직접적인 연결)
    
    }
}

class B{
	public void methodB(){
    	System.out.println("methodB()");	}
}

class InterfaceTest{
	public static void main(String[] args){
    A a = new A();
    a.methodA(new B());
    }

}

느슨한결합: 느리지만 유연하고 변경에 유리

간접적인 관계의 두 클래스(A->I->B)

class A{
	public void methodA(I i){ 
    	i.methodB(); // I를 사용 (A와 B의 관계는 없음)
    
    }
}

interface I{
	public abstract void methodB();
}

class B implements I {
	public void methodB(){
    	System.out.println("methodB()");	}
}


// 나중에 B를 C로 변경하여도 C만 변경하면 됨.
// methodB를 호출하는 A를 변경할필요없다
class C implements I {
	public void methodB(){
    	System.out.println("methodB() in C");	}
}


class InterfaceTest{
	public static void main(String[] args){
    A a = new A();
    a.methodA(new B());
    }

}

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

인터페이스에서의 기본 메소드란?

  • 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법

  • 해당 인터페이스를 구현한 클래스의 어떠한 영향없이 새로운 기능을 추가하는 방법

  • default method는 해당 인터페이스를 구현한 구현체가 모르게 추가된 기능임으로 그만큼 리스크가 따른다.
    컴파일 에러는 발생하지 않지만, 특정한 구현체의 로직에 따라 런타임 에러가 발생할 수 있다.
    사용하게 된다면 구현체가 잘못사용하지 않도록 반드시 문서화 하자!

  • Object가 제공하는 기능(equals,hashCode)와 같은 기본 메소드는 제공할수없다.
    구현체가 재정의하여 사용하는 것은 상관없다

  • 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다.

  • 인터페이스를 상속받은 인터페이스에서 다시 추상 메소드로 변경할 수 있다.

  • 인터페이스 구현체가 default method를 재정의 할수있다.

why?

기존 Collection에 새로운 기능들이 추가되려고 할때,

  • forEach,of,... 등등
    인터페이스에 기능을 추가했음에도, 이 인터페이스를 상속한 클래스들이 깨지지 않게

..(중략).. 바로 "하휘 호환성" 때문이다.
예를 들어 설명하자면, 여러분들이 만약 오픈 소스코드를 만들었다고 가정하자.
그 오픈소스가 엄청 유명해져서 전 세계 사람들이 다 사용하고 있는데,
인터페이스에 새로운 메소드를 만들어야 하는 상황이 발생했다.
자칫 잘못하면 내가 만든 오픈소스를 사용한 사람들은 모두 오류가 발생하고 수정해야 하는 일이 발생할 수도 있다.
이럴때 사용하는 것이 바로 default 메소드 이다.

  • 기존에 존재하던 인터페이스를 이용하여서 구현된 클래스를 만들고 사용하고 있는데,

  • 인터페이스를 보완하는 과정에서 추가적으로 구현해야할 혹은 필수적으로 존재해야 할 메소드가 있다면,

  • 이미 이 인터페이스를 구현한 클래스와의 호환성이 떨어지게 된다.

  • 이런 경우 default 메소드를 추가하게 된다면 하위 호환성은 유지하면서 인터페이스 보완을 진행할수있다.

예제 코드로 알아보는 기본메소드

Foo.java

package week8;

public interface Foo {
    void printName();
}

DefaultFoo

package week8;

public class DefaultFoo implements Foo{
   @Override
    public void printName(){
       System.out.println("DefaultFoo");
   }
}

위와 같은 상황에서 Foo 인터페이스를 구현한 모든 구현체에 공통적인 기능을 제공해야 하는 요구사항이 발생되었다.

  • 추가된 요구사항에 대한 메소드: printNameUpperCase();

또다른 인터페이스 메소드를 추가하여 제공할수있다.
or defualt method를 활용하여 제공할 수 있다.

Foo.java

package week8;

public interface Foo {
    void printName();
    voidprintNameUpperCase();
}

위와 같이 Foo.java 인터페이스에 "printNameUpperCase()" 메소드가 추가되었다.

이렇게 되는 경우 Foo.java 인터페이스를 구현한 구현체들은 모두 컴파일 에러가 발생한다.

why: 추가된 인터페이스의 메소드를 구현하지 않았기 때문이다.

이때, 구현한 구현체들에게 영향을 받지 않고 기능을 제공하고 싶다면?

default method 추가

Foo.java

package week8;

import java.util.Locale;

public interface Foo {
    void printName();
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase(Locale.ROOT));
    }
    String getName();
}

DefaultFoo.java

package week8;

public class DefaultFoo implements Foo{
    private String name;
    @Override
    public void printName(){
       System.out.println("DefaultFoo");
   }

    @Override
    public String getName() {
        return name;
    }

    public DefaultFoo(String name) {
        this.name = name;
    }
}

FooMain

package week8;

public class FooMain {
    public static void main(String[] args) {
        DefaultFoo defaultFoo = new DefaultFoo("hi");

        defaultFoo.printName();
        defaultFoo.printNameUpperCase();
    }

}

출력

DefaultFoo
HI

위와 같이 default method를 추가한다면? Foo.java를 구현한 구현체인 DefaultFoo.java 에서는 추가적인 메서드 구현 없이도 사용할 수 있게 된다.

WARNING

  • default method로 제공한 기능이 항상 제대로 동작할 것이라는 보장이 없다.

  • 구현체들은 default method가 어떻게 구현되어 있는지 알 수 없다
    위 예제와 같은 경우에서는 getName()이 어떻게 구현되어있는지 모르는 상황에서
    default moethod에서 getName() 메소드를 호출하여 로직을 구현하였다.

  • 문제가 발생되면 RuntimeException이 발생할 수 있다.

  • 사용할 경우 문서화를 잘하자!

Foo.java

package week8;

import java.util.Locale;

public interface Foo {
    void printName();

    // @impleSec이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력해준다.
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase(Locale.ROOT));
    }
    String getName();
}

구현체에서는 default method로 제공되는 기능이 있지만, 이를 추가적으로 재정의하여 사용할 수 도있다.

DefaultFoo.java

  @Override    
    public void printNameUpperCase(){
        // do something
   }

plus

Foo.java 인터페이스를 extends한 새로운 인터페이슨느 default method를 추상 메소드로 재정의하여 제공할 수 있다.

bar.java extends Foo.java

package week8;

public interface Bar extends Foo{
    // Foo가 제공하는 기본 메소드를 제공하고 싶지 않을때
    // -> 추상 메서드로 다시 정의할 수 있다.
    void printNameUpperCase();    
}

plus

두개의 인터페이스에서 동일한 default method가 있다면?

두 개 인터페이스를 사용하는 곳에선 어떻게 될까?

Foo.java

package week8;

import java.util.Locale;

public interface Foo {
    void printName();

    // @impleSec이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력해준다.
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase(Locale.ROOT));
    }
    String getName();
}

Bar.java

package week8;

public interface Bar {
    default void printNameUpperCase(){
        System.out.println("BAR");
    }
}

아래와 같이 Compile error가 발생하게 된다.

-> 이런 케이스에서는 직접 해당 메소드를 사용하는 단에서 오버라이딩하여 사용해야 한다.

함수형 인터페이스

자바8 에서는 함수를 '1급 시민처럼 다룰수 있도록, 함수형 인터페이스를 제공한다.

1급시민이 되면, 함수는

  • 변수에 값을 할당할 수 있으며
  • 함수를 파라미터로 넘겨줄 수 있고,
  • 함수의 반환값이 될 수 있다.

예제코드

@FuntionalInterface
interface Addition{
	int addition(final int num1,final int num2);
}

함수형 인터페이스는

  • 한개의 추상메서드만 가져야한다.
  • @FunctionalInterface 어노테이션을 붙여야 한다.
  • 이렇게 되면 Addition 타입을 가지는 addition 이라는 메서드는 이제부터 기존에 섰던 1급시민처럼 사용할수있다.

1. 변수에 값을 할당

Addition add = (i1,i2) -> i1+i2 ; // 함수형태로 변수에 값을 넣음.
System.out.println(add.addition(1,2)); // 3

2. 함수를 파라미터로 넘겨줌

public static void main(String[] args){
	test((i1,i2)->i1+i2)); // 함수가 피라미터로 들어갔다.
}

static void test(Addition addition){
System.out.println(addition.addition(1,2));	
}

3. 함수의 반환값이 될 수 있어야한다.

public static void main(String[] args) {
    Addition add = test();
    System.out.println(add.addition(1,2));
}

static Addition test() {
    return (i1, i2) -> i1 + i2; // 함수가 리턴값에 들어갔습니다.
}

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

자바8에서의 인터페이스 기본 메서드

과거 인터페이스의 기본 메서드가 제공되지 않았던 시절
아래와 같은 형태로 개발을 진행하기도 하였다.

  • 인터페이스의 여러가지 메서더들 중 한가지 메서드만 사용하는 구현체가 있을때

  • 중간에 추상 클래스를 만들어서, 이 추상 클래스를 확장하는 구현체에서는 필요한 메소드만 구현할 수 있도록 하는 일종의 편의 제공성으로 개발되었다.

하지만, 자바 8 이상에서 인터페이스의 기본 메서드가 제공됨에 따라 위와 같이 추가적인 추상클래스가 필요없이도 원하는대로 개발할 수 있게 되었다.

으를 통해, 구현체들은 상속에 대해 자유로워지게 되었다.

interface의 static method

  • 해당 인터페이스를 구현한 모든 인스턴스, 해당 타입에 관련되어 있는 유틸리티, 헬퍼 메서드를 제공하고 싶다면? -> static method로 제공할수있다.

  • 인스턴스 없이 수행할 수 있는 작업을 정의할 수 있는 것이라고 볼 수 있다.

  • 형태:

	static 리턴타입 메소드명(파라미터,...){
    	// code
    }

예제코드:

Foo.java

package week8;

import java.util.Locale;

public interface Foo {
    void printName();

    // @impleSec이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력해준다.
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase(Locale.ROOT));
    }
    String getName();
    
    static void printAnything(){
        System.out.println("FOO static");
    }
}

Use Foo.java

package week8;

public class FooMain {
    public static void main(String[] args) {
        Foo.printAnything();
    }

}

출력:

FOO static

주의사항:


링크텍스트

interface Dancable {
    void fly();
//    default void fly(){
//        System.out.println("call from default");
//    }
}

interface Flyable extends Dancable {
    static void fly() {
        System.out.println("call from static");
    }
}
// error : Static method 'fly()' in 'Flyable' cannot override instance method 'fly()' in 'Dancable'

class Print implements Flyable {
}

Q. 인터페이스의 default,static 메서드가 등장하면서 추상클래스가 의미가 없어지는것아닌가?

인터페이스

package week8;

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

    default void afterJoin(){
        System.out.println("after member");
    }
}

추상클래스

package week8;

public abstract class AbstractJoinMember implements joinMember{
    private  String  message = "이런 클래스는 그럼 필요가 없어지는거아님?";
    
    @Override
    public void preJoin(){
        System.out.println(message);
    }
    
    public void setMessage(String message){
        this.message=message;
    }
}

추상클래스에서 할수있는 것을 인터페이스에서 다할수있는가?

아닐것이다

인터페이스에서는 위 추상클래스처럼 private String message와 같은 변수를 선언할수없다

public static final과 같은 상수만 선언가능하다.

Java 8 이상에서만 가능하다.

추상클래스의 많은 것들은 인터페이스로 옮겨서 구현할 수 있게 된것은 맞지만,
추상클래스에서만 할 수 있는 것들이 있기 때문에, 추상클래스 자체의 효용가치는 아직 존재하고있다.


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

interface의 private 메소드

java8 에서는 default method와 static method가 추가되었는데
java9 에서는 추가적으로 private methodprivate static method가 추가되었다.

why?

  • java8의 default method와 static method는 여전히 불편하게 만드는 부분이 있다.

  • 특정 기능을 처리하는 내부 method일 뿐인데도, 외부에 공개되는 public method로 만들어야하기 때문이다.

  • interface를 구현하는 다른 interface 혹은 class 가 해당 method에 엑세스 하거나 상속할 수 있는 것을 원하지 않지만, 그렇게 될 수 있는것이다.

private 메소드의 네가지 규칙

  • private 메서드는 구현부를 가져야만 한다.

  • 오직 interface 내부에서만 사용할 수 있다.

  • private static 메서드는 다른 static 또는 static이 아닌 메소드에서 사용할 수 있다.

  • static이 아닌 private 메서드는 다른 private static 메서드에서 사용할 수 없다.

interface CustomCalculator{
	default int addEvenNumbers(int... nums){
		return add(n -> n % 2 == 0, nums);
	}

	default int addOddNumbers(int... nums){
		return add(n -> n % 2 != 0, nums);
	}

	private int add(IntPredicate predicate, int... nums){
		return IntStream.of(nums)
										.filter(predicate)
										.sum();
	}
}

java 9에서는 위와 같은 사항으로 인해 private method 와 private satatic method라는 새로운 기능을 제공함으로써 문제를 풀어나간다.

-> 코드의 중복을 피하고 interface에 대한 캡슐화를 유지할수있게 된다.

Foo.java 인터페이스

package week8;

import java.util.Locale;

public interface Foo {
    void fooMethod();
    
    default void defaultFooMethod(){
        System.out.println("default Foo Method");
        privateFooMethod();
        privateStaticFooMethod();
    }
    
    private void privateFooMethod(){
        System.out.println("private foo method");
    }
    
    private static void privateStaticFooMethod(){
        System.out.println("private static foo method");
    }

    
}

DefaultFoo.java

package week8;

public class DefaultFoo implements Foo{
    @Override
    public void fooMethod(){
    System.out.println("foo method by DefaultFoo");
    };
}

User DefaultFoo.java

package week8;

public class FooMain {
    public static void main(String[] args) {
        DefaultFoo defaultFoo = new DefaultFoo();
        defaultFoo.fooMethod();
        defaultFoo.defaultFooMethod();
    }
}

출력:

foo method by DefaultFoo
default Foo Method
private foo method
private static foo method

참고

링크텍스트

링크텍스트

링크텍스트

링크텍스트

링크텍스트

링크텍스트

링크텍스트

0개의 댓글