プライマリ・コンテンツに移動
Java Platform, Standard Editionコア・ライブラリ
リリース10
E94988-01
目次へ移動
目次

前
次

5 不変のリスト、セットおよびマップの作成

JDK 9でリストセットおよびマップの各インタフェースに追加されたコンビニエンス静的ファクトリ・メソッドによって、不変リスト、不変セットおよび不変マップを簡単に作成できるようになりました。

構築後に状態を変更できないオブジェクトは、不変オブジェクトとみなされます。コレクションの不変インスタンスが作成されると、これに対する参照が存在するかぎり、ここには同じデータが保持され続けます。

これらのメソッドを使用して作成されたコレクションに不変オブジェクトが含まれている場合、これらは構築後自動的にスレッド・セーフになります。構造で変更をサポートする必要がないため、少ない領域で効率的に作成できます。通常、不変コレクションのインスタンスのメモリー消費量は、可変の場合よりはるかに少なくなります。

「不変性について」で説明しているとおり、不変コレクションには可変オブジェクトを含めることができます。含まれている場合、そのコレクションは不変でもスレッド・セーフでもなくなります。

ユースケース

不変メソッドの一般的なユースケースは、既知の値で初期化され、まったく変更されないコレクションです。また、データの変更頻度が低い場合も、これらのメソッドの使用を検討してください。

パフォーマンスを最適化するには、不変コレクションに変更されないデータ・セットを保存します。ただし、変更されるデータについても、パフォーマンスと領域効率が改善されます。時々変更されるデータの場合も、これらのコレクションを使用すると、可変コレクションよりパフォーマンスが改善されます。

値の数が多い場合は、HashMapに保存することを検討してください。エントリの追加と削除が常に発生する場合に適しています。ただし、値のセットがまったく変更されない、またはめったに変更されない場合に、そのセットを頻繁に読み取るのであれば、不変マップが最も適しています。データ・セットの読取りが頻繁に行われ、その値がめったに変更されない場合は、値が変更されたときに不変マップを破棄して再構築することによるパフォーマンスの影響を考慮しても、全体的な時間効率が向上します。

構文

これらの新しいコレクションのAPIは、要素の数が少ない場合は特にシンプルです。

不変リストの静的ファクトリ・メソッド

List.of静的ファクトリ・メソッドを使用すると、不変リストを簡単に作成できます。

リストは順序付けされたコレクションであり、通常は重複要素が許可されます。NULL値は設定できません。

これらのメソッドの構文は次のとおりです。

List.of()
List.of(e1)
List.of(e1, e2)         // fixed-argument form overloads up to 10 elements
List.of(elements...)   // varargs form supports an arbitrary number of elements or an array

例5-1 例

JDK 8の場合:

List<String> stringList = Arrays.asList("a", "b", "c");
stringList = Collections.unmodifiableList(stringList);

JDK 9の場合:

List stringList = List.of("a", "b", "c");

「不変リストの静的ファクトリ・メソッド」を参照してください。

不変セットの静的ファクトリ・メソッド

Set.of静的ファクトリ・メソッドを使用すると、不変セットを簡単に作成できます。

セットは、重複要素を含まないコレクションです。重複エントリが検出された場合は、IllegalArgumentExceptionがスローされます。NULL値は設定できません。

これらのメソッドの構文は次のとおりです。

Set.of()
Set.of(e1)
Set.of(e1, e2)         // fixed-argument form overloads up to 10 elements
Set.of(elements...)   // varargs form supports an arbitrary number of elements or an array

例5-2 例

JDK 8の場合:

Set<String> stringSet = new HashSet<>(Arrays.asList("a", "b", "c"));
stringSet = Collections.unmodifiableSet(stringSet);

JDK 9の場合:

Set<String> stringSet = Set.of("a", "b", "c");

「不変セットの静的ファクトリ・メソッド」を参照してください。

不変マップの静的ファクトリ・メソッド

Map.ofMap.ofEntriesの各静的ファクトリ・メソッドを使用すると、不変マップを簡単に作成できます。

マップには、同一のキーを複数登録できません。各キーは1つの値にしかマッピングできません。重複キーが検出された場合は、IllegalArgumentExceptionがスローされます。Null値はマップのキーまたは値として使用できません。

これらのメソッドの構文は次のとおりです。

Map.of()
Map.of(k1, v1)
Map.of(k1, v1, k2, v2)    // fixed-argument form overloads up to 10 key-value pairs
Map.ofEntries(entry(k1, v1), entry(k2, v2),...)
 // varargs form supports an arbitrary number of Entry objects or an array

例5-3 例

JDK 8の場合:

Map<String, Integer> stringMap = new HashMap<String, Integer>(); 
stringMap.put("a", 1); 
stringMap.put("b", 2);
stringMap.put("c", 3);
stringMap = Collections.unmodifiableMap(stringMap);

JDK 9の場合:

Map stringMap = Map.of("a", 1, "b", 2, "c", 3);

例5-4 任意の数のペアを持つマップ

キーと値のペアの数が10を超える場合は、Map.entryメソッドを使用してマップのエントリを作成し、それらのオブジェクトをMap.ofEntriesメソッドに渡します。次に例を示します。

import static java.util.Map.entry;
Map <Integer, String> friendMap = Map.ofEntries(
   entry(1, "Tom"),
   entry(2, "Dick"),
   entry(3, "Harry"),
   ...
   entry(99, "Mathilde"));

「不変マップの静的ファクトリ・メソッド」を参照してください。

コレクションの不変コピーの作成

要素を追加し、それを変更することによってコレクションを作成し、ある時点で、そのコレクションの不変スナップショットが必要になる場合があるとします。JDK 10に追加されたcopyOfメソッド・ファミリを使用してコピーを作成します。

たとえば、いくつかの場所から要素を収集するコードがあるとします。   
   List<Item> list = new ArrayList<>();
   list.addAll(getItemsFromSomewhere());
   list.addAll(getItemsFromElsewhere());
   list.addAll(getItemsFromYetAnotherPlace());
List.ofメソッドを使用して不変コレクションを作成することは不便です。このことを実行するには、正しいサイズの配列を作成し、リストの要素を配列にコピーして、その後、List.of(array)を呼び出して不変スナップショットを作成する必要があります。かわりにcopyOf staticファクトリ・メソッドを使用して、1つのステップで実行します。  
   List<Item> snapshot = List.copyOf(list); 

セットとマップには、Set.copyOfおよびMap.copyOfと呼ばれる対応するstaticファクトリ・メソッドがあります。

元のコレクションが可変である場合、copyOfメソッドは、元の文書のコピーである不変コレクションを作成します。つまり、結果には元と同じすべての要素が含まれます。元のコレクションに要素が追加されたり、元のコレクションから要素が削除された場合、そのコピーには影響しません。

元のコレクションがすでに不変である場合、copyOfメソッドは元のコレクションへの参照のみを戻します。コピーを作成する理由は、戻されたコレクションを元のコレクションの変更から分離するためです。ただし、元のコレクションを変更できない場合は、コピーを作成する必要がありません。

これらのケースの両方において、要素が可変で、その1つが変更された場合、この変更によって元のコレクションとコピーの両方が変更されたように見えます。

ストリームからの不変コレクションの作成

ストリーム・ライブラリには、コレクタと呼ばれる端末操作のセットが含まれます。コレクタは、ストリームの要素を含む新規コレクションを作成する場合に最もよく使用されます。JDK 10以降では、java.util.stream.Collectorsクラスに、ストリームの要素から新しい不変コレクションを作成するコレクタが含まれています。

戻されたコレクションが不変であることを保証する必要がある場合は、toUnmodifiable-コレクタのいずれかを使用する必要があります。これらのコレクタは次のとおりです。

   Collectors.toUnmodifiableList()
   Collectors.toUnmodifiableSet()
   Collectors.toUnmodifiableMap(keyMapper, valueMapper)     
   Collectors.toUnmodifiableMap(keyMapper, valueMapper, mergeFunction)

たとえば、ソース・コレクションの要素を変換して結果を不変セットに配置するには、次のようにします。

   Set<Item> immutableSet =
      sourceCollection.stream()
                      .map(...) 
                      .collect(Collectors.toUnmodifiableSet());

これらのコレクタは、同等のtoListtoSet、および対応する2つのtoMapメソッドと概念的に似ていますが、特性が異なります。特に、toListtoSetおよびtoMapメソッドは、戻されたコレクションが可変または不変であることを保証しません。

ランダム化された反復順序

Set要素およびMapキーの反復順序がランダム化されました。これはJVMの実行ごとに異なる可能性があります。これは意図的な動作です - これによって、反復順序に依存するコードの識別が容易になります。コードに潜む反復順序の依存性によって、デバッグするのが難しい問題が発生することがあります。

jshellが再起動するまで反復順序が同じであることがわかります。

jshell> Map stringMap = Map.of("a", 1, "b", 2, "c", 3);
stringMap ==> {b=2, c=3, a=1}

jshell> Map stringMap = Map.of("a", 1, "b", 2, "c", 3);
stringMap ==> {b=2, c=3, a=1}

jshell> /exit
|  Goodbye

C:\Program Files\Java\jdk-9\bin>jshell
|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro

jshell> Map stringMap = Map.of("a", 1, "b", 2, "c", 3);
stringMap ==> {a=1, b=2, c=3}

Set.ofMap.ofおよびMap.ofEntriesの各メソッドを使用して作成されたコレクション・インスタンスの反復順序のみがランダム化されています。HashMapおよびHashSetなどのコレクション実装の反復順序に変更はありません。

不変性について

JDK 9で追加されたコンビニエンス・ファクトリ・メソッドによって返されるコレクションは、慣例的に不変です。これらのコレクションに対して要素の追加、設定または削除を試行すると、UnsupportedOperationExceptionがスローされます。

これらのコレクションは「不変持続」コレクションまたは「機能」コレクションではありません。これらのいずれかのコレクションを使用している場合、これを変更することはできますが、変更した場合は、最初のコレクションと同じ構造の新しく更新されたコレクションが返されます。

不変コレクションの利点の1つは、自動的にスレッド・セーフであることです。コレクションを作成したら、複数のスレッドに渡すことができ、そこでは一貫したビューが表示されます。

ただし、オブジェクトの不変コレクションは不変オブジェクトのコレクションと同じではありません。含まれている要素が可変要素である場合は、コレクションの動作の一貫性が失われるか、コレクションのコンテンツが変更されます。

不変コレクションに可変要素が含まれている場合の例を示します。jshellを使用して、ArrayListクラスを使用した文字列オブジェクトのリストを2つ作成します。ここで、2番目のリストは1番目のリストをコピーして作成します。トリビアルjshell出力は削除されました。

jshell> List<String> list1 = new ArrayList<>();
jshell> list1.add("a")
jshell> list1.add("b")
jshell> list1
list1 ==> [a, b]

jshell> List<String> list2 = new ArrayList<>(list1);
list2 ==> [a, b]

次に、List.ofメソッドを使用して、最初に作成したリストをポイントするilist1ilist2を作成します。ilist1の変更を試行すると、ilist1は不変であるため、例外エラーが表示されます。変更を試行すると例外がスローされます。

jshell> List<List<String>> ilist1 = List.of(list1, list1);
ilist1 ==> [[a, b], [a, b]]

jshell> List<List<String>> ilist2 = List.of(list2, list2);
ilist2 ==> [[a, b], [a, b]]

jshell> ilist1.add(new ArrayList<String>())
|  java.lang.UnsupportedOperationException thrown:
|        at ImmutableCollections.uoe (ImmutableCollections.java:70)
|        at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections
.java:76)
|        at (#10:1)

ただし、元のlist1を変更した場合、ilist1ilist2は等しくなくなります。

jshell> list1.add("c")
jshell> list1
list1 ==> [a, b, c]
jshell> ilist1
ilist1 ==> [[a, b, c], [a, b, c]]

jshell> ilist2
ilist2 ==> [[a, b], [a, b]]

jshell> ilist1.equals(ilist2)
$14 ==> false

不変と同じではない変更不可能

不変コレクションはCollections.unmodifiable...ラッパーと同じように動作します。ただし、これらのコレクションはラッパーではありません - これらはクラスによって実装されたデータ構造であり、データの変更を試行すると例外がスローされます。

リストを作成してCollections.unmodifiableListメソッドに渡した場合は、変更不可能なビューを取得します。基となるリストは変更可能であり、これを変更した場合は返されるリストに表示されるため、実際には不変ではありません。

この動作を確認するには、リストを作成してCollections.unmodifiableListに渡します。そのリストに直接追加しようとすると、例外がスローされます。

jshell> List<String> unmodlist1 = Collections.unmodifiableList(list1);
unmodlist1 ==> [a, b, c]

jshell> unmodlist1.add("d")
|  java.lang.UnsupportedOperationException thrown:
|        at Collections$UnmodifiableCollection.add (Collections.java:1056)
|        at (#17:1)

ただし、元のlist1を変更した場合、エラーは生成されず、unmodlist1リストが変更されます。

jshell> list1.add("d")
$19 ==> true
jshell> list1
list1 ==> [a, b, c, d]

jshell> unmodlist1
unmodlist1 ==> [a, b, c, d]

領域効率

コンビニエンス・ファクトリ・メソッドによって返されるコレクションは、同等の可変コレクションより領域効率に優れています。

これらのコレクションのすべての実装は、静的ファクトリ・メソッドの背後にあるプライベート・クラスです。これがコールされると、静的ファクトリ・メソッドはサイズに基づいて実装クラスを選択します。データは圧縮されたフィールドベースまたは配列ベースのレイアウトに保存されます。

2つの代替実装によって消費されるヒープ領域を示します。まず、2つの文字列が含まれる変更不可能なHashSetを示します。
Set<String> set = new HashSet<>(3);   // 3 buckets
set.add("silly");
set.add("string");
set = Collections.unmodifiableSet(set);
セットには、変更不可能なラッパー、HashSet、それに含まれるHashMap、バケットの表(配列)、2つのノード・インスタンス(要素ごとに1つ)、の6つのオブジェクトが含まれています。オブジェクトごとに12バイトのヘッダーを持つ通常のVMでは、セットのオーバーヘッド合計は96バイト + 28 * 2 = 152バイトになります。これは、保存されるデータ量に対して大きなオーバーヘッド容量です。さらに、データにアクセスするために複数のメソッドのコールや異なるポインタが必要になります。

かわりに、Set.ofを使用してセットを実装します。

Set<String> set = Set.of("silly", "string");

これはフィールドベースの実装であるため、セットには1つのオブジェクトと2つのフィールドが含まれます。オーバーヘッドは20バイトです。新しいコレクションが消費するヒープ領域は、要素ごとの固定オーバーヘッドであるため、小さくなります。

変更をサポートしないこともまた、領域の節約に貢献します。さらに、データを保持する必要があるオブジェクトが少なくなるため、ローカル参照が改善されます。