web-dev-qa-db-ja.com

Java 9、Set.of()およびMap.of()可変引数のオーバーロード

Immutableコレクションのファクトリメソッドを研究しています。 Set.of()メソッドに10個の可変引数がオーバーロードされていることがわかります(Map.of()についても同じです)。なんでこんなに多いのかよくわかりません。結局、関数ImmutableCollections.SetN<>(elements)はとにかく呼び出されます。

ドキュメントで私はこれを見つけました:

これによりAPIが乱雑になりますが、varargs呼び出しによって発生する配列の割り当て、初期化、およびガベージコレクションのオーバーヘッドが回避されます。

混乱は確かにパフォーマンスを向上させる価値がありますか?はいの場合、理想的には、任意のN要素に対して個別のメソッドを作成しますか?

24
Schidu Luca

現時点そのメソッドはとにかく呼び出されます-これは変更される可能性があります。たとえば、4などの3つの要素のみでSetを作成する可能性があります。

また、それらのすべてSetNに委任するわけではありません-ゼロ、1、および2つの要素を持つものは実際のクラスがImmutableCollections.Set0ImmutableCollections.Set1およびImmutableCollections.Set2

またはこれに関する実際の質問を読むことができます... ここ その質問のStuart Marksからのコメントを読んでください-彼はこれらのコレクションを作成した人物であるため。

11
Eugene

これのいくつかの側面は、将来の保証の一形態である可能性があります。

APIを進化させる場合、メソッドのシグネチャがどのように変化するかに注意を払う必要があります。

_public class API {
  public static final <T> Set<T> of(T... elements) { ... }
}
_

Varargsは十分に優れていると言えます... varargsforcesは、オブジェクト配列の割り当てを強制しますが、実際にはかなり安価です。パフォーマンスに影響を与えます。たとえば、 このマイクロベンチマーク を参照してください。これは、可変引数形式に切り替えたときに、操作なしのロギング(つまり、ログレベルがloggableよりも低い)のスループットが50%低下することを示しています。

さて、いくつかの分析を行い、最も一般的なケースはシングルトンであると言うので、リファクタリングすることにしました...

_public class API {
  public static final <T> Set<T> of(T first) { ... }
  public static final <T> Set<T> of(T first, T... others) { ... }
}
_

おっと...それはバイナリ互換ではありません...ソース互換ですが、バイナリ互換ではありません...バイナリ互換性を維持するには、以前の署名を保持する必要があります。

_public class API {
  public static final <T> Set<T> of(T first) { ... }
  @Deprecated public static final <T> Set<T> of(T... elements) { ... }
  public static final <T> Set<T> of(T first, T... others) { ... }
}
_

うーん... IDEコードの完了は今や混乱しています...さらに、配列のセットを作成するにはどうすればよいですか?(リストを使用している場合はおそらくより関連性があります)API.of(new Object[0])はあいまいです...最初にvarargを追加していなければ...

したがって、彼らが行ったことは、追加のスタックサイズがvararg作成のコストを満たすポイントに到達するのに十分な明示的な引数を追加したことだと思います。これはおそらく約10引数です(少なくとも、Log4J2がvarargをバージョンに追加したときに行った測定に基づく) 2 API)...しかし、あなたは証拠に基づいた将来の保証のためにこれを行っています...

言い換えれば、特殊な実装を必要とする証拠がなく、varargバリアントにフォールスルーするだけのすべてのケースをだますことができます。

_public class API {
  private static final <T> Set<T> internalOf(T... elements) { ... }
  public static final <T> Set<T> of(T first) { return internalOf(first); }
  public static final <T> Set<T> of(T first, T second) { return internalOf(first, second); }
  ...
  public static final <T> Set<T> of(T t1, T t2, T t3, T t4, T t5, T... rest) { ... }
}
_

次に、実際の使用パターンをプロファイリングして確認し、4引数形式までの有意な使用量と、妥当なパフォーマンスゲインがあることを示すベンチマークを確認した場合は、その時点で、舞台裏でメソッドimplを変更します。誰もが勝ちます...再コンパイルは必要ありません

10

使用しているAPIの範囲によって異なると思います。これらの不変クラスについて話すときは、jdkの一部として含まれているものについて話します。そのため、範囲は非常に広いです。

だからあなたは持っています:

  1. 一方では、これらの不変クラスは、すべてのビットが重要な(および割り当て/割り当て解除で無駄になるナノ秒ごとの)アプリケーションで使用される可能性があります。
  2. 反対に、これらのニーズのないアプリケーションは悪影響を受けません
  3. 唯一の「ネガティブ」な側面は、そのAPIの実装者にとって、処理がより煩雑になるため、保守性に影響します(ただし、この場合は大きな問題ではありません)。

あなたがあなた自身のものを実装しているなら、あなたがそれらの余分なビット(そして余分なパフォーマンスなど)について本当に心配する必要がない限り、私はそれについてあまり気にしません(しかしvarargs引数には注意してください)。

2
albert_nil