1概要

Java 9は、簡潔な1行のコードを使用して小さな変更不可能な

Collection

instance

__s

__を作成するための待望の構文糖をもたらします。


JEP 269

に従って、新しい便利なファクトリメソッドがJDK 9に含まれるでしょう。

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


2歴史と動機

Javaで小さな不変の

Collection

を作成することは伝統的な方法を使って非常に冗長です。


Set

の例を見てみましょう。

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

これは単純な作業には多すぎるコードなので、1つの式で実行することは可能です。


Map

にも当てはまりますが、

List

にはファクトリメソッドがあります。

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

この

List

の作成はコンストラクターの初期化より優れていますが、これはhttps://en.wikipedia.org/wiki/Principle

of

least

astonishment[明白なことではありません]です。これは、

List

を作成するメソッドについて

Arrays__クラスを調べないためです。

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

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

またはJava 8

Streams

を使用して:

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

二重括弧法は少し冗長ではありませんが読みやすさを大いに減少させます(そしてアンチパターンと考えられます)。

ただし、Java 8バージョンは1行の式ですが、いくつか問題もあります。 1つは明確で直感的ではないこと、2つ目はまだ冗長であること、3つ目は不要なオブジェクトを作成すること、4つ目は

Map

の作成には使用できないことです。

欠点を要約すると、上記のアプローチはいずれも、修正不可能な小さなファーストクラスクラスの問題を引き起こす特定のユースケースを扱うものではありません。

** 3説明と使用法

要素を引数として受け取り、それぞれ

List



Set

、および

Map

のインスタンスを返す

List



Set

、および

Map

インターフェース用の静的メソッドが提供されています。

このメソッドは、3つのインタフェースすべてに対して

of(…​)

という名前です。


3.1.

List



Set



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のオーバーロードバージョンがあります。0から10のパラメータを持つ11とvar-argsを持つ11

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メソッド呼び出しは暗黙的に配列を作成します。オーバーロードされたメソッドを持つことで、不要なオブジェクトの作成とそのガベージコレクションのオーバーヘッドを回避できます。

ファクトリメソッドを使用した

Set

の作成中に、重複した要素がパラメータとして渡されると、実行時に

IllegalArgumentException

がスローされます。

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

ここで注意すべき重要な点は、ファクトリメソッドは総称を使用するため、プリミティブ型はオートボクシングされることです。

プリミティブ型の配列が渡されると、そのプリミティブ型の

List

of

array

が返されます。

例えば:

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

この場合、サイズ1の

List <int[]>

が返され、インデックス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実装上の注意

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

たとえば、

List



ArrayList

ではなく、

Map



HashMap

でもありません。これらは、Java 9で導入されたさまざまな実装です。これらの実装は内部的であり、それらのコンストラクタはアクセスが制限されています。

このセクションでは、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);
}


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結論

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

変更不可能なコレクションを作成するための過去の方法をいくつか調べて、この機能が歓迎すべき変更である理由を説明しました。使用方法を説明し、使用中に考慮すべき重要な点を強調しました。

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

この記事の完全なソースコードと単体テストはhttps://github.com/eugenp/tutorials/tree/master/core-java-9[GitHubで入手可能]です。