Java 9には、不変のリストを作成するための コンビニエンスファクトリメソッド が付属しています。最後に、リストの作成は次のように簡単です。
List<String> list = List.of("foo", "bar");
ただし、このメソッドには12個のオーバーロードされたバージョンがあり、11個は0〜10個の要素を持ち、1個はvarargsを持ちます。
static <E> List<E> of(E... elements)
Set
とMap
の場合も同様です。
Var argsメソッドがあるので、11個のメソッドを追加する意味は何ですか?
私が思うに、var-argsは配列を作成するので、他の11のメソッドは余分なオブジェクトの作成をスキップでき、ほとんどの場合0〜10の要素で十分です。これには他に理由がありますか?
JEP docs 自体から-
説明-
これらには可変個引数のオーバーロードが含まれるため、コレクションサイズに固定の制限はありません。ただし、そのように作成されたコレクションインスタンスは、より小さなサイズに調整される場合があります。最大10個の要素に対応する特殊なケースのAPI(固定引数のオーバーロード)が提供されます。 これによりAPIが乱雑になりますが、可変引数呼び出しによって発生する配列の割り当て、初期化、およびガベージコレクションのオーバーヘッドが回避されます。重要なことに、ソースコード呼び出しサイトのは、fixed-argまたはvarargsオーバーロードが呼び出されたかどうかに関係なく同じです。
編集-モチベーションを追加し、@ CKingによるコメントですでに述べたように:
非目標-
任意の数の要素を持つ高性能でスケーラブルなコレクションをサポートすることは目標ではありません。 焦点は小さなコレクションにあります。
モチベーション-
変更できない小さなコレクション(セットなど)を作成するには、コレクションを作成してローカル変数に格納し、add()を数回呼び出してから、ラップする必要があります。
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));
Java 8 Stream APIを使用して、ストリームファクトリメソッドとコレクターを組み合わせることにより、小さなコレクションを構築できます。
// Java 8
Set<String> set1 = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(Collectors.toSet()));
コレクションリテラルの利点の多くは、言語を変更する場合と比較して大幅にコストとリスクを削減しながら、小さなコレクションインスタンスを作成するためのライブラリAPIを提供することで得られます。たとえば、小さなSetインスタンスを作成するコードは次のようになります。
// Java 9
Set set2 = Set.of("a", "b", "c");
ご想像のとおり、これはパフォーマンスの向上です。 Varargメソッドは「内部」で配列を作成し、1〜10個の引数を取るメソッドを持つことで、この冗長な配列の作成を直接回避できます。
Josh BlochのEffective Java(2nd ed。)enlighteningの項目42の次の文章を見つけることができます。
Varargsメソッドを呼び出すたびに、配列の割り当てと初期化が発生します。このコストを支払う余裕がないが、可変個引数の柔軟性が必要であると経験的に判断した場合は、ケーキを持って食べることができるパターンがあります。メソッドの呼び出しの95%に3つ以下のパラメーターがあると判断したとします。次に、メソッドの5つのオーバーロードを宣言します。1つはそれぞれ0から3つの通常のパラメーターで、引数の数が3を超える場合に使用する単一の可変引数メソッドです[...]
逆に見ることもできます。 varargsメソッドは配列を受け入れることができるため、このようなメソッドは、配列をList
に変換するための代替手段として機能します。
String []strArr = new String[]{"1","2"};
List<String> list = List.of(strArr);
このアプローチの代替手段はArrays.asList
を使用することですが、この場合にList
に加えられた変更は、List.of
の場合とは異なり配列に反映されます。したがって、List
と配列を同期させたくない場合は、List.of
を使用できます。
注仕様に記載されている正当化は、私には微視的な最適化のように思えます。 (これは、APIの所有者自身が another answerへのコメントで確認しました)
このパターンは、varargsパラメーターを受け入れるメソッドの最適化に使用されます。
ほとんどの場合、それらの2つだけを使用していることがわかる場合は、最も使用されているパラメーターの量を使用してメソッドのオーバーロードを定義することをお勧めします。
_public void foo(int num1);
public void foo(int num1, int num2);
public void foo(int num1, int num2, int num3);
public void foo(int... nums);
_
これは、varargsメソッドの呼び出し中に配列が作成されないようにするのに役立ちます。パフォーマンスの最適化に使用されるパターン:
_List<String> list = List.of("foo", "bar");
// Delegates call here
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List2<>(e1, e2); // Constructor with 2 parameters, varargs avoided!
}
_
この背後にあるさらに興味深い点は、3つのパラメーターから始めて、再びvarargsコンストラクターに委任していることです。
_static <E> List<E> of(E e1, E e2, E e3) {
return new ImmutableCollections.ListN<>(e1, e2, e3); // varargs constructor
}
_
これは今のところ奇妙に思えますが、私が推測するように、これは将来の改善のために予約されており、オプションとして、すべてのコンストラクターList3(3 params), List7(7 params)...
などの潜在的なオーバーロードです。