4. 기초_인터페이스에서의_메서드
default 메서드
메서드 앞에 키워드 defalut 를 붙이며 일반 메서드와 같이 구현부가 있어야 합니다.
접근 제어자가 public이며 생략이 가능합니다.
자식 클래스에서 default 메서드를 오버라이딩하여 재정의가 가능합니다.
보통 인터페이스를 구현한 이후, 수정 과정에서 인터페이스 모든 구현체에게 수정 없이 광역으로 함수를 만들어주고 싶을 때 사용합니다. 대신, 모든 구현체가 원하는 값을 리턴하도록 보장하기 위해서 @implSpec 이라는 자바 doc 태그를 사용하여 문서화 해줘야 합니다.
주의해야 할 점은 인터페이스는 Object 클래스를 상속받지 않기 때문에, Object 클래스가 제공하는 기능 (equals, hashCode)는 기본 메서드로 제공할 수 없습니다. 따라서, 구현체가 직접 재정의 해줘야 합니다.
허용되지 않는 키워드 abstract: default 메서드는 추상 메서드와는 달리 구현을 제공해야 하므로, abstract 키워드와 함께 사용할 수 없습니다. static: default 메서드는 인스턴스 메서드로 설계되어 있기 때문에 static 키워드와 함께 사용할 수 없습니다. final: default 메서드는 인터페이스를 구현하는 클래스에서 재정의할 수 있으므로 final 키워드와 함께 사용할 수 없습니다. private: private 메서드는 접근이 제한되며, 인터페이스의 다른 메서드와 다르게 동작합니다. 따라서 default와 함께 사용할 수 없습니다.
interface Calculator {
int plus(int i, int j);
int multiple(int i, int j);
// default로 선언함으로 메소드를 구현할 수 있다.
default int sub(int i, int j){
return i - j;
}
}
// Calculator인터페이스를 구현한 MyCalculator클래스
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 Main {
public static void main(String[] args){
MyCalculator mycal = new MyCalculator();
// 인터페이스 타입으로 업캐스팅
Calculator cal = (Calculator) mycal; // 괄호 생략해도 됨
// 인스턴스의 인터페이스 디폴트 메서드 호출
int value = cal.sub(5, 10);
System.out.println(value); // -5
}
}
인터페이스의 디폴트 메서드를 호출하기 위해서는, 객체의 타입이 반드시 인터페이스 타입으로 업캐스팅 되어야 합니다.
@implSpec 주석 문서
인터페이스를 구현한 후, 수정과정에서 모든 구현체에게 수정 없이, 광역으로 함수를 만들어줘야 할 때 사용됩 니다. 예를 들어 A, B 업체가 협업하여 어떤 인터페이스를 구현하고 있는데, 개발자가 해당 인터페이스 기능 을 추가하기 위해서 추상 메서드를 추가하면, 이 인터페이스를 구현하고 있는 모든 클래스에도 수정을 가해야 합니다. 이럴 때는 인터페이스에 디폴트 메서드를 추가하면, 추가된 메서드를 구현체 클래스에서 굳이 구현하지 않아도 에러 없이 사용할 수 있고, 나중에 필요하면 재정의를 통해 업그레이드된 메서드 구현도 가능하집니다. 대신에 모든 구현체가 원하는 값을 return 하게 보장하기 위해 @implSpec 자바 doc 태그를 사용하여 문서화 해줘야 합니다.
interface IJson {
String printJson(); // 추상 메서드
/**
* @impspec
* printJson()의 결과를 대문자 변환한다.
*/
default void uppperString() { // default 메서드
// 구현 로직상, 추상 메서드인 printJson()의 반환 값이 정상적인 값이 될수도 있고 null이되서 예외 오류가 발생할 수 있으니 @impspec 문서화를 한다.
String text = printJson().toUpperCase();
System.out.println(text);
}
}
클래스의 다중 상속 문제점
동일한 메서드 명이 겹치는 경우 죽음의 다이아몬드 현상이 발생합니다. 그래서 다중 상속을 금지시켰는데, 이번엔 인터페이스에 디폴트 메서드라는 것이 추가되면서 인터페이스를 다중 구현할 때 클래스 다중 상속 문제와 똑같은 문제가 발생하게 되었습니다. 따라서, 인터페이스 다중 구현에 한해서 자바에서는 다음과 같은 규칙을 정하였습니다.
다중 인터페이스들 간의 디폴트 메서드 충돌
같은 명의 디폴트 메서드를 가진 두 인터페이스를 하나의 클래스에 구현하고, 아무런 조치를 취하지 않으면 컴파일 자체가 되지 않는다.
인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩하여 하나로 통합합니다.
인터페이스의 디폴트 메서드와 부모 클래스 메서드 간의 충돌
자식 클래스에서 인터페이스와 부모 클래스를 동시에 extends / implement 하였을 때, 디폴트 메서드와 인스턴스 메서드 간의 충돌을 가정한 상황에서는, 부모 클래스의 메서드가 상속되고, 디폴트 메서드는 무시 됩니다. 만일 인터페이스 쪽의 디폴트 메서드를 사용할 필요가 있다면, 필요한 쪽의 메서드와 같은 내용으로 그냥 오버라이딩 해버립니다.
default 메소드의 super
상위 클래스를 상속하고 상위 메소드를 오버라이딩하여 재정의하였을 때, 부모 메서드를 호출할 일이 생긴다면 super 키워드를 통하여 부모 메서드를 호출할 수 있습니다. 이와 같이 인터페이스도 디폴트 메서드를 구현한 클래스에서 오버라이딩 하였을 때, super 키워드를 통해 인터페이스의 원래의 디폴트 메서드를 호출할 수 있습니다.
다만 문법이 클래스 방식과 약간 차이가 있습니다.
인터페이스의 super는 다음과 같은 구성으로 호출된다. 인터페이스명.super.디폴트메서드
interface IPrint{
default void print(){
System.out.println("인터페이스의 디폴트 메서드 입니다.");
}
}
class MyClass implements IPrint {
@Override
public void print() {
IPrint.super.print(); // 인터페이스의 super 메서드를 호출
System.out.println("인터페이스의 디폴트 메서드를 오버라이딩한 메서드 입니다.");
}
}
public class Main {
public static void main(String[] args) {
MyClass cls = new MyClass();
cls.print();
}
}
static 메서드
인스턴스 생성과 상관없이 인터페이스 타입으로 접근해 사용할 수 있는 메서드
일반 클래스의 static 메소드와 다를 바 없음
해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때, 인터페이스에 스태틱 메소드로 제공하기도 한다.
private 메서드
자바9 버전에 추가된 메서드
인터페이스테 default, static 메서드가 생긴 후, 이러한 메서드들의 로직을 공통화하고, 재사용하기 위해 생신 메서드
private 메서드는 구현부를 가져야 함
단, private 메서드는 인터페이스 내부에서만 돌아가는 코드 인터페이스를 구현한 클래스에서 재사용하거나 재정의 할 수 없음 따라서, 인터페이스 내부에서 private 메서드를 호출할 때, default 메서드 내부에서 호출해야 하며, 만일 private staticㅁ 키워드를 붙임 메서드는 static 메서드에서만 호출이 가능
어렵게 생각할 것 없이, 클래스에서도 private 메서드는 호출 메서드에서 private 내부 메서드를 호출하여 사용하는 것과 같이, 인터페이스에서도 같다. 하지만, 인터페이스는 클래스가 아니기 때문에 this 키워드를 사용할 수 없다.
인터페이스 상수는 private으로 만들 수 없다. 인터페이스는 실제 객체는 아니지만 서로 간의 약속으로 사용된다. 정해진 약속을 한 쪽에서 일방적으로 수정하게 되면 문제가 발생할 수 있다. 따라서, 인터페이스 에 선언하는 필드들은 자동으로 public static final 상수가 변수가 된다.
인터페이스의 private 을 추상 클래스에 적용했을 때 final과 뭐가 다를까
둘 다 자식 클래스에서 해당 메서드를 재정의 할 수 없습니다. 하지만, 그 목적과 쓰임새가 다릅니다.
final 메서드는 public, protected, default 접근 지정자를 가질 수 있어, 자식 클래스에서 접근하여 사용은 가능하나, 재정의는 하지 못합니다. 반면 private 메서드는 접근 자체가 불가능합니다. final 키워드는 메서드의 구현을 자식 클래스에서 변경하지 못하도록 보장하는 역할을 하는 반면, private 키워드는 메서드를 클래스 내부에서만 사용할 수 있게 하여 캡슐화를 강화합니다.
Ref
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC
Last updated