⇒ 인터페이스, 추상 클래스
공통점
두 매커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있음.
차이점
추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 함
인터페이스가 선언한 메서드를 모두 정의하고 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급됨
기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있음
🔴 두 클래스가 같은 추상 클래스를 확장하길 원한다면, 그 추상 클래스는 계층구조상 두 클래스의 공통 조상이어야 함 → 기존 클래스 위에 새로운 추상 클래스를 끼워넣기 어려움
🟢 클래스 선언에 implements구문만 추가해서 인터페이스가 요구하는 메서드를 추가할 수 있음
인터페이스는 믹스인 정의에 안성맞춤임
<aside> 💡
믹스인?
클래스가 구현할 수 있는 타입
프로그래머가 특정 코드를 다른 클래스에 삽입 할 수 있도록 하는 프로그래밍 개념
</aside>
🔴 추상 클래스로는 믹스인을 정의할 수 없음
🟢 인터페이스는 가능함
→ 믹스인 인터페이스를 정의해두고 여러 클래스에서 기능을 재사용할 수 있음
인터페이스로는 계층구조가 없는 타입 프레임워크를 만들 수 있음
🔴 타입을 계층적으로 정의하는 것이 어려운 개념의 경우 고도비만 계층구조가 만들어질 수 있음
public abstract class Singer {
abstract void sing();
}
public abstract class SongWriter {
abstract void compose();
}
// Singer이면서 SongWriter인 클래스가 필요하다면?
public abstract class SingerSongWriter {
abstract void actSensitive();
abstract void sing();
abstract void compose();
}
→ 새로운 SingerSongWriter을 만드는 방법밖에 없어 클래스를 상속하면서 복잡해짐
🟢 계층구조가 없는 경우 여러 인터페이스를 사용해도 문제가 되지 않음
public interface Singer{
public void sing();
}
public interface SongWriter{
public void compose();
}
public class People implements Singer, SongWriter{
@Override
public void sing() {}
@Override
public void compose() {}
}
public interface SingerSongWriter extends Singer, SongWriter{
public void actSensitive();
}
→ Singer와 SongWriter을 모두 구현하고 새로운 메서드까지 추가한 제 3의 인터페이스를 정의할 수도 있음
래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이 됨
🔴 타입을 추상 클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속 뿐임
→ 상속해서 만든 클래스는 래퍼 클래스보다 활용도가 떨어지고 깨지기 더 쉬움 (부모 클래스의 메서드가 변경되면 이를 상속받는 모든 클래스에 영향을 주게 됨)
🟢 래퍼 클래스는 감싸고 있는 클래스의 구현을 변경하지 않고도 기능을 추가할 수 있음
→ 래퍼 클래스는 감싸고 있는 클래스의 메서드가 변경되더라도 그 자체로는 영향받지 않음
인터페이스에서 디폴트 메서드를 제공할 수 있음
🟢 개발자의 수고를 덜어줄 수 있음
📛 디폴트 메서드의 제약
equals, hashCode 는 디폴트 메서드로 제공해서는 안됨
→ 객체의 상태에 따라 다르게 동작해야 하는데 디폴트 메서드에서 같게 해버리면,,
인터페이스는 인스턴스 필드를 가질 수 없음
public이 아닌 정적 멤버도 가질 수 없음 (private 정적 메서드 예외)
직접 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없음
→ 인터페이스와 추상 클래스의 장점을 모두 취할 수 있음
템플릿 메서드 패턴 사용하기
골격 구현을 사용해 완성한 구체 클래스 예시
static List<Integer> intArrayAsList(int[] array){
Objects.requireNonNull(array);
return new AbstractList<Integer>() {
@Override
public Integer get(final int index) {
return array[index];
}
@Override
public Integer set(final int index, final Integer element) {
int oldValue = array[index];
array[index] = element;
return oldValue;
}
@Override
public int size() {
return array.length;
}
};
}
-> 추상 클래스처럼 구현을 도와주면서 추상 클래스로 타입을 정의할 때 오는 제약에서 자유로움