1. 概要

Java 9は、簡潔なコードのワンライナーを使用して、変更不可能な小さなCollectionインスタンスを作成するための待望の構文糖衣構文を提供します。 JEP 269 に従って、新しいコンビニエンスファクトリメソッドがJDK9に含まれます。

この記事では、その使用法と実装の詳細について説明します。

2. 歴史と動機

Javaで小さな不変のCollectionを作成することは、従来の方法を使用すると非常に冗長です。

Setの例を見てみましょう。

Set<String> set = new HashSet<>();
set.add("foo");
set.add("bar");
set.add("baz");
set = Collections.unmodifiableSet(set);

これは単純なタスクには多すぎるコードであり、単一の式で実行できるはずです。

上記はマップにも当てはまります。

ただし、 List には、ファクトリメソッドがあります。

List<String> list = Arrays.asList("foo", "bar", "baz");

このListの作成はコンストラクターの初期化よりも優れていますが、一般的な直感では Arrays クラスを調べて作成するメソッドがないため、これはあまり明白ではありませんリスト

ダブルブレース初期化手法のように、冗長性を減らす方法は他にもあります。

Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{
    add("foo"); add("bar"); add("baz");
}});

またはJava8 Streams を使用して:

Stream.of("foo", "bar", "baz")
  .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

ダブルブレーステクニックは少し冗長ではありませんが、読みやすさを大幅に低下させます(アンチパターンと見なされます)。

ただし、Java 8バージョンは1行の式であり、いくつかの問題もあります。 まず、それは明白で直感的ではありません。 第二に、それはまだ冗長です。 第三に、不要なオブジェクトの作成が含まれます。 そして第4に、このメソッドはMapの作成には使用できません。

欠点を要約すると、上記のアプローチはいずれも、変更できない小さなコレクションファーストクラスの問題を作成する特定のユースケースを扱いません。

3. 説明と使用法

静的メソッドがList Set 、および Map インターフェースに提供されており、これらは引数として要素を受け取り、Listのインスタンスを返します。それぞれSetMap

このメソッドは、3つのインターフェイスすべてに対して of(…)という名前が付けられています。

3.1. リストおよびセット

ListおよびSetファクトリメソッドのシグネチャと特性は同じです。

static <E> List<E> of(E e1, E e2, E e3)
static <E> Set<E>  of(E e1, E e2, E e3)

メソッドの使用法:

List<String> list = List.of("foo", "bar", "baz");
Set<String> set = Set.of("foo", "bar", "baz");

ご覧のとおり、非常にシンプルで短く、簡潔です。

この例では、パラメーターとして正確に3つの要素を取り、サイズ3の List / Setを返すメソッドを使用しました。

ただし、このメソッドには12のオーバーロードされたバージョンがあります。11は0から10のパラメーターを持ち、1つはvar-argsを持ちます。

static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
// ....and so on

static <E> List<E> of(E... elems)

ほとんどの実用的な目的では、10個の要素で十分ですが、それ以上が必要な場合は、var-argsバージョンを使用できます。

ここで、任意の数の要素に対して機能するvar-argsバージョンがある場合、11個の追加メソッドを持つことのポイントは何であるかを尋ねることができます。

その答えはパフォーマンスです。 すべてのvar-argsメソッド呼び出しは、暗黙的に配列を作成します。 オーバーロードされたメソッドを使用すると、不要なオブジェクトの作成とそのガベージコレクションのオーバーヘッドを回避できます。 それどころか、 Arrays.asList 常にその暗黙の配列を作成するため、要素の数が少ないと効率が低下します。

ファクトリメソッドを使用してSetを作成しているときに、重複する要素がパラメータとして渡されると、実行時にIllegalArgumentExceptionがスローされます。

@Test(expected = IllegalArgumentException.class)
public void onDuplicateElem_IfIllegalArgExp_thenSuccess() {
    Set.of("foo", "bar", "baz", "foo");
}

ここで注意すべき重要な点は、ファクトリメソッドはジェネリックを使用するため、プリミティブ型は自動ボックス化されるということです。

プリミティブ型の配列が渡されると、そのプリミティブ型のarrayListが返されます。

例えば:

int[] arr = { 1, 2, 3, 4, 5 };
List<int[]> list = List.of(arr);

この場合、 リストサイズ1が返され、インデックス0の要素に配列が含まれます。

3.2. マップ

Mapファクトリメソッドのシグネチャは次のとおりです。

static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3)

と使用法:

Map<String, String> map = Map.of("foo", "a", "bar", "b", "baz", "c");

ListおよびSetと同様に、 of(…)メソッドは、0〜10個のキーと値のペアを持つようにオーバーロードされます。

Map の場合、10を超えるキーと値のペアに対して別の方法があります。

static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)

そしてそれの使用法:

Map<String, String> map = Map.ofEntries(
  new AbstractMap.SimpleEntry<>("foo", "a"),
  new AbstractMap.SimpleEntry<>("bar", "b"),
  new AbstractMap.SimpleEntry<>("baz", "c"));

Keyに重複する値を渡すと、IllegalArgumentExceptionがスローされます。

@Test(expected = IllegalArgumentException.class)
public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() {
    Map.of("foo", "a", "foo", "b");
}

繰り返しますが、 Map の場合も、プリミティブ型は自動ボックス化されます。

4. 実装上の注意

ファクトリメソッドを使用して作成されたコレクションは、一般的に使用される実装ではありません。

たとえば、ListArrayListではなく、MapHashMapではありません。 これらは、Java9で導入されたさまざまな実装です。 これらの実装は内部であり、それらのコンストラクターはアクセスが制限されています。

このセクションでは、3つのタイプのコレクションすべてに共通するいくつかの重要な実装の違いを確認します。

4.1. 不変

ファクトリメソッドを使用して作成されたコレクションは不変であり、要素の変更、新しい要素の追加、または要素の削除により、UnsupportedOperationExceptionがスローされます。

@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
    Set<String> set = Set.of("foo", "bar");
    set.add("baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
    List<String> list = List.of("foo", "bar");
    list.set(0, "baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map<String, String> map = Map.of("foo", "a", "bar", "b");
    map.remove("foo");
}

4.2. null要素は許可されていません

ListおよびSetの場合、要素をnullにすることはできません。 Map の場合、キーも値もnullにすることはできません。 null 引数を渡すと、NullPointerExceptionがスローされます。

@Test(expected = NullPointerException.class)
public void onNullElem_ifNullPtrExpnThrown_thenSuccess() {
    List.of("foo", "bar", null);
}

List.of とは対照的に、Arrays.asListメソッドはnull値を受け入れます。

4.3. 値ベースのインスタンス

ファクトリメソッドによって作成されたインスタンスは値ベースです。 これは、ファクトリが自由に新しいインスタンスを作成したり、既存のインスタンスを返したりできることを意味します。

したがって、同じ値でリストを作成する場合、それらはヒープ上の同じオブジェクトを参照する場合と参照しない場合があります。

List<String> list1 = List.of("foo", "bar");
List<String> list2 = List.of("foo", "bar");

この場合、 list1 == list2 は、JVMに応じてtrueと評価される場合と評価されない場合があります。

4.4. シリアル化

コレクションの要素がSerializable。の場合、ファクトリメソッドから作成されたコレクションはSerializableです。

5. 結論

この記事では、Java9で導入されたコレクションの新しいファクトリメソッドを紹介しました。

変更不可能なコレクションを作成するための過去の方法をいくつか検討することで、この機能が歓迎すべき変更である理由を結論付けました。 その使用法について説明し、それらを使用する際に考慮すべき重要なポイントを強調しました。

最後に、これらのコレクションが一般的に使用される実装とは異なることを明確にし、重要な違いを指摘しました。

この記事の完全なソースコードと単体テストは、GitHubから入手できます。