1. 자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있고 기존 클래스도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public interface TimeClient {
void setTime(int hour, int minute, int second);
void setDate(int day, int month, int year);
void setDateAndTime(int day, int month, int year, int hour, int minute, int second);
LocalDateTime getLocalDateTime();
}
위와 같은 TimeClient인터페이스도 언젠가 시간이 지나다보면 변경을 하고싶어지는 경우가 있을 수 있습니다. 예를 들어 새로운 메서드를 추가하고싶지만 인터페이스에 메서드를 추가하게되면 TimeClient의 구현체들은 모두 TimeClient의 인터페이스에 새로 추가한 메서드를 모두 구현해줘야하는데 현실적으로 불가능합니다. 그래서 자바8 이후로 인터페이스에 default메서드를 구현하면 이러한 문제를 방지할 수 있습니다.
추가된 default 메서드
static ZoneId getZonedId(String zoneString) {
try {
return ZoneId.of(zoneString);
} catch (DateTimeException e) {
System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead.");
return ZoneId.systemDefault();
}
}
default ZonedDateTime getZonedDateTime(String zoneString) {
return ZonedDateTime.of(getLocalDateTime(), getZonedId(zoneString));
}
default메서드를 추가해도 TimeClient의 구현체들은 이 default메서드를 재정의하지 않아도 됩니다.
2. 인터페이스는 믹스인(mixtin) 정의에 안성맞춤이다. (선택적인 기능 추가)
package me.whiteship.chapter04.item20.defaultmethod;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class SimpleTimeClient implements TimeClient {
private LocalDateTime dateAndTime;
public SimpleTimeClient() {
dateAndTime = LocalDateTime.now();
}
public void setTime(int hour, int minute, int second) {
LocalDate currentDate = LocalDate.from(dateAndTime);
LocalTime timeToSet = LocalTime.of(hour, minute, second);
dateAndTime = LocalDateTime.of(currentDate, timeToSet);
}
public void setDate(int day, int month, int year) {
LocalDate dateToSet = LocalDate.of(day, month, year);
LocalTime currentTime = LocalTime.from(dateAndTime);
dateAndTime = LocalDateTime.of(dateToSet, currentTime);
}
public void setDateAndTime(int day, int month, int year,
int hour, int minute, int second) {
LocalDate dateToSet = LocalDate.of(day, month, year);
LocalTime timeToSet = LocalTime.of(hour, minute, second);
dateAndTime = LocalDateTime.of(dateToSet, timeToSet);
}
public LocalDateTime getLocalDateTime() {
return dateAndTime;
}
public String toString() {
return dateAndTime.toString();
}
}
예를들어 위와같이 TimeClient인터페이스를 구현하는 SimpleTimeClient클래스가 추가적으로 다른 인터페이스를 추가해서 구현을 할 수 있다.
여러개의 인터페이스를 구현가능
public class SimpleTimeClient implements TimeClient, AutoCliseable {
// 코드 생략
}
3. 계층구조가 없는 타입 프레임워크를 만들 수 있다.
타입간에 계층구조가 명확하지 않은 경우가 있는데 그때 여러 인터페이스를 조합해서 하나의 타입으로 만들 수 있다.
Singer
public interface Singer {
AudioClip sing(Song song);
}
Songwriter
public interface Songwriter {
Song compose(int shartPosition);
}
SingerSongwriter
public interface SingerSongwriter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}
가수와 작사의 관계는 계층관계가 아닙니다. 하지만 가수와 작사를 합쳐서 싱어송라이터라는 새로운 타입을 선언할 수 있습니다.
4. 래퍼 클래스와 함께 사용하면 인터페이스는 기능을 향상 시키는 안전하고 강력한 수단이 된다.
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
// 코드 18-3 재사용할 수 있는 전달 클래스 (118쪽)
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator<E> iterator() { return s.iterator(); }
public boolean add(E e) { return s.add(e); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection<?> c)
{ return s.containsAll(c); }
public boolean addAll(Collection<? extends E> c)
{ return s.addAll(c); }
public boolean removeAll(Collection<?> c)
{ return s.removeAll(c); }
public boolean retainAll(Collection<?> c)
{ return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override public boolean equals(Object o)
{ return s.equals(o); }
@Override public int hashCode() { return s.hashCode(); }
@Override public String toString() { return s.toString(); }
}
아이템 18에서 살펴본것과 같이 컴포지션을 사용하여 Set인터페이스를 구현하는 ForwardingSet은 캡슐화가 되어있기때문에 안전하게 사용가능합니다.
5. 구현이 명백한 것은 인터페이스의 디폴트 메서드를 사용해 프로그래머의 일감을 덜어 줄 수 있다.
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public interface TimeClient {
void setTime(int hour, int minute, int second);
void setDate(int day, int month, int year);
void setDateAndTime(int day, int month, int year, int hour, int minute, int second);
LocalDateTime getLocalDateTime();
static ZoneId getZonedId(String zoneString) {
try {
return ZoneId.of(zoneString);
} catch (DateTimeException e) {
System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead.");
return ZoneId.systemDefault();
}
}
default ZonedDateTime getZonedDateTime(String zoneString) {
return ZonedDateTime.of(getLocalDateTime(), getZonedId(zoneString));
}
}
'개발관련 서적 정리 > Effective Java' 카테고리의 다른 글
아이템 23. 태그 달린 클래스 보다는 클래스 계층 구조를 활용하라 (0) | 2023.02.05 |
---|---|
아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2023.02.05 |
아이템 18. 상속보다는 컴포지션을 사용하라 (1) | 2023.02.05 |
아이템 17. 변경 가능성을 최소화 하라 (1) | 2023.02.05 |
아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.02.05 |