コレクション用のJava 9コンビニエンスファクトリメソッド

コレクションのJava 9コンビニエンスファクトリメソッド

1. 概要

Java 9は、簡潔な1行のコードを使用して変更不可能な小さなCollectionインスタンスsを作成するための、待望のシンタックスシュガーをもたらします。 JEP 269に従って、新しいコンビニエンスファクトリメソッドがJDK9に含まれます。

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

2. 歴史と動機

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

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

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

これは単純なタスクにはコードが多すぎるため、1つの式で実行できるはずです。

これはMapにも当てはまりますが、Listにはファクトリメソッドがあります。

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

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

double-brace手法のように、冗長性を減らす方法は他にもあります。

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

またはJava8Streamsを使用して:

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

ダブルブレースの手法は、冗長性がやや劣りますが、可読性が大幅に低下します(アンチパターンと見なされます)。

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

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

3. 説明と使用法

要素を引数として取り、ListSet、およびSetのインスタンスを返すListSet、およびMapインターフェイスに静的メソッドが提供されています。それぞれt5)s。

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

3.1. ListおよびSet

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

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

メソッドの使用:

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

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

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

ただし、このメソッドには12個のオーバーロードされたバージョンがあります。11個は0〜10個のパラメーターで、もう1個はvar-argsです。

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

static  List of(E... elems)

最も実用的な目的では、10個の要素で十分ですが、さらに多くの要素が必要な場合は、var-argsバージョンを使用できます。

ここで、任意の数の要素に対して機能するvar-argsバージョンがある場合、11個の追加メソッドを使用する意味は何でしょうか。

その答えはパフォーマンスです。 Every var-args method call implicitly creates an array. Having the overloaded methods avoid unnecessary object creation and the garbage collection overhead thereof.

ファクトリメソッドを使用した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 list = List.of(arr);

この場合、サイズ1のList<int[]>が返され、インデックス0の要素に配列が含まれます。

3.2. Map

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

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

そして使用法:

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

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

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

static  Map ofEntries(Map.Entry... entries)

そしてその使用法:

Map 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ではありません。 これらは、Java 9で導入された異なる実装です。 これらの実装は内部であり、そのコンストラクターはアクセスが制限されています。

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

4.1. 不変

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

@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
    Set set = Set.of("foo", "bar");
    set.add("baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
    List list = List.of("foo", "bar");
    list.set(0, "baz");
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map 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);
}

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

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

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

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

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

4.4. 直列化

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

5. 結論

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

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

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

この記事と単体テストの完全なソースコードはavailable over on GitHubです。