티스토리 뷰

donaricano-btn
반응형
public interface Student {

    String getName();
}
public interface Citizen {

    String getName();
}
public class Brad implements Citizen, Student{

    @Override
    public String getName() {
        return "Brad";
    }
}

 

Student Interface 와 Citizen Interface 를 동시에 구현하는 Brad 라는 클래스가 있습니다. 각 인터페이스는 getName 이라는 문자열을 반환하는 메서드를 가지고 있고 이 인터페이스를 구현하는 Brad 클래스는 반드시 getName 메서드를 오버라이드 해야 합니다. Brad 클래스 에서는 getName 을 오버라이드 하고 있습니다.

 

다음과 같은 코드로 getName 메서드를 실행해서 결과를 확인해 보도록 하겠습니다.

 

Citizen citizen = new Brad();
Student student = new Brad();
Brad brad = new Brad();

System.out.println(citizen.getName());
System.out.println(student.getName());
System.out.println(brad.getName());
Brad
Brad
Brad

Process finished with exit code 0

 

오류 없이 getName 메소드가 정상적으로 동작하는 것을 확인할 수 있습니다. 같은 이름의 메서드를 가진 두 개의 인터페이스를 구현하고 있지만 어차피 실제로 이 메서드가 구현되는 것은 단 한번 이기 때문에 컴파일러는 흔히 다중상속에서 나타나는 다이아몬드 문제 없이 잘 동작 하는 것을 알 수 있습니다. 

 

자바8 이전의 자바에서는 인터페이스의 모든 메서드가 추상메서드 여야 했습니다. 다시 말해 구현부가 없어야 했습니다, 그렇기 때문에 위 코드와 같이 다중상속의 문제에서 자유로울 수 있었습니다. 인터페이스에 메서드 구현부가 없었던 이유는 초기의 언어 설계자들이 인터페이스를 객체를 사용하는 쪽과 제공하는 쪽이 서로 공유하는 일종의 계약, 혹은 추상적인 명세로 정의했기 때문입니다.

 

하지만 이제 인터페이스는 진화했습니다. 자바8 에서 부터는 정적 메서드나 기본 메서드를 통해 구현부를 가질 수 있게 되었습니다.

 

public interface Student {

    ...
    
    public static Student of(String name) {
        return new HighSchoolStudent(name);
    }
}


정적 메서드의 가장 대표적인 활용 사례는 위와 같은 팩토리 메서드 입니다. 이런 식으로 팩토리 메서드를 사용하게 되면 호출자는 인스턴스에 대해 고려할 필요가 없습니다. 

 

예전에는 보통 이런 식의 정적 메서드를 동반 클래스(companion class)에 두엇습니다. 대표적은 예로 Collection/Collections 가 있습니다. 하지만 이제는 이런식으로 분할할 필요가 없어졌습니다.

 

public interface Student {

    String getName();

    default String getGreetingMessage() {
        return "Hi! I am a student!";
    }
}

 

반면 기본 메서드는 위 코드 처럼 어느 메서드에서도 기본 구현을 작성할 수 있습니다. 물론 이 인터페이스를 구현하는 클래스는 기본 메서드를 그대로 사용할 수도 있고 오버라이드 해서 사용할 수도 있습니다.

 

이렇게 기본 메서드를 사용함으로서 이제 인터페이스를 구현하기 위해 모든 메서드를 다 오버라이드 할 필요가 없어졌습니다.

 

그러면 여기서 한가지 궁금증이 생깁니다. 같은 이름의 기본 메서드를 가진 두 개 이상의 인터페이스를 구현한 클래스는 과연 어떤 기본 메서드를 실행하게 될까요?

 

public interface Student {

    String getName();

    default String getGreetingMessage() {
        return "Hi! I am a student!";
    }
}
public interface Citizen {

    String getName();

    default String getGreetingMessage() {
        return "Hi! I am a Citizen!";
    }
}
public class Brad implements Citizen, Student{

    @Override
    public String getName() {
        return "Brad";
    }
}

 

이 코드는 컴파일 에러가 나게 됩니다. 어느 쪽의 getGreetingMessage 메서드를 선택해야 하는지 알 수 없기 때문이죠. 이렇게 충돌이 발생 할 수 있기 때문에 클래스에서 인터페이스 두 개 이상을 구현할 때 에는 반드시 충돌을 먼저 해결해야 합니다. 자주 일어나는 상황은 아니지만 이런 경우를 해결하기 위해서 다음과 같은 두 가지 방법을 사용할 수 있습니다.

 

public class Brad implements Citizen, Student{

    @Override
    public String getName() {
        return "Brad";
    }

    @Override
    public String getGreetingMessage() {
        return "Hi! I am Brad!";
    }
}

 

그 중 하나는 위처럼 구현 클래스에서 메서드를 재정의 하는 것입니다. 이렇게 하면 명확해 집니다. 컴파일러는 오류를 보고하지 않고 실행되게 됩니다.

 

public class Brad implements Citizen, Student{

    @Override
    public String getName() {
        return "Brad";
    }

    @Override
    public String getGreetingMessage() {
        return Student.super.getGreetingMessage();
    }
}

 

또 다른 방법으로는 위 코드 처럼 충돌한 메서드 중 어느 하나에 명시적으로 위임해 주는 것입니다. 이렇게 하면 충돌의 문제 없이 위임된 인터페이스의 기본 메서드를 수행하게 됩니다.

반응형

'language > JAVA' 카테고리의 다른 글

JAVA Enum(열거 타입) 값 비교하기  (0) 2021.01.22
자바(JAVA)에서 배열(Arrays), 리스트(List) 정렬하기  (0) 2021.01.22
Optional<T>  (0) 2020.05.07
Stream, 스트림  (0) 2020.05.06
람다 표현식(Lambda Expressions)  (0) 2020.05.03
donaricano-btn
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함