Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

JDK 9ではちょっとしたコレクションはファクトリメソッドで作れる

JEP 269: Convenience Factory Methods for Collections

コレクションへの便利なファクトリメソッドを追加します、と。

上記のページにはJavaでのコレクション生成についてのJDK 9以前の書き方がいろいろありました。

一番ベーシックなやつ。

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set = Collections.unmodifiableSet(set);

Arrays.asList()を使うパターン。

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

これはしたことなかった。 "double brace" テクニックと呼ばれているそうです。「it costs an extra class at each usage」と、使うたびにクラスを追加するので、余計なコストはかかりますね。

Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{
    add("a"); add("b"); add("c");
}});

Java 8ならStream APIでこう書けるぜ!でも正直やりません。

Set<String> set = Collections.unmodifiableSet(Stream.of("a", "b", "c").collect(toSet()));

それが!JDK 9ではこう書けるぞ!うん、これぐらいがいいよね。これがJava的にも限界かな。

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

さっそく試してみましょう。JDK™ 9 Early Access Releasesを使うと楽チンです。

https://jdk9.java.net/download/

せっかくなんで、JDK 9で導入される、JavaのREPLツール「jshell」でやりましょう。

$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+99-2015-12-23-184955.javare.4146.nc)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+99-2015-12-23-184955.javare.4146.nc, mixed mode)

$ jshell  

|  Welcome to JShell -- Version 9-ea
|  Type /help for help

-> Set.of(1,2,3)
|  Expression value is: [1, 2, 3]
|    assigned to temporary variable $1 of type Set<Integer>

-> Set set = Set.of(1,2,3,4)
|  Added variable set of type Set with initial value [1, 2, 3, 4]

-> set.size()
|  Expression value is: 4
|    assigned to temporary variable $3 of type int

おおー、Set.of()で要素ありのSetが作れました。このSetってクラスは何でしょうね?

-> set.getClass()
|  Expression value is: class java.util.Collections$UnmodifiableSet
|    assigned to temporary variable $2 of type Class<? extends Set>

UnmodifiableSetですね。

ListもMapもof()で作れます。

-> List list = List.of("a", "b", "c")
|  Added variable list of type List with initial value [a, b, c]

-> list.getClass()
|  Expression value is: class java.util.Collections$UnmodifiableRandomAccessList
|    assigned to temporary variable $4 of type Class<? extends List>

ListはUnmodifiableRandomAccessListでした。 Mapはこうです。

-> Map map = Map.of("hoge", 1, "fuga", 2, "java", 3)
|  Added variable map of type Map with initial value {fuga=2, java=3, hoge=1}

-> map.getClass()
|  Expression value is: class java.util.Collections$UnmodifiableMap
|    assigned to temporary variable $6 of type Class<? extends Map>

ちょっとくすぐったい感じがします。of(key1, value1, key2, value2, key3, value3)と記述します。もし奇数個だったら…

-> Map.of("hoge", 1, "fuga", 2, "java")
|  Error:
|  ofに適切なメソッドが見つかりません(java.lang.String,int,java.lang.String,int,java.lang.String)
|      メソッド java.util.Map.<K,V>of()は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|      メソッド java.util.Map.<K,V>of(K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V,K,V)は使用できません
|        (型変数K,Vを推論できません
|          (実引数リストと仮引数リストの長さが異なります))
|  Map.of("hoge", 1, "fuga", 2, "java")
|  ^----^

すごく怒られました。Mapにはof()だけでなくMap.ofEntries(Map.Entry<K,V>...)というのもあります。Entryを作るのは、Map.entry(K k, V v)というstaticメソッドが便利です。

-> Map.ofEntries(Map.entry("hoge",1), Map.entry("fuga", 2))
|  Expression value is: {hoge=1, fuga=2}
|    assigned to temporary variable $8 of type Map<String,Integer>

ちょっとしたコレクションは、断然作りやすくなりそうですね。