web-dev-qa-db-ja.com

コレクターを再利用することは許可/推奨されていますか?

私のコードには次のような非常に多くのスポットがあります:

_someStream.collect(Collectors.toList())
_

Collectors.toList()は、使用するたびに新しいコレクターを作成します。

これは、次のようなことが許可されており、推奨されるかどうかという質問につながります:

_private final static Collector<…> TO_LIST = Collectors.toList()
_

私が使用するすべてのタイプに対して、そのような単一のコレクターを使用します:

_someStream.collect(TO_LIST)
_

コレクターが必要な場合。

コレクターはステートレスであり、単なる関数と特性のコレクションであるため、機能するはずですが、OTOH、Collectors.toList()は呼び出しごとに新しい_CollectorImpl<>_を作成します。

コレクターを再利用することの欠点は何ですか?

45
glglgl

これはstyleの質問に近いと思いますが、考えてみましょう。

  • common実践notのようなCONSTコレクターオブジェクトを使用するようです。その意味では、そうすることで一部の読者を驚かせる可能性があり、読者を驚かせるのはめったに良いことではありません。
  • その後:いくつかのコードを「コピー」するだけで済みます(おそらく、コードの重複を避けるべきではありません)。それでも:distinctコレクターオブジェクトを指すと、ストリーム構造のリファクタリングまたは再利用が少し難しくなる場合があります。
  • それを超えて:あなたは自分でそれを述べました。コレクターの再利用依存 a ステートレス実装。したがって、ステートレスな実装に依存することになります。おそらく問題ではありません。ただし、riskを念頭に置いてください!
  • おそらくもっと重要なのは、一見すると、あなたのアイデアは最適化のニース平均のように見えます。しかしよく;ストリームを使用することによる「パフォーマンスへの影響」を心配する場合、ファイナルコレクターの単一のオブジェクト作成は「カットしない」ことになります。

つまり、「無駄な」パフォーマンスが心配な場合。ストリームを使用するコードの各行を調べて、そのストリームが「十分な」オブジェクトで機能しているかどうかを判断し、そもそもストリームの使用を正当化する必要があります。これらのストリームにはquite someオーバーヘッドが付いています!

要するに、Javaコミュニティはまだストリームの「標準的なベストプラクティス」を見つけていないため、現時点では私の(個人的な)2セント:「全員」が使用しているパターンを好む-避ける特に「パフォーマンス関連」である場合。

35
GhostCat

Collectorは基本的に4つの関数と特性フラグのコンテナであるため、再利用しても問題はありませんが、メモリ管理に対するこのような軽量オブジェクトの影響は無視できるとしても無視できるため、利点はほとんどありませんとにかくオプティマイザーによって完全に削除されました。

組み込みのCollectorで見られるように、Collectorssを再利用しない主な理由は、タイプセーフな方法でそれを実行できないことです。任意の型のListsのコレクターを提供する場合、常に同じCollectorインスタンスを配布するために未チェックの操作が必要になります。代わりにCollectorを適切に型付けされた変数に保存し、未チェックの操作なしで使用する場合は、Listsの1つのタイプにのみ使用でき、その例にとどまります。

Collections.emptyList()などの場合、JRE開発者は別の方法を使いましたが、定数EMPTY_LISTEMPTY_MAPEMPTY_SETはジェネリックの導入前にすでに存在しており、30個以上の組み込みコレクターのうち4つの特別なケースであるいくつかのキャッシュ可能なCollectorsよりも汎用性が高いと思います。関数パラメーターのためにキャッシュされます。関数パラメーターはラムダ式を介して実装されることが多く、不特定の同一性/等価性のオブジェクトを生成するため、それらをコレクターインスタンスにマッピングするキャッシュは予測不可能な効率を持ちますが、メモリマネージャーが一時インスタンスを処理するよりもはるかに効率が低い可能性が非常に高くなります。

30
Holger

ライブラリが有用なオブジェクトを取得するためにfactory methodを提供することは良い習慣です。ライブラリが提供されているメソッド:Collectors.toList()であるため、ライブラリを改ざんする代わりに、オブジェクトが要求されるたびに新しいインスタンスを作成するかどうかをライブラリに決定させます.

これは、GhostCatとHolgerの回答に、支持的な議論として追加される:)

13
Honza Zidek

@Holgerがオプティマイザーがスマートであり、その構造を完全に置き換えることについての彼の答えで言っていることは、ほんの小さな補足です。それはscalar replacementと呼ばれます。メソッド内で使用されるオブジェクトがde-constructedであり、そのフィールドがstack allocated like normal local variablesである場合。そのため、結果のCollectorは、JVMレベルでオブジェクト自体として扱われない場合があります。それはJIT timeで起こります。

6
Eugene

単一の静的オブジェクトを使用して、オンザフライで作成されたオブジェクトの代わりになるという古典的な問題は、可変性です。 Java 8ソースのクイックスキャンは、Set<Characteristics>フィールドは問題の可能性があります。

明らかに、どこかのコードで次のようなことをすることが可能です。

private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList();


public void test() {
    // Any method could do this (no idea why but it should be possible).
    TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH);
}

このcouldeveryの使用をグローバルに変更しますTO_LISTこれは非常に不明瞭なバグを作成する可能性があります。

だから私見-しないでください!

3
OldCurmudgeon

これは、時期尚早な最適化の場合です。オブジェクトの作成は非常に安価です。通常のラップトップでは、1秒あたり10M〜50Mのオブジェクトを作成できると予想されます。これらの数値を念頭に置いて、演習全体が無意味になります。

1