【Modern Java】JEP 269 Convenience Factory Methods for Collections

f:id:Naotsugu:20200724174249p:plain

blog1.mammb.com

JEP 269: Convenience Factory Methods for Collections

Java 9 でコレクションとマップのインスタンスを簡素に生成する静的なファクトリメソッドが追加されました。

var list = List.of("a", "b", "c");
var set = Set.of("a", "b", "c");


Map の場合はキーと値の組み合わせで以下のようにインスタンス生成ができます。

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

要素数が10を超える場合は以下のようにします。

var map = Map.ofEntries(
        entry("a", 1),
        entry("b", 2),
        entry("c", 3),
        // ...
        entry("z", 26));


旧来のインスタンス生成

少ない要素の変更不可能なコレクションを生成するには、旧来は以下のように行いました。

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")));

Java 8 Stream API を使った以下のような書き方もできます。

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


匿名の内部クラスでインスタンス初期化子構成を使用したMap生成も良く行われてきました。

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

この方法は、囲んだインスタンスやキャプチャされたオブジェクトへの隠れた参照が保持されるため、メモリリークやシリアル化の問題が発生する可能性があるため良い方法とは言えません。


コレクションの生成

前述の通り、以下のようにインスタンス生成ができます。

var list = List.of("a", "b", "c");
var set = Set.of("a", "b", "c");

List を例に取ると、インターフェースに静的メソッドとして以下のように定義されています。

public interface List<E> extends Collection<E> {

    @SuppressWarnings("unchecked")
    static <E> List<E> of() {
        return (List<E>) ImmutableCollections.ListN.EMPTY_LIST;
    }

    static <E> List<E> of(E e1) {
        return new ImmutableCollections.List12<>(e1);
    }

    static <E> List<E> of(E e1, E e2) {
        return new ImmutableCollections.List12<>(e1, e2);
    }

    static <E> List<E> of(E e1, E e2, E e3) {
        return new ImmutableCollections.ListN<>(e1, e2, e3);
    }

    // ...

    static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) {
        return new ImmutableCollections.ListN<>(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
    }

    @SafeVarargs
    @SuppressWarnings("varargs")
    static <E> List<E> of(E... elements) {
        switch (elements.length) { // implicit null check of elements
            case 0:
                @SuppressWarnings("unchecked")
                var list = (List<E>) ImmutableCollections.ListN.EMPTY_LIST;
                return list;
            case 1:
                return new ImmutableCollections.List12<>(elements[0]);
            case 2:
                return new ImmutableCollections.List12<>(elements[0], elements[1]);
            default:
                return new ImmutableCollections.ListN<>(elements);
        }
    }
}

可変長引数のメソッドも別途定義されており、要素数に対する制限はありません。

引数は10個まではオーバーロードされたファクトリメソッドが定義されています。 これにより、可変長引数呼び出し時の配列の割り当て・初期化・varargs 呼び出しで発生するガベージコレクションのオーバーヘッドを回避します。


生成されるインスタンスは、要素数がたかだか2個以下の ImmutableCollections.List12 と、内部に配列を持つ ImmutableCollections.ListN のいずれかになります。


マップの生成

前述の通り、以下のようにインスタンス生成ができます。

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

インターフェースに静的メソッドとして以下のように定義されています。

public interface Map<K, V> {
    @SuppressWarnings("unchecked")
    static <K, V> Map<K, V> of() {
        return (Map<K,V>) ImmutableCollections.MapN.EMPTY_MAP;
    }

    static <K, V> Map<K, V> of(K k1, V v1) {
        return new ImmutableCollections.Map1<>(k1, v1);
    }

    static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
        return new ImmutableCollections.MapN<>(k1, v1, k2, v2);
    }

    // ...

    static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5,
                               K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
        return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5,
                                               k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
    }
}

引数は10個まではオーバーロードされており、10個を超える場合は以下のようにしてインスタンスを生成する必要があります。

var map = Map.ofEntries(
        entry("a", 1),
        entry("b", 2),
        entry("c", 3),
        // ...
        entry("z", 26));


その他特記事項

  • 生成されたコレクション、マップのインスタンスはシリアル化可能
  • コレクションの要素、マップのキーと値には null 値が禁止される
  • List のインスタンスは RandomAccess マーカーインターフェイスを実装する
  • コレクション・インスタンスは複数のスレッドによる同時アクセスでも安全に利用できる