专栏首页Java后端技术栈cwnait数组转List,一定要小心这个坑!

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

在日常开发过程中,数组转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.ArrayListjava.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进去,这里就不贴代码了,这是最笨的办法。

总结

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

本文分享自微信公众号 - Java后端技术全栈(jjs-2018),作者:田老师

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅谈几种设计模式

    策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    用户4143945
  • Java ArrayList 的不同排序方法

    由于其功能性和灵活性,ArrayList是 Java 集合框架中使用最为普遍的集合类之一。ArrayList 是一种 List 实现,它的内部用一个动态数组来存...

    用户4143945
  • 再也不敢使用集合默认初始化值了

    集合初始化通常进行分配容量、设定特定参数等相关工作。我们以使用频率相对较高的ArrayList和HashMap为例,简要说明初始化的相关工作,并解释为什么在任何...

    用户4143945
  • JavaSE(八)集合之List

    前面一篇的corejava讲的是集合的概述,这一篇我将详细的和大家讲解一下Collection下面的List、set、queue这三个子接口。希望大家能得到提升...

    用户1195962
  • VisualStudio 打断点和不打断点的区别

    因为小伙伴告诉我他的代码在打断点的时候可以运行,但是在不打的时候出现异常。我去他那里看到,真的是这样,最后发现原来是代码写错了。本文来告诉大家,如果遇到了进入断...

    林德熙
  • react-native之navigation

    这个文件目录除了src 其他的都是通过react-native init my_app自动生成的。

    FinGet
  • Unity 依赖注入

    关于Ioc的框架有很多,比如astle Windsor、Unity、Spring.NET、StructureMap,我们这边使用微软提供的Unity做示例,你可...

    郑小超.
  • Java类库之日期操作类(核心)

    在Java之中,如果要想表示出日期型,则使用java.util.Date类完成。 如果要想通过这个类取得当前的日期时间,那么只需要直接实例化Date类对象即可...

    葆宁
  • 3种 Springboot 全局时间格式化方式,别再写重复代码了

    时间格式化在项目中使用频率是非常高的,当我们的 API 接口返回结果,需要对其中某一个 date 字段属性进行特殊的格式化处理,通常会用到 SimpleDate...

    程序员内点事
  • 3种 Springboot 全局时间格式化方式,别再写重复代码了

    时间格式化在项目中使用频率是非常高的,当我们的 API 接口返回结果,需要对其中某一个 date 字段属性进行特殊的格式化处理,通常会用到 SimpleDate...

    程序员内点事

扫码关注云+社区

领取腾讯云代金券