목차
- 기본 메소드 (Default Methods)
- 스태틱 메소드 (Static Methods)
- 참고 자료
기본 메서드 (Default Methods)
기본 메서드(Default Methods) ?
- 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법
- 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있다
- 기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다
- 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다
- 반드시 문서화 할 것. (@implSpec 자바독 태그 사용)
- Object가 제공하는 기능 (equals, hasCode)는 기본 메소드로 제공할 수 없다
- 구현체가 재정의해야 한다
- 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다
- 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다
- 인터페이스 구현체가 재정의 할 수도 있다.
기본 메서드가 필요한(등장) 이유?
// 인터페이스 Foo
public interface Foo {
void printName();
}
// 인터페이스 Foo 구현한 클래스 DefaultFoo
public class DefaultFoo implements Foo{
@Override
public void printName() {
System.out.println("DefaultFoo");
}
}
이때 이름을 전부 대문자로 출력할 수 있는 메소드를 만들어달라고 요구사항이 발생한다면?
// 인터페이스 Foo
public interface Foo {
void printName();
void printNameUpperCase();
}
// 인터페이스 Foo 구현한 클래스 DefaultFoo
public class DefaultFoo implements Foo{
@Override
public void printName() {
System.out.println("DefaultFoo");
}
}
DefaultFoo 클래스에서는 추상화된 메서드인 printNameUpperCase() 메소드를 구현하지 않았기 때문에
컴파일 에러가 발생한다.
지금은 Foo 인터페이스를 상속받은 클래스가 1개지만 만약 엄청 많다면?
다 하나하나 구현을 하는 게 맞을까?
자바에서는 default 메서드를 제공한다.
// 인터페이스 Foo
public interface Foo {
void printName();
default void printNameUpperCase(){
System.out.println("FOO");
}
}
// 인터페이스 Foo 구현한 클래스 DefaultFoo
public class DefaultFoo implements Foo{
@Override
public void printName() {
System.out.println("DefaultFoo");
}
}
default 메서드 활용하기
public interface Foo {
void printName();
default void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
String getName();
}
public class DefaultFoo implements Foo{
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(this.name);
}
@Override
public String getName(){
return this.name;
}
}
public class Test {
public static void main(String[] args) {
Foo foo = new DefaultFoo("jeu");
foo.printName();
foo.printNameUpperCase();
}
}
기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다
주의: 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다
public class Test {
public static void main(String[] args) {
Foo foo = new DefaultFoo(null);
foo.printName();
foo.printNameUpperCase(); // 런타임 에러 발생 null.toUpperCase();
}
}
현재 DefaultFoo 객체를 생성하면서 생성자로 인스턴스 변수 이름에 null 값을 넘기고 있다.
이름 출력은 그냥 null 값을 출력하지만
printNameUpperCase() 에서는 null.toUpperCase() 동작을 하여 런타임 에러가 발생한다.
반드시 문서화 하자. (@implSpec 자바독 태그 사용)
public interface Foo {
void printName();
/**
* getName()의 리턴값인 문자열을 대문자로 바꿔서 반환한다.
*
* @return String
* @implSpec
* 이 메서드는 toUpperCase()를 사용하기 때문에 getName() 리턴값이 null이면 NullPointException이 발생합니다.
*/
default void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
String getName();
}
인터페이스 구현체가 재정의 할 수도 있다.
public interface Foo {
void printName();
default void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
String getName();
}
public class DefaultFoo implements Foo{
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(this.name);
}
@Override
public String getName(){
return this.name;
}
// 재정의
@Override
public void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
}
인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다
public interface Foo {
void printName();
default void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
String getName();
}
public interface Bar extends Foo{
void printNameUpperCase();
}
물론 Bar 인터페이스를 구현하는 구현체는 추상 메소드로 바뀐 printNameUpperCase()를 다시 구현해야 한다.
다이아몬드 Problem
다이아몬드 문제란?
다이아몬드 문제는 다중 상속 시, 동일한 메서드를 여러 조상(여기서는 인터페이스)으로부터 상속받을 때 발생한다.
자바는 이 문제를 명시적으로 해결하도록 요구하며, 두 인터페이스 중 어떤 메서드를 사용할지 지정하거나, 충돌하는 메서드를 직접 오버라이딩하는 방식으로 해결할 수 있다.
public interface Bar{
default void printNameUpperCase(){
System.out.println("Bar");
}
}
public interface Foo {
void printName();
default void printNameUpperCase(){
System.out.println(getName().toUpperCase());
}
String getName();
}
// 컴파일 에러 발생
public class DefaultFoo implements Foo, Bar{
private String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(this.name);
}
@Override
public String getName(){
return this.name;
}
}
현재 DefaultFoo 클래스를 보면 Foo 인터페이스와 Bar 인터페이스 모두 구현하고 있다.
하지만 메소드 중복이 일어난다. (다이아몬드 문제)
이때 자바는 컴파일 에러를 발생시킨다.
스태틱 메서드 (Static Methods)
해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다.
활용
public interface Foo {
void printName();
// 스태틱 메소드
static void printAny(){
System.out.println("static");
}
}
public class Test {
public static void main(String[] args) {
Foo.printAny(); // static 출력
}
}
참고 자료
'Computer Sience > Java' 카테고리의 다른 글
[JAVA8] Stream (0) | 2024.10.02 |
---|---|
[JAVA8] 자바 8 API의 기본 메소드와 스태틱 메소드 (6) | 2024.10.02 |
[JAVA8] 메소드 래퍼런스 (0) | 2024.09.30 |
[JAVA8] 람다 표현식 + 변수 캡쳐 & 변수 쉐도잉 (0) | 2024.09.30 |
[JAVA8] 자바에서 제공하는 함수형 인터페이스 (0) | 2024.09.30 |