ひびのログ

日々ではないけどログを出力していくブログ

Java9 で追加されたList.of()と、以前から存在する Arrays.asList()の違い

検証PCのスペック

  • OS: Windows 7 64bit Professional
  • CPU: Intel Core i5-3470 3.20GHz
  • メモリ: 8.00GB

Javadocの定義

まずは Javadoc を確認。

Arrays.asList()

Java9 Arrays Javadoc

修飾子と型 メソッド 説明
static <T> List<T> asList(T... a) 指定された配列に連動する固定サイズのリストを返します。

List.of()

Java9 List Javadoc

修飾子と型 メソッド 説明
static <E> List<E> of () ゼロ要素を含む不変のリストを返します。
static <E> List<E> of (E e1) 1つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2) 2つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3) 3つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4) 4つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4, E e5) 5つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4, E e5, E e6) 6つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4, E e5, E e6, E e7) 7つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) 8つの要素を含む不変のリストを返します。
static <E> List<E> of (E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) 9つの要素を含む不変のリストを返します。
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) 10個の要素を含む不変のリストを返します。
static <E> List<E> of (E... elements) 任意の数の要素を含む不変のリストを返します。

違い

Arrays.asList()は可変長引数を受け取るメソッドしかないのに対し、 List.of()は可変長以外にも0~10の引数を受け取るメソッドが個別で定義されている。

それぞれのソースコード

Oracle JDK を Eclipse で使えるようにして、それぞれのメソッド定義を参照した。

Arrays.asList()

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a); // 注:内部クラスの ArrayList(下記)を呼んでいる
}

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private static final long serialVersionUID = -2764017481108945198L;
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }

    @Override
    public int size() {
        return a.length;
    }

    @Override
    public Object[] toArray() {
        return Arrays.copyOf(a, a.length, Object[].class);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
            return Arrays.copyOf(this.a, size,
                                 (Class<? extends T[]>) a.getClass());
        System.arraycopy(this.a, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    @Override
    public E get(int index) {
        return a[index];
    }

    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }

    @Override
    public int indexOf(Object o) {
        E[] a = this.a;
        if (o == null) {
            for (int i = 0; i < a.length; i++)
                if (a[i] == null)
                    return i;
        } else {
            for (int i = 0; i < a.length; i++)
                if (o.equals(a[i]))
                    return i;
        }
        return -1;
    }

    @Override
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    @Override
    public Spliterator<E> spliterator() {
        return Spliterators.spliterator(a, Spliterator.ORDERED);
    }

    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (E e : a) {
            action.accept(e);
        }
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        E[] a = this.a;
        for (int i = 0; i < a.length; i++) {
            a[i] = operator.apply(a[i]);
        }
    }

    @Override
    public void sort(Comparator<? super E> c) {
        Arrays.sort(a, c);
    }

    @Override
    public Iterator<E> iterator() {
        return new ArrayItr<>(a);
    }
}

List.of()

static <E> List<E> of() {
    return ImmutableCollections.List0.instance();
}
static <E> List<E> of(E e1) {
    return new ImmutableCollections.List1<>(e1);
}
static <E> List<E> of(E e1, E e2) {
    return new ImmutableCollections.List2<>(e1, e2);
}
static <E> List<E> of(E e1, E e2, E e3) {
    return new ImmutableCollections.ListN<>(e1, e2, e3);
}
// :
// :
@SafeVarargs
@SuppressWarnings("varargs")
static <E> List<E> of(E... elements) {
    switch (elements.length) { // implicit null check of elements
        case 0:
            return ImmutableCollections.List0.instance();
        case 1:
            return new ImmutableCollections.List1<>(elements[0]);
        case 2:
            return new ImmutableCollections.List2<>(elements[0], elements[1]);
        default:
            return new ImmutableCollections.ListN<>(elements);
    }
}

// ImmutableCollections.ListXの実装の一つ
// List0, List1, List2, ListN があるが割愛
static final class List1<E> extends AbstractImmutableList<E> {
    @Stable
    private final E e0;

    List1(E e0) {
        this.e0 = Objects.requireNonNull(e0);
    }

    @Override
    public int size() {
        return 1;
    }

    @Override
    public E get(int index) {
        Objects.checkIndex(index, 1);
        return e0;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new InvalidObjectException("not serial proxy");
    }

    private Object writeReplace() {
        return new CollSer(CollSer.IMM_LIST, e0);
    }

    @Override
    public boolean contains(Object o) {
        return o.equals(e0); // implicit nullcheck of o
    }

    @Override
    public int hashCode() {
        return 31 + e0.hashCode();
    }
}

// ↑の継承元
abstract static class AbstractImmutableList<E> extends AbstractList<E>
                                            implements RandomAccess, Serializable {
    @Override public boolean add(E e) { throw uoe(); } // 注:throw UnsupportedOperationException
    @Override public boolean addAll(Collection<? extends E> c) { throw uoe(); }
    @Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
    @Override public void    clear() { throw uoe(); }
    @Override public boolean remove(Object o) { throw uoe(); }
    @Override public boolean removeAll(Collection<?> c) { throw uoe(); }
    @Override public boolean removeIf(Predicate<? super E> filter) { throw uoe(); }
    @Override public void    replaceAll(UnaryOperator<E> operator) { throw uoe(); }
    @Override public boolean retainAll(Collection<?> c) { throw uoe(); }
    @Override public void    sort(Comparator<? super E> c) { throw uoe(); }
}

速度の検証

内部的には結構違うみたいなので、とりあえず速度を計測してみます。

内容

1要素・10要素・0要素のリストを、Arrays.asList()List.of()で生成。 1億回繰り返して、かかった時間を取得。 それを10回ずつ繰り返して、平均を出力。

ソースコード

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Java9Test {

    public static void main(String[] args) {
        // 1要素のリストを生成
        List<Long> asListResult = new ArrayList<>();
        List<Long> ofResult = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            asListResult.add(arraysAsList());
            ofResult.add(listOf());
        }
        System.out.println("asListResult: " + asListResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());
        System.out.println("ofResult: " + ofResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());

        // 10要素のリストを生成
        List<Long> asListManyResult = new ArrayList<>();
        List<Long> ofManyResult = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            asListManyResult.add(arraysAsListMany());
            ofManyResult.add(listOfMany());
        }
        System.out.println("asListManyResult: " + asListManyResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());
        System.out.println("ofManyResult: " + ofManyResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());

        // 0要素のリストを生成
        List<Long> asListZeroResult = new ArrayList<>();
        List<Long> ofZeroResult = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            asListZeroResult.add(arraysAsListZero());
            ofZeroResult.add(listOfZero());
        }
        System.out.println("asListZeroResult: " + asListZeroResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());
        System.out.println("ofZeroResult: " + ofZeroResult.stream().collect(Collectors.averagingLong((l) -> l)).toString());
    }

    private static long listOf() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = List.of("a");
        }

        return (System.nanoTime() - start) / 1_000;
    }

    private static long arraysAsList() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = Arrays.asList("a");
        }

        return (System.nanoTime() - start) / 1_000;
    }

    private static long listOfMany() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
        }

        return (System.nanoTime() - start) / 1_000;
    }

    private static long arraysAsListMany() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
        }

        return (System.nanoTime() - start) / 1_000;
    }

    private static long listOfZero() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = List.of();
        }

        return (System.nanoTime() - start) / 1_000;
    }

    private static long arraysAsListZero() {
        long start = System.nanoTime();

        List<String> a;
        for (int i = 0; i < 100_000_000; i++) {
            a = Arrays.asList();
        }

        return (System.nanoTime() - start) / 1_000;
    }

}

結果

単位はµs(マイクロ秒)です。

// 1要素のリストの生成時間
asListResult: 1812.4
ofResult: 2865.1

// 10要素のリストの生成時間
asListManyResult: 3285.1
ofManyResult: 2533388.9

// 0要素のリストの生成時間
asListZeroResult: 1159.2
ofZeroResult: 451.8

List.of()遅すぎやしませんか……?

まとめ

正しく計測できているか不安ですが、基本的にArrays.asList()のほうが、リストの生成は速いみたいです。 特に要素数が多い場合は、List.of()がだいぶ遅くなるようです。 ただし、0要素のリストを生成する場合は、List.of()はキャッシュしてあるインスタンスを返すため、速いようです。

List.of()の明日はどっちだ!?

要素の取得の速度については測っていないので誰か試してくだしあ(丸投げ)。