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
例3-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
例3-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.ofとMap.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
例3-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);
例3-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"));
「不変マップの静的ファクトリ・メソッド」を参照してください。
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.of、Map.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メソッドを使用して、最初に作成したリストをポイントするilist1
とilist2
を作成します。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
を変更した場合、ilist1
とilist2
は等しくなくなります。
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]
コンビニエンス・ファクトリ・メソッドによって返されるコレクションは、同等の可変コレクションより領域効率に優れています。
これらのコレクションのすべての実装は、静的ファクトリ・メソッドの背後にあるプライベート・クラスです。これがコールされると、静的ファクトリ・メソッドはサイズに基づいて実装クラスを選択します。データは圧縮されたフィールドベースまたは配列ベースのレイアウトに保存されます。
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バイトです。新しいコレクションが消費するヒープ領域は、要素ごとの固定オーバーヘッドであるため、小さくなります。
変更をサポートしないこともまた、領域の節約に貢献します。さらに、データを保持する必要があるオブジェクトが少なくなるため、ローカル参照が改善されます。