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
メソッドが何を返しているかを確認するために実装を覗いてみる。
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
...略
return new ArrayList<>(a);
でArrayListを返しているじゃん!って言いたくなるけど、更にそのArrayList
を辿ってみる。
/**
* @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インターフェース型で扱うのが一番良いと思います。