30.ジェネリックメソッドを選択すべし
- Collectionsの中のアルゴリズム系のメソッド(バイナリサーチやソート)はジェネリックメソッドである。
- ジェネリックメソッドを書くことはジェネリック型を書くのと似ている。以下のようにrawタイプで書くとwarningが現れる。
// Uses raw types - unacceptable! (Item 26)
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
warningを消して、タイプセーフにするにおいては、メソッド修飾子とメソッドの返り値の間に型パラメータを書いて以下のようにする。
// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
- immutableであるけれど、たくさんの異なる型に適応できるオブジェクトが必要となる場合があるかもしれない。
ジェネリックスはerasureによって実装されているので、求められるすべての型パラメータに対して、1つのオブジェクトで対応できるが、繰り返し、求められる型のオブジェクトを生成するstaticなファクトリメソッドを書く必要がある。このパターンは、generic singleton factoryと呼ばれ、関数オブジェクト(Item42)生成などに用いられる。 - 自分自身を返す関数が必要だとすると、Fuction.identityメソッドを用いれば事足りるが、説明にはもってこいなので下記に記す。
IDENTITY_FN
をUnaryOperator<T>
にキャストするときにwarningがでるが、identity
関数は引数を変更せずに返却するので、warningを抑制することができる。
package tryAny.effectiveJava;
import java.util.function.UnaryOperator;
public class GenericsTest7 {
public static void main(String[] args) {
String[] strings = { "jute", "hemp", "nylon" };
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
// Generic singleton factory pattern
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
}
- 型パラメータが自身の型変数を含んだ表現で定義されている場合を、再帰型境界という。再帰型境界は
Comparable
インターフェースと関係がある。
public interface Comparable<T> {
int compareTo(T o);
}
-
Collection
の並べかえ、ソート、最大最小導出のメソッドでは、Comparable
を実装した要素を使っている。これらのメソッドでは互いに比較可能な要素が必要となるが、それを以下のように表現してる。
// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);
<E extends Comparable<E>>
の表すところは、「自身と比較可能なあらゆる要素E」である。
以下が再帰型境界を使ったmaxメソッドとなる。
package tryAny.effectiveJava;
import java.util.Collection;
import java.util.Objects;
public class GenericsTest8 {
// Returns max value in a collection - uses recursive type bound
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
}
上記のメソッドはlistが空の時にIllegalArgumentExceptionを発生させる。良い代替案としては、戻り値をOptional<E>
とすることがあげられる。
- 再帰型境界はもっと複雑になりえるが、まれである。本章で扱ったイディオムと、ワイルドカードを用いる場合(Item31)と、simulated self-type(Item2)を理解すれば、実際に直面する大体の再帰型境界は扱うことができる。