
package me.whiteship.chapter04.item20.defaultmethod;
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();
}
}
ZonedDateTime getZonedDateTime(String zoneString);
// default ZonedDateTime getZonedDateTime(String zoneString) {
// return ZonedDateTime.of(getLocalDateTime(), getZonedId(zoneString));
// }
}
ZonedDateTime getZonedDateTime(String zoneString); 가 추가된 경우 기존에 implements한 코드들에서 전부 해당 기능이 깨지기 때문에 컴파일 오류가 난다.
default 메소드로 제공할 수 있으면 굉장히 손쉽게 기능을 확장하고 인터페이스를 진화시킬 수 있다.
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();
}
public static void main(String... args) {
TimeClient myTimeClient = new SimpleTimeClient();
System.out.println(myTimeClient);
System.out.println(myTimeClient.getZonedDateTime("America/Los_Angeles"));
}
}

package me.whiteship.chapter04.item20.typeframework;
public interface SingerSongwriter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}
package me.whiteship.chapter04.item20.typeframework;
public interface Singer {
AudioClip sing(Song song);
}
package me.whiteship.chapter04.item20.typeframework;
public interface Songwriter {
Song compose(int shartPosition);
}
package me.whiteship.chapter04.item18;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
// 코드 18-1 잘못된 예 - 상속을 잘못 사용했다! (114쪽)
public class InstrumentedHashSet<E> extends HashSet<E> {
// 추가된 원소의 수
private int addCount = 0;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
private int getAddCount() {
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("틱", "탁탁", "펑"));
System.out.println(s.getAddCount());
}
}
이것은 상위클래스의 동작에 따라 구현이 달라질 수 있게 된다.
package me.whiteship.chapter04.item18;
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(); }
}
추상클래스라고 가정해서 인터페이스를 통해서 개발을 했다고 구현했다고 쳐보자.
package me.whiteship.chapter04.item18;
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(); }
}
인터페이스를 가정한 경우 내부구현이 없으니까 구현이 바뀔게 없다. 굉장히 안전한 확장 방법이다.
상위클래스에서 메소드가 추가될 수도 있다. 추가된 경우 추가된 것을 확인 가능하다.
default, static을 사용해서 추가 구현을 할 수 있다.

package me.whiteship.chapter04.item20.skeleton;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
// 코드 20-1 골격 구현을 사용해 완성한 구체 클래스 (133쪽)
public class IntArrays {
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);
// 다이아몬드 연산자를 이렇게 사용하는 건 자바 9부터 가능하다.
// 더 낮은 버전을 사용한다면 <Integer>로 수정하자.
return new AbstractList<>() {
@Override public Integer get(int i) {
return a[i]; // 오토박싱(아이템 6)
}
@Override public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; // 오토언박싱
return oldVal; // 오토박싱
}
@Override public int size() {
return a.length;
}
};
}
public static void main(String[] args) {
int[] a = new int[10];
for (int i = 0; i < a.length; i++)
a[i] = i;
List<Integer> list = intArrayAsList(a);
Collections.shuffle(list);
System.out.println(list);
}
}

모든 규약에 다 맞추려면 엄청나게 해야 하는데 AbstractClass를 사용하면 일부만 구현해도 된다.
package me.whiteship.chapter04.item20.multipleinheritance;
public abstract class AbstractCat {
protected abstract String sound();
protected abstract String name();
}
package me.whiteship.chapter04.item20.multipleinheritance;
public class MyCat extends AbstractCat implements Flyable {
private MyFlyable myFlyable = new MyFlyable();
@Override
protected String sound() {
return "인싸 고양이 두 마리가 나가신다!";
}
@Override
protected String name() {
return "유미";
}
public static void main(String[] args) {
MyCat myCat = new MyCat();
System.out.println(myCat.sound());
System.out.println(myCat.name());
myCat.fly();
}
@Override
public void fly() {
this.myFlyable.fly();
}
private class MyFlyable extends AbstractFlyable {
@Override
public void fly() {
System.out.println("날아라.");
}
}
}
package me.whiteship.chapter04.item20.multipleinheritance;
public interface Flyable {
void fly();
}

위 내용이 시뮬레이트한 다중상속이다.


package me.whiteship.chapter04.item20.templatemethod;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public abstract class FileProcessor {
private String path;
public FileProcessor(String path) {
this.path = path;
}
public final int process() {
try(BufferedReader reader = new BufferedReader(new FileReader(path))) {
int result = 0;
String line = null;
while((line = reader.readLine()) != null) {
result = getResult(result, Integer.parseInt(line));
}
return result;
} catch (IOException e) {
throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
}
}
protected abstract int getResult(int result, int number);
}
package me.whiteship.chapter04.item20.templatemethod;
public class Plus extends FileProcessor {
public Plus(String path) {
super(path);
}
@Override
protected int getResult(int result, int number) {
return result + number;
}
}
package me.whiteship.chapter04.item20.templatemethod;
public class Client {
public static void main(String[] args) {
FileProcessor fileProcessor = new Plus("number.txt");
System.out.println(fileProcessor.process());
}
}
1
2
3
4
5

BiFunction : 2개의 인자를 받아서 하나의 결과값을 돌려주는 어떤 오퍼레이션을 정의할 때 쓸 수 있는 함수
package me.whiteship.chapter04.item20.templatemethod;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.function.BiFunction;
public abstract class FileProcessor {
private String path;
public FileProcessor(String path) {
this.path = path;
}
public final int process(BiFunction<Integer, Integer, Integer> operator) {
try(BufferedReader reader = new BufferedReader(new FileReader(path))) {
int result = 0;
String line = null;
while((line = reader.readLine()) != null) {
result = operator.apply(result, Integer.parseInt(line));
}
return result;
} catch (IOException e) {
throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
}
}
protected abstract int getResult(int result, int number);
}
package me.whiteship.chapter04.item20.templatemethod;
public class Client {
public static void main(String[] args) {
FileProcessor fileProcessor = new Plus("number.txt");
System.out.println(fileProcessor.process((a,b) -> a + b));
}
}
package me.whiteship.chapter04.item20.templatemethod;
public class Client {
public static void main(String[] args) {
FileProcessor fileProcessor = new Plus("number.txt");
System.out.println(fileProcessor.process(Integer::sum));
}
}

package me.whiteship.chapter04.item20.objectmethod;
public interface MyInterface {
default String toString() {
return "myString";
}
default int hashCode() {
return 10;
}
default boolean equals(Object o) {
return true;
}
}
컴파일 에러가 나온다.

default는 메소드의 진화와 관련이 있다. 메소드에 새로운 기능을 추가할 때 기존의 인터페이스를 구현한 모든 클래스들을 그대로 유지하면서도 아주 간단한 추가 기능을 넣어줄 수 있는 그런 기능을 만들고 싶었다.
이런 설계에 굉장히 큰 변화, 그리고 위험을 가져다 주는 이런 변화를 끼워넣고자 함이 아니기 때문이다.
package me.whiteship.chapter04.item20.objectmethod;
public class MyClass extends Object implements MyInterface {
}
이렇게 되는 경우 Object의 equals를 사용하게 되는지 MyInterface에서 사용하는 equals를 사용하게 되는지를 모르게 된다.
equals, toString 등 interface에서는 어차피 변수가 들어갈 수 없으므로 해당 메소드를 지원할 수 없다.