数组转List,一定要小心这个坑!

2020-09-22 15:09:32 浏览数 (1)

在日常开发过程中,数组转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.ArrayListjava.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源码:

代码语言:javascript复制
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却老老实实的两个方法都实现了。

代码语言:javascript复制
    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进去,这里就不贴代码了,这是最笨的办法。

总结

  1. Arrays.asList(strArray)方式将数组转换为List后,不能增删改原数组的值,仅作读取使用;
  2. ArrayList构造器方式,在List的数据量不大的情况下,可以使用;
  3. 集合工具类Collections.addAll(),在List的数据量巨大的情况下,优先使用,可以提高操作速度。
  4. 不仅是ArrayList,其余List的子类(LinkList/Vector)也可以用同样的方法实现数组和集合转变。

0 人点赞