我们经常会使用Arrays.asList来初始化一个列表List。例如
List<String> list = Arrays.asList("123","234");
或者
List<String> list = Arrays.<String>asList("123",234");
不过这里存在一个容易忽视的问题,Arrays.asList返回的是一个底层为大小固定的数组所构成的ArrayList。底层大小固定,所以任何对list的添加、删除或者运用到添加、删除操作的动作都无法进行。
例如上面的代码执行:
list.add("456");
就会报java.lang.UnsupportedOperationException异常。
更好一点的做法是将asList产生的列表当做参数传到另一个集合的构造器中来进行初始化,就不会有这样的限制。
List<String> list = new ArrayList<String>(Arrays.asList("123","234"));
不过为什么会有这样的限制呢。我们看Arrays.asList的源码
Arrays.java
public class Arrays{
...
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
/**
* 该静态内部类中并没有重写父类的add,remove等方法
*/
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) {
//requireNonNull只是判断参数array是否为空,空抛出异常
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public E get(int index) {
return a[index];
}
//重写了set方法,所以对list是可以进行set操作的。当然如果index超出了原来数组的大小,还是会报ArrayIndexOutOfBoundsException异常的
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
...
}
注意到asList返回的是一个静态内部类示例,且内部类中存储数据的是一个final类型的数组。从这里我们也知道,final类型的数组有些特殊,并不是说完全不可改变,只是数组的大小不能变,数组的元素却还可以替换。
另一方面,静态类并没有重写add,remove方法。而它的父类AbstractList中对于add,remove方法是怎么定义的呢
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
这也就是为什么在对Arrays.asList()返回的列表进行增添和删除操作会抛出UnsupportedOperationException异常的原因了。
这里我们可以补充一下final的知识。当对基本数据类型运用final时,数据即被锁定,无法修改。但对自定义类型,类等引用类型运用final时,final
将引用固定。一旦引用被初始化指向某个对象,那么这个引用就不能再指向别的对象了,但对象自身确是可以改变的。数组也是一种对象。
如果我们在给final变量初始化一个对象前,将对象赋给另一个引用a,那么就可以在外部通过a来改变这个对象。而final保持恒定不变的只是自身的引用,对象本身却还是改变了。如下例:
public class Main {
private final String[] arr;
public Main(String[] arr){
this.arr = arr;
}
public static void main(String[] args) {
String[] a = new String[]{"123","234","345"};
Main main = new Main(a);
a[0] = "000";
main.arr[1] = "123";
for (String s : main.arr) {
System.out.print(s+" ");
}
}
}
在给final变量arr初始化 new String[]{“123”,”234”,”345”};对象的时候,我把对象先赋给了引用a,初始化arr之后,arr指向了 new String[]{“123”,”234”,”345”}这个对象,但a也指向这个对象。final保证了arr只能指向这个对象,无法另行指向别的数组。但保证不了对象自身的改变,我通过a改变了这个对象。
所以在用final的时候,最佳实践是只用在基本数据类型上。否则可能会出现不可预料的错误