この質問を読むIterable#forEach
と、ラムダ式と組み合わせて、従来のfor-eachループを作成するためのショートカット/置換であるという印象を得ることができます。これは単に真実ではありません。OPからのこのコード:
joins.forEach(join -> mIrc.join(mSession, join));
されていない書き込みのショートカットとして意図
for (String join : joins) {
mIrc.join(mSession, join);
}
そして、確かにこのように使用すべきではありません。代わりに、それは書くためのショートカットとして意図されています(正確には同じではありません)
joins.forEach(new Consumer<T>() {
@Override
public void accept(T join) {
mIrc.join(mSession, join);
}
});
そして、それは次のJava 7コードの代替としてです。
final Consumer<T> c = new Consumer<T>() {
@Override
public void accept(T join) {
mIrc.join(mSession, join);
}
};
for (T t : joins) {
c.accept(t);
}
上記の例のように、ループの本体を関数型インターフェースに置き換えると、コードがより明確になります。つまり、(1)ループの本体は周囲のコードと制御フローに影響を与えない、(2)ループの本体は、周囲のコードに影響を与えることなく、関数の別の実装に置き換えることができます。外部スコープの非final変数にアクセスできないことは、関数/ラムダの不足ではなく、従来のfor-eachループのセマンティクスからセマンティクスを区別する機能ですIterable#forEach
。の構文Iterable#forEach
に慣れると、コードに関するこの追加情報をすぐに取得できるため、コードが読みやすくなります。
従来のfor-eachループは、Javaでは確かに(「使いすぎ」という用語の「ベストプラクティス」を回避するために)良い習慣を維持します。しかし、これは悪い習慣や悪いスタイルと見なされるべきではないという意味ではありません。仕事をするのに適切なツールを使用することは常に良い習慣であり、これには、従来のfor-eachループをと混合することも含まれます。Iterable#forEach
Iterable#forEach
のマイナス面Iterable#forEach
についてはこのスレッドで既に説明しているので、いくつかの理由を説明します。なぜ使用したいのでしょうかIterable#forEach
。
コードをより明示的にするには:上記のように、状況によってはコードをより明示的で読みやすくするIterable#forEach
ことができます。
コードの拡張性と保守性を高めるには:関数をループの本体として使用すると、この関数を別の実装に置き換えることができます(戦略パターンを参照)。たとえば、ラムダ式をメソッド呼び出しに簡単に置き換えることができます。これは、サブクラスによって上書きされる場合があります。
joins.forEach(getJoinStrategy());
次に、機能インターフェースを実装する列挙型を使用してデフォルトの戦略を提供できます。これにより、コードの拡張性が高まるだけでなく、ループの実装がループ宣言から切り離されるため、保守性も向上します。
コードをよりデバッグしやすくするには:ループの実装を宣言から分離すると、デバッグをより簡単にすることもできますif(DEBUG)System.out.println()
。これは、でメインコードを煩雑にせずに、デバッグメッセージを出力する専用のデバッグ実装を用意できるためです。デバッグ実装は、たとえば実際の関数実装を装飾するデリゲートである可能性があります。
パフォーマンスが重要なコードを最適化するには:このスレッドの一部のアサーションとは対照的に、少なくともArrayListを使用して「-client」モードでHotspotを実行している場合は、従来のfor-eachループよりも優れたパフォーマンスをすでに提供Iterable#forEach
しています。このパフォーマンスの向上はわずかであり、ほとんどのユースケースでは無視できますが、この追加のパフォーマンスによって違いが生じる場合があります。たとえば、ライブラリのメンテナは、既存のループ実装の一部をで置き換える必要がある場合、評価を望みIterable#forEach
ます。
この説明を事実で裏付けるために、私はCaliperでいくつかのマイクロベンチマークを行いました。これがテストコードです(gitの最新のCaliperが必要です):
@VmOptions("-server")
public class Java8IterationBenchmarks {
public static class TestObject {
public int result;
}
public @Param({"100", "10000"}) int elementCount;
ArrayList<TestObject> list;
TestObject[] array;
@BeforeExperiment
public void setup(){
list = new ArrayList<>(elementCount);
for (int i = 0; i < elementCount; i++) {
list.add(new TestObject());
}
array = list.toArray(new TestObject[list.size()]);
}
@Benchmark
public void timeTraditionalForEach(int reps){
for (int i = 0; i < reps; i++) {
for (TestObject t : list) {
t.result++;
}
}
return;
}
@Benchmark
public void timeForEachAnonymousClass(int reps){
for (int i = 0; i < reps; i++) {
list.forEach(new Consumer<TestObject>() {
@Override
public void accept(TestObject t) {
t.result++;
}
});
}
return;
}
@Benchmark
public void timeForEachLambda(int reps){
for (int i = 0; i < reps; i++) {
list.forEach(t -> t.result++);
}
return;
}
@Benchmark
public void timeForEachOverArray(int reps){
for (int i = 0; i < reps; i++) {
for (TestObject t : array) {
t.result++;
}
}
}
}
そしてここに結果があります:
「-client」を指定して実行するとIterable#forEach
、ArrayListに対する従来のforループより優れていますが、配列を直接反復するよりも低速です。「-server」で実行すると、すべてのアプローチのパフォーマンスはほぼ同じになります。
並列実行のオプションのサポートを提供するには:ストリームIterable#forEach
を使用して並列で機能インターフェースを実行する可能性が重要な側面であることはすでにここで述べられています。は、ループが実際に並列で実行されることを保証しないため、これをオプション機能と見なす必要があります。リストをで反復することにより、次のように明示的に言う:このループは並列実行をサポートしますが、それに依存しません。繰り返しますが、これは機能であり、赤字ではありません!Collection#parallelStream()
list.parallelStream().forEach(...);
並列実行の決定を実際のループ実装から遠ざけることにより、コード自体に影響を与えることなく、コードのオプションの最適化を許可できます。これは良いことです。また、デフォルトの並列ストリーム実装がニーズに合わない場合でも、独自の実装を提供することを妨げるものはありません。たとえば、基になるオペレーティングシステム、コレクションのサイズ、コアの数、およびいくつかの設定に応じて、最適化されたコレクションを提供できます。
public abstract class MyOptimizedCollection<E> implements Collection<E>{
private enum OperatingSystem{
LINUX, WINDOWS, ANDROID
}
private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
private int numberOfCores = Runtime.getRuntime().availableProcessors();
private Collection<E> delegate;
@Override
public Stream<E> parallelStream() {
if (!System.getProperty("parallelSupport").equals("true")) {
return this.delegate.stream();
}
switch (operatingSystem) {
case WINDOWS:
if (numberOfCores > 3 && delegate.size() > 10000) {
return this.delegate.parallelStream();
}else{
return this.delegate.stream();
}
case LINUX:
return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
case ANDROID:
default:
return this.delegate.stream();
}
}
}
ここでの良い点は、ループの実装がこれらの詳細を知っていたり、気にする必要がないことです。