이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.
보통 인터페이스를 구현한다고 하면, void printName()을 클래스에서 인터페이스를 implements하여 메서드를 Override하여 구현 하는 것이다. 이를 구현하지 않으면 컴파일 에러가 발생한다.
하지만 default 라는 키워드를 붙인 메서드는 따로 구현하지 않아도 특정 기능을 제공할 수 있다.
자바가 제공하는 Collection에도 removeIf()처럼 default 키워드가 붙어있는 메서드는 Collection을 구현한 모든 하위 클래스들은 removeIf()기능을 갖게 된 것이라 할 수 있다.
이번에는 인터페이스에 반환 타입이 String인 메서드와, 이를 구현하면서 파라미터를 받는 생성자까지 만들어둔 클래스가 있다고 하자.
인터페이스에는 대문자로 이름을 출력해주는 printNameUpperCase()라는 이름을 가진 메서드를 구현해놨다.
이를 실행하면 인터페이스의 메서드를 재정의하여 구현된 printName()에서는 집어넣은 그대로 출력되고, 인터페이스에서 미리 구현한 default 메서드는 printNameUpperCase()라는 이름대로 그대로 출력되는 것을 확인할 수 있다.
하지만, 인터페이스에서 구현된 default 메서드가 항상 제 기능이 제공될 거라는 보장이 없다.
getName()에서 무슨 값이 올지 모르기 때문이다. 만약 null이 오면 RuntimeException이 발생할 수 있다.
그런 빈틈을 방지하려면 이 default 메서드를 사용하는 클래스들이 이해하고 사용할 수 있도록 문서화를 잘 해야 한다.
예를 들어, @implSpec
을 활용해서 내가 제공하고자 하는 구현체가 어떤 일을 하는지(상세 구현 내용에 대해) 설명해주면 된다.
또한 실제로 문제가 된다면, 이를 클래스쪽에서 재정의할 수도 있다.
단, default 메서드를 제공할 수 없는 경우도 있다.
equals()나 hashcode() 같은 경우를 예로 들 수 있다. Object 클래스에서 제공되는 기능들은 default method로 쓸 수 없다.
Default method 'toString' overrides a member of 'java.lang.Object'
Default method인 'toString'을 Object 클래스의 멤버로 재정의하세요
인터페이스에 추상 선언하는 것은 괜찮다. 하지만, 이걸 선언 하더라도 모든 Object가 구현하는 메서드 중 하나이기 때문에 의미가 없다.
인터페이스가 가지는 특별한 제약사항이 있는 경우에 쓰면 된다. 그런 제약사항을 걸어놓으면 그에 따른 문서화도 필수이다.
분명 Foo 인터페이스에서는 printNameUpperCase()가 default 메서드로 구현되어 있었다.
Foo를 상속받은 Bar 인터페이스에서는 만약 이 default로 구현되어있는 printNameUpperCase()기능을 굳이 제공하고 싶지 않다면, 위 사진처럼 다시 추상 메서드로 선언하면 된다.
단, 이 Bar 인터페이스를 클래스에서 따로 구현해줘야 한다.
추상 메서드로 변경하지 않으면 default 메서드였던 printNameUpperCase()의 기능이 제공된다.
Foo인터페이스와 Bar인터페이스에서 똑같은 default 메서드인 printNameUpperCase()를 구현했고, DefaultFoo 클래스에는 Foo,Bar를 구현한다고 하면 컴파일에러가 발생한다.
양쪽에 똑같은 default 메서드인 printNameUpperCase()가 있으므로 충돌이 발생하는 것이다.
자바에서는 이를 판단할 수 없기에, 이런 상황에서는 두 인터페이스 중 하나의 default 메서드를 재정의해야 한다.
defauat 메서드같은 구현체들은 인스턴스가 사용할 수 있다. 하지만, 해당 인터페이스를 구현한 모든 인스턴스/타입에 관련되어 있는 utility/helper 메서드를 제공하고 싶은 경우에는 static으로 제공할 수 있다.
이렇게 Foo라는 타입을 가지고 참조해서 메서드를 호출해서 쓸 수 있다.