Java中級者以上の必須本である、Effective Java 第3版に Kindle版が出たので、まとめる。
前:Effective Java 第3版 第6章enumとアノテーション
次:Effective Java 第3版 第8章メソッド
項目42 無名クラスよりもラムダを選ぶ
// 無名クラス
Collections.sort(words, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
// ラムダを使う
Collections.sort(words, (o1, o2) -> Integer.compare(o1.length(), o2.length()));
// コンパレータ構築メソッド
Collections.sort(words, comparingInt(String::length));
// Java8ではさらに短く
words.sort(comparingInt(String::length));
- 関数オブジェクトフィールドと定数固有の振る舞いを持つenumの例。
public enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x + y),
TIMES("*", (x, y) -> x + y),
DIVIDE("/", (x, y) -> x + y);
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override public String toString() {
return symbol;
}
public double apply(double x, double y) {
return op.applyAsDouble(x, y)
}
}
項目43 ラムダよりもメソッド参照を選ぶ
- メソッド参照の種類
- static・・・
Integer::parseInt
- バウンド・・・
Instant.now()::isAfter
- アンバウンド・・・
String::toLowerCase
- クラスコンストラクタ・・・
TreeMap<K,V>::new
- 配列コンストラクタ・・・
int[]::new
- static・・・
- メソッド参照の方が短く明瞭であれば、メソッド参照を使い、そうでなければラムダを使う。
項目44 標準の関数型インタフェースを使う
-
java.util.Function
には、43個のインタフェースがあり、全ては覚えられないが、6個の基本インタフェースを覚えてさえいれば残りのインタフェースは必要な時に導き出せる。 -
Operator
インタフェースは、その結果の型が引数と同じ関数を表す。 -
Predicate
インタフェースは、引数を受け取りbooleanを返す。 -
Function
インタフェースは、引数とは別の型を返す。 -
Supplier
インタフェースは、引数を取らずに値を返す。(結果を提供する) -
Consumer
インタフェースは、引数を受け取り、何も返さない。(引数を消費する)
インタフェース | 関数のシグニチャ | 例 |
---|---|---|
UnaryOperator | T apply(T t) | String::toLowerCase |
BinaryOperator | T apply(T t1, T t2) | BigInteger::add |
Predicate | boolean test(T t) | Collection::isEmpty |
Function | R apply(T t) | Arrays::asList |
Supplier | T get() | Instant::now |
Consumer | void accept(T t) | System.out::println |
項目45 ストリームを注意して使う
- 処理により、ストリームで行うのが最善の場合と、ループで行うのが最善の場合がある。多くの場合、その二つの方法を組み合わせて行うのが最善である。
- ストリームかループのどちらかで処理を行うのが良いか確信が持てないなら、両方を試して調べてみる。
項目46 ストリームで副作用のない関数を選ぶ
- ストリームパイプラインプログラミングの本質は、副作用の無い関数オブジェクト。
- 終端操作
forEach
は、計算を行うためではなく、結果を表示するためだけに使われるべき。 - 重要なコレクターファクトリは、
toSet
、toMap
、groupingBy
、joining
。
項目47 戻り値としてStreamよりもCollectionを選ぶ
- 要素のシーケンスを返すメソッドを書く場合、ユーザによってはストリームとして処理したいかもしれないし、他のユーザはループしたいかもしれない。両方のユーザに適応するように試みる。
- すでにコレクション内に要素を持っているか、シーケンス中の要素数が新たなコレクションの作成を正当化するほど小さければ、ArrayListなどの標準コレクションを返す。そうでなければ、特別なコレクションの実装を検討する。
- コレクションを返すことができないなら、StreamかIterableのどちらか自然と思われる方を返す。
項目48 ストリームを並列化するときは注意を払う
- Javaリリース時には、同期及び
wait/notify
を持ち、スレッドのサポートを組み込んでいた。 - Java5では、コンカレントコレクションとエグゼキュータフレームワークを持つ
java.util.concurrent
ライブラリを導入した。 - Java7では、並列に分解するための高性能な
fork-join
フレームワークが導入された。 - Java8では、ストリームが導入され、
parallel
メソッドを一度呼び出すだけで並列化できる。 - 処理の正しさを維持し、パフォーマンスを向上させると信じるに値する十分な理由がない限り、ストリームのパイプラインの並列化をするべきではない。
- 並列に動作させてもコードの正しさが維持されることを保証し、現実の条件下でのパフォーマンスを注意深く測定する。
-
.parallel()
のデフォルトスレッド数は、マシンのコア数-1
。
素数を数えるストリームパイプライン並列化
static long pi(long n) {
return LongStream.rangeClosed(2, n)
.parallel()
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}