LoginSignup
1
2

More than 3 years have passed since last update.

Arrays.asListメソッドが返すのはjava.util.ArrayListではない

Last updated at Posted at 2020-04-22

Arrays.asListとjava.util.ArrayListには互換性はない

可変長の引数からListオブジェクトを生成できるstaticメソッド、Arrays.asList(T... t)だが名前が似ているからってjava.util.ArrayListのオブジェクトは返さない。

以下のようなコードはコンパイルエラーとなる。

java.util.ArrayList<Integer> arrayList = Arrays.asList(1,2,3); // no instance(s) of type variable(s) T exist so that List<T> conforms to ArrayList<Integer>

List型で返されるのでjava.util.ArrayListにキャストすればよさげに見えるし、実際コンパイルエラーは消える。

やっちゃダメなキャスト
java.util.ArrayList<Integer> arrayList = (java.util.ArrayList<Integer>) Arrays.asList(1,2,3);

でも実行時にClassCastExceptionが出ちゃいます。絶対にやっちゃダメ。

Arrays.asListの中身を見てみる

Arrays.asListメソッドが何を返しているかを確認するために実装を覗いてみる。

Array.java
@SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
...

return new ArrayList<>(a);でArrayListを返しているじゃん!って言いたくなるけど、更にそのArrayListを辿ってみる。

Array.java
/**
     * @serial include
     */
    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);
        }
...

なんとArrayクラス内にネストしたprivateでstaticなArrayListクラスを持っており、それをnewしてインスタンス生成していることがわかる。

そしてそのArrayListクラスはAbstractListを継承しており、java.util.ArayListクラスを継承してはいない。(java.util.ArayListもAbstractListを継承しているので継承の階層的には同じ)

つまり、Arrays.asListは、java.util.ArrayListと同名(パッケージを除く)だが異なるクラス(継承関係も無い)のインスタンスを返していることが分かる。

java.util.ArrayListクラスArraysクラス内のArrayListクラスでは互換性が無いのである。

ゆえに上記の

やっちゃダメなキャスト
java.util.ArrayList<Integer> arrayList = (java.util.ArrayList<Integer>) Arrays.asList(1,2,3);

ClassCastExceptionを投げる理由が判明した。

公式ドキュメントを読んでみる

Arraysクラスの公式ドキュメントを読んでみると、asListの説明には

指定された配列に連動する固定サイズのリストを返します。

とある。

つまり、Arraysクラス内のArrayListクラスは固定長のリストであり、可変長リストであるjava.util.ArrayListとの違いが分かる。

Arrays.asListで取得したListにaddをしちゃダメ

上記で確認したようにArrays内のArrayListクラスは固定長なので、リスト内の要素数を変更することはできない。

なので以下のようなコードはコンパイルは通るが実行時に例外UnsupportedOperationExceptionが発生してしまう。

// 固定長リストに対して要素の追加をしようとしている
List<Integer> list1 = Arrays.asList(1,2,3);
list1.add(4);  // UnsupportedOperationException

これは、継承しているAbstractListクラスの公式ドキュメントのaddメソッドの説明に

この実装は、add(int, E)がオーバーライドされないかぎりUnsupportedOperationExceptionをスローします。

とあるように、Arrays内のArrayListクラスでadd(int, E)メソッドをオーバーライドしていないためである。

Arrays.asListが固定長のリストを返すと分かっていないとaddをつかってしまう人も多いのではなかろうか。

removeはどうか

ちなみにremoveメソッドも使うと実行時にUnsupportedOperationExceptionが発生する。

あくまで固定長のリストなので、絶対にサイズを変えさせないという強い意志を感じる...。

まとめ

Arrays.asListが返すリストとjava.util.ArrayListには互換性はありません。キャストはしてはいけません。

ついでにArrays.asListが返すリストは固定長のリストです。後からサイズを変えると例外が発生しちゃいます。

どうしてもjava.util.ArrayList型で使いたいなら、コンストラクタの引数に渡して使ってください。固定長じゃなくなるのでaddもremoveもできます。


// コンパイルエラーも実行時にClassCastExceptionも発生しない
List<Integer> list1 = Arrays.asList(1,2,3);
ArrayList<Integer> list2 = new ArrayList<>(list1);

ただ、Arrays.asListが返すのは固定長のリストというメリットが全く無くなってしまうので、わざわざこのようなコードを書く必要性は薄いと思いますし、リストはListインターフェース型で扱うのが一番良いと思います。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2