아이템 20. 추상 클래스 보다는 인터페이스를 우선하라

이펙티브 자바

아이템 20. 추상 클래스 보다는 인터페이스를 우선하라

자바는 단일상속만 되니 추상클래스는 단 하나만 구현할수 있다.
인터페이스는 어떠한 상속을 했던간에 인터페이스가 선언한 메소드를 구현하고 있으면 같은 타입으로 인식한다.

그래서 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을수 있다.

인터페이스의 장점

  • 믹스인(mixin)정의에 안성맞춤이다.(Comparable)
  • 인터페이스로는 계층구조가 없는 타입 프레임워크를 만들수있다.
1
2
3
4
5
6
7
8
9
10

package com.github.sejoung.codetest.interfaces;

public class Song {
private String composer;
private String lyrics;
private String arrangement;
}


1
2
3
4
5
6
7
8
9
10
11

package com.github.sejoung.codetest.interfaces;

import java.applet.AudioClip;

public interface Singer {
AudioClip sing(Song song);

}


1
2
3
4
5
6
7
8

package com.github.sejoung.codetest.interfaces;

public interface SongWiter {
Song compose(int chartPosition);
}


1
2
3
4
5
6
7
8
9
10
11

package com.github.sejoung.codetest.interfaces;

import java.applet.AudioClip;

public interface SingerSongWiter extends Singer, SongWiter {
AudioClip strum();
void actSensitive();
}


이런식으로 정의 할수 있는데 계층 구조로 만들려면 아래처럼 만들어야 중복되는 메소드를 만들어야 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.github.sejoung.codetest.abstracts;

import com.github.sejoung.codetest.interfaces.Song;

import java.applet.AudioClip;

public abstract class Singer {
abstract AudioClip sing(Song song);

}



1
2
3
4
5
6
7
8
9
10

package com.github.sejoung.codetest.abstracts;

import com.github.sejoung.codetest.interfaces.Song;

public abstract class SongWiter {
abstract Song compose(int chartPosition);
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package com.github.sejoung.codetest.abstracts;

import com.github.sejoung.codetest.interfaces.Singer;
import com.github.sejoung.codetest.interfaces.Song;
import com.github.sejoung.codetest.interfaces.SongWiter;

import java.applet.AudioClip;

public abstract class SingerSongWiter {
abstract AudioClip sing(Song song);
abstract Song compose(int chartPosition);
abstract AudioClip strum();
abstract void actSensitive();
}


위처럼 되는데 조합폭발이라고 말하는 클래스가 만들어 지면 매서드가 폭발된다.

  • 래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 항상시키는 안전하고 강력한 수단이 된다.

인터페이스에도 디폴드 메서드가 제공되니 개발자의 노고를 덜어줄수있다.

Collection 인터페이스에서 제공하는 removeIf 메소드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

/**
* Removes all of the elements of this collection that satisfy the given
* predicate. Errors or runtime exceptions thrown during iteration or by
* the predicate are relayed to the caller.
*
* @implSpec
* The default implementation traverses all elements of the collection using
* its {@link #iterator}. Each matching element is removed using
* {@link Iterator#remove()}. If the collection's iterator does not
* support removal then an {@code UnsupportedOperationException} will be
* thrown on the first matching element.
*
* @param filter a predicate which returns {@code true} for elements to be
* removed
* @return {@code true} if any elements were removed
* @throws NullPointerException if the specified filter is null
* @throws UnsupportedOperationException if elements cannot be removed
* from this collection. Implementations may throw this exception if a
* matching element cannot be removed or if, in general, removal is not
* supported.
* @since 1.8
*/
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}


디폴트 메소드로는 boolean equals(Object o), int hashCode(); 는 제공하면 안된다.

인터페이스와 추상골격 구현 클래스의 장점을 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다.(템플릿 메서드 패턴)

관례상 인터페이스 이름이 Interface면 그 골격 구현클래스의 이름은 AbstractInterface라고 명명한다.(이부분은 Abstract 클래스인데 음 모르겠다.) 템플릿 메소트 패턴을 설명하는듯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

package com.github.sejoung.codetest.interfaces;

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<Integer>() {
@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);
}
}


위에서는 AbstractList를 구현하므로써 내가 필요한 로직을 구현한것을 볼수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

package com.github.sejoung.codetest.interfaces;

import java.util.Map;
import java.util.Objects;

// 코드 20-2 골격 구현 클래스 (134-135쪽)
public abstract class AbstractMapEntry<K, V>
implements Map.Entry<K, V> {
// 변경 가능한 엔트리는 이 메서드를 반드시 재정의해야 한다.
@Override
public V setValue(V value) {
throw new UnsupportedOperationException();
}

// Map.Entry.equals의 일반 규약을 구현한다.
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> e = (Map.Entry) o;
return Objects.equals(e.getKey(), getKey())
&& Objects.equals(e.getValue(), getValue());
}

// Map.Entry.hashCode의 일반 규약을 구현한다.
@Override
public int hashCode() {
return Objects.hashCode(getKey())
^ Objects.hashCode(getValue());
}

@Override
public String toString() {
return getKey() + "=" + getValue();
}
}


위처럼 인터페이스의 메소드 모두가 기반메서드가 된다면 equals, hashCode, toString을 구현해도 된다.

위처럼 골격구현은 상속해서 사용하는것을 전제하므로 꼭 상속에대한 문서를 적어 놓아야 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

// Implementation Note: SimpleEntry and SimpleImmutableEntry
// are distinct unrelated classes, even though they share
// some code. Since you can't add or subtract final-ness
// of a field in a subclass, they can't share representations,
// and the amount of duplicated code is too small to warrant
// exposing a common abstract class.


/**
* An Entry maintaining a key and a value. The value may be
* changed using the <tt>setValue</tt> method. This class
* facilitates the process of building custom map
* implementations. For example, it may be convenient to return
* arrays of <tt>SimpleEntry</tt> instances in method
* <tt>Map.entrySet().toArray</tt>.
*
* @since 1.6
*/
public static class SimpleEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = -8499721149061103585L;

private final K key;
private V value;

/**
* Creates an entry representing a mapping from the specified
* key to the specified value.
*
* @param key the key represented by this entry
* @param value the value represented by this entry
*/
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}

/**
* Creates an entry representing the same mapping as the
* specified entry.
*
* @param entry the entry to copy
*/
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}

/**
* Returns the key corresponding to this entry.
*
* @return the key corresponding to this entry
*/
public K getKey() {
return key;
}

/**
* Returns the value corresponding to this entry.
*
* @return the value corresponding to this entry
*/
public V getValue() {
return value;
}

/**
* Replaces the value corresponding to this entry with the specified
* value.
*
* @param value new value to be stored in this entry
* @return the old value corresponding to the entry
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}

/**
* Compares the specified object with this entry for equality.
* Returns {@code true} if the given object is also a map entry and
* the two entries represent the same mapping. More formally, two
* entries {@code e1} and {@code e2} represent the same mapping
* if<pre>
* (e1.getKey()==null ?
* e2.getKey()==null :
* e1.getKey().equals(e2.getKey()))
* &amp;&amp;
* (e1.getValue()==null ?
* e2.getValue()==null :
* e1.getValue().equals(e2.getValue()))</pre>
* This ensures that the {@code equals} method works properly across
* different implementations of the {@code Map.Entry} interface.
*
* @param o object to be compared for equality with this map entry
* @return {@code true} if the specified object is equal to this map
* entry
* @see #hashCode
*/
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}

/**
* Returns the hash code value for this map entry. The hash code
* of a map entry {@code e} is defined to be: <pre>
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())</pre>
* This ensures that {@code e1.equals(e2)} implies that
* {@code e1.hashCode()==e2.hashCode()} for any two Entries
* {@code e1} and {@code e2}, as required by the general
* contract of {@link Object#hashCode}.
*
* @return the hash code value for this map entry
* @see #equals
*/
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}

/**
* Returns a String representation of this map entry. This
* implementation returns the string representation of this
* entry's key followed by the equals character ("<tt>=</tt>")
* followed by the string representation of this entry's value.
*
* @return a String representation of this map entry
*/
public String toString() {
return key + "=" + value;
}

}

단순구현은 골격 구현의 작은 변종으로 위의 AbstractMap.SimpleEntry가 좋은 예이다.
단순 구현은 쉽게 말해 동작하는 가장 단순한 구현이다. 단순구현은 그래로 써도 되고 필요에 맞게 확장해도 된다.

참조