回复“ 面试 ”获取全套面试资料
在日常开发过程中,数组转List的使用频率非常之高。大家可以回想一下,通常你都是怎么转的呢?
用代码说话,下面来看一段代码:
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将其移除。从逻辑上来说,确实没毛病,也有很多人认为这本来就没问题。下面我们来运行一下上面这段代码,结果:
很多人估计都不敢相信自己的眼睛,上面这段代码居然会报错。
如果我们把上面这段代码改一下:
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" )); } }
运行结果:
true
奇了怪了吧,这就没问题了。
我们来看看
Arrays.asList("hello");
这个asList方法到底干了些什么?
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修饰的数组:
private final E[] a;
final修饰变量表示此变量是不可修改的 。也就是我们上面的remove为什么报错的原因。居然是因为这个Arrays中的ArrayList中使用的是一个固定大小 的数据来存储数据的,同理我们也可以推断,不能使用add方法,下面我来试试add方法就知道了。
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;
这段代码有也有个数组用来保存数据:
transient Object[] elementData;
人家不是使用final修饰,transient
修饰只是和系列化有关系。所以人家java.util.ArrayList
是不会报异常的。
上面Arrays.ArrayList中居然没有add和remove方法。认真的你会发现,它也继承了AbstractList。进去看看她的源码:
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");
这个不要用,只是你得搞清楚原理,一不小心就会为别人(也可能是自己)留下坑 。
方式一:
String[] strings = {"hello" , "world" }; List list = new ArrayList(Arrays.asList(strings));
方式二(推荐):
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)也可以用同样的方法实现数组和集合转变。