在日常开发过程中,数组转List的使用频率非常之高。大家可以回想一下,通常你都是怎么转的呢?
用代码说话,下面来看一段代码:
代码语言:javascript复制public class Test {
public static void main(String[] args) {
List<String> list= Arrays.asList("hello","world");
System.out.println(list.remove("hello"));
}
}
先自己想想这段代码有没有问题,使用
List<String> list= Arrays.asList("hello","world");
来创建一个List,然后再使用remove将其移除。从逻辑上来说,确实没毛病,也有很多人认为这本来就没问题。下面我们来运行一下上面这段代码,结果:
很多人估计都不敢相信自己的眼睛,上面这段代码居然会报错。
如果我们把上面这段代码改一下:
代码语言:javascript复制public class Test {
public static void main(String[] args) {
List<String> list= new ArrayList<>();
list.add("hello");
list.add("world");
System.out.println(list.remove("hello"));
}
}
运行结果:
代码语言:javascript复制true
奇了怪了吧,这就没问题了。
我们来看看
Arrays.asList("hello");
这个asList方法到底干了些什么?
代码语言:javascript复制public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
粗略的看了一下,他返回的不就是ArrayList
对象吗?下面这段代码也是new一个ArrayList对象。
List<String> list= new ArrayList<>();
为什么上面那段就不行呢?整个java.util.Arrays.ArrayList
类有哪些方法
他居然是Arrays的一个静态内部类。现在可以确认这个
java.util.Arrays.ArrayList
和java.util.ArrayList
不是同一个。
重点来了,这个静态内部类里有个final修饰的数组:
代码语言:javascript复制private final E[] a;
final修饰变量表示此变量是不可修改的。也就是我们上面的remove为什么报错的原因。居然是因为这个Arrays中的ArrayList中使用的是一个固定大小的数据来存储数据的,同理我们也可以推断,不能使用add方法,下面我来试试add方法就知道了。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
List<String> list= Arrays.asList("hello","world");
System.out.println(list.add("!"));
}
}
运行结果:
运行结果和我们预期的是一样的。
另外我们看看java.util.ArrayList
源码:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
这段代码有也有个数组用来保存数据:
代码语言:javascript复制 transient Object[] elementData;
人家不是使用final修饰,transient
修饰只是和系列化有关系。所以人家java.util.ArrayList
是不会报异常的。
上面Arrays.ArrayList中居然没有add和remove方法。认真的你会发现,它也继承了AbstractList。进去看看她的源码:
代码语言:javascript复制public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
protected AbstractList() {
}
//这是前面我们使用过,并且抛异常的方法
public boolean add(E e) {
add(size(), e);
return true;
}
//上面的add方法调用的是这个方法
public void add(int index, E element) {
//抛异常
throw new UnsupportedOperationException();
}
public E remove(int index) {
//抛异常
throw new UnsupportedOperationException();
}
//其他代码省略
}
前面已经把java.util.Arrays.ArrayList
的方法都看过了,并没有add和remove,也就是说他没实现父类的add和remove两个方法。那就是调用了AbstractList
的方法了,所以上面抛的两个异常是在这里抛出来的。
相反java.util.ArrayList
却老老实实的两个方法都实现了。
public boolean add(E e) {
ensureCapacityInternal(size 1); // Increments modCount!!
elementData[size ] = e;
return true;
}
public E remove(int index) {
rangeCheck(index);
modCount ;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index 1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
好了,上面问题已经得出了答案。下面来说说应该如何正确使用数组转List。
正确的姿势
倒也不是说List<String> list= Arrays.asList("hello");
这个不要用,只是你得搞清楚原理,一不小心就会为别人(也可能是自己)留下坑。
方式一:
代码语言:javascript复制String[] strings = {"hello", "world"};
List list = new ArrayList(Arrays.asList(strings));
方式二(推荐):
代码语言:javascript复制 String[] strings = {"hello", "world"};
List<String> list = new ArrayList<>(strings.length);
Collections.addAll(list, strings);
方式三:
原始的方法就是变量数组,然后new 对象ArrayList,遍历数组,一个一个add进去,这里就不贴代码了,这是最笨的办法。
总结
- Arrays.asList(strArray)方式将数组转换为List后,不能增删改原数组的值,仅作读取使用;
- ArrayList构造器方式,在List的数据量不大的情况下,可以使用;
- 集合工具类Collections.addAll(),在List的数据量巨大的情况下,优先使用,可以提高操作速度。
- 不仅是ArrayList,其余List的子类(LinkList/Vector)也可以用同样的方法实现数组和集合转变。