优秀的编程知识分享平台

网站首页 > 技术文章 正文

被asList坑了后,整理出Java数组与集合相互转换正确方式

nanyue 2024-08-12 22:19:55 技术文章 9 ℃

[啤酒]满怀忧思,不如先干再说!做干净纯粹的技术分享!欢迎评论区或私信交流!

本文记录一下数组和集合相互转换的几种方法和一些坑,自己总结的同时也分享给有缘人,希望在工作和面试的时候有所帮助,集合和数组的作用区别就不废话了,直接进入正文!

共识

首先要达成的共识:

  • 数组可以存储任意类型的数据,包括基本数据类型和引用数据类型【也就是对象】
  • 而集合虽然可以扩展大小,但是只能存储对象并不能存储基本数据类型

所以在使用集合存储整数,字符,小数时需要使用对应的包装类

不禁问一下初学Java的小朋友,String是基本数据类型吗?[狗头][白眼]

接下来就是,数组和集合相互转换的具体实现和常见BUG的复现和解决!

数组转集合

asList基本数据类型数组问题

java.util.Arrays这个JDK提供的工具类中有一个asList方法,可以将数组转换为一个集合

// 声明数组
Integer[] arr = {1,2,3,4,5};

// 转换集合
List<Integer> list = Arrays.asList(arr);

// 遍历数组
for (Integer arrResult : arr) {
    System.out.println(arrResult);
}
// 遍历集合
for (Integer listResult : list) {
    System.out.println(listResult);
}

上边的数组是引用数据类型,转换时没一点问题,

但是当数组是基本数据类型时就会出现bug了,如下方代码,转换前数组长度为5,转换后长度变为1

// 声明数组,基本数据类型
int[] arr = {1,2,3,4,5};

// 转换集合,此时的泛型编译器自动识别为【int[]】类型
List<int[]> list = Arrays.asList(arr);
// 原数组长度为5
System.out.println(arr.length);
// 转换后的集合大小大小为1
System.out.println(list.size());

这个问题原因通过看asList源码可以了解到

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

此处发现asList方法接收一个泛型变长参数,而基本数据类型不能泛型化,只有对象可以泛型化,如果想让基本数据类型泛型化必须使用其包装类,如果使用基本数据类型的数组通过asList转换,则直接将数组当做一个数据塞进了集合中,所以得到的集合大小为1。

不仅仅int有这样的问题,其他的7个基本数据类型都有该问题,使用时一定注意!

asList转换的集合不能添加和删除元素

下方代码将学生数组转换为集合

// 声明数组
String[] students = {"石昊","石毅","月禅","火灵儿"};
// 转换为集合
List<String> list = Arrays.asList(students);

// 向集合中添加数据
list.add("石中天");

对集合进行add或者remove方法就会出现非法操作异常

根据上边asList源码发现,是new ArrayList返回,问题就在于创建的ArrayList并不是java.util包中的,而是Arrays工具类中的内部类

java.util.ArrayList相同继承了java.util.AbstractList这个抽象类,而该抽象类中的add方法默认返回异常

  • java.util.ArrayList:重写了add,remove等方法,所以可以添加数据
  • Arrays.ArrayList:并没有重写,调用时依旧可以调用,但是就会抛出异常

下方为Arrays.ArrayList内部类部分源码

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) {
        a = Objects.requireNonNull(array);
    }
    // 获取长度
    @Override
    public int size() {
        return a.length;
    }
    // 转换为数组
    @Override
    public Object[] toArray() {
        return a.clone();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        if (a.length < size)
            return Arrays.copyOf(this.a, size,
                                 (Class<? extends T[]>) a.getClass());
        System.arraycopy(this.a, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    @Override
    public E get(int index) {
        return a[index];
    }

    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }

    @Override
    public int indexOf(Object o) {
        E[] a = this.a;
        if (o == null) {
            for (int i = 0; i < a.length; i++)
                if (a[i] == null)
                    return i;
        } else {
            for (int i = 0; i < a.length; i++)
                if (o.equals(a[i]))
                    return i;
        }
        return -1;
    }

    @Override
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    @Override
    public Spliterator<E> spliterator() {
        return Spliterators.spliterator(a, Spliterator.ORDERED);
    }

    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        for (E e : a) {
            action.accept(e);
        }
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        E[] a = this.a;
        for (int i = 0; i < a.length; i++) {
            a[i] = operator.apply(a[i]);
        }
    }

    @Override
    public void sort(Comparator<? super E> c) {
        Arrays.sort(a, c);
    }
}

由此看出其实asList返回的是一个长度不可变的List,数组多长,返回的List也就多大,仅仅是包了一个外壳,并不支持扩容,新增数据和删除数据等操作,如果将这个List通过方法调用传递到一个要add和removeList的方法中就会产生意想不到的结果。

这可以认为是Java设计上的一个缺陷,使用时一定要注意!

如果你确定这个集合只读,而不会做修改操作,则可以放心使用,但是如果拿捏不准,就需要其装进java.util包下的ArrayList中,转换成 "真正的" ArrayList,以免出现不可预料的结果

// 声明数组
String[] students = {"石昊","石毅","月禅","火灵儿"};
// 转换为集合
List<String> list = Arrays.asList(students);

// 放进java.util的ArrayList中
List<String> realList = new ArrayList<>(list);

// 向集合中添加数据
realList.add("石中天");
for (String s : realList) {
    System.out.println(s);
}

通过Collections.addAll转换

// 声明数组
String[] students = {"石昊","石毅","月禅","火灵儿"};
// 创建集合
ArrayList<String> list = new ArrayList<>();

Collections.addAll(list,students);
list.add("石中天");
for (String s : list) {
    System.out.println(s);
}

此处创建java.util.ArrayList,可以继续进行add和remove等操作,但是不能将基本数据类型的数组转换为集合

通过JDK8的流操作

此处可以使用boxed()方法将基本数据类型的数组转换成包装类,非常方便

// 声明数组
int[] arr = {1,2,3,4};
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());
list.add(5);
for (Integer result : list) {
    System.out.println(result);
}

集合转数组

通过集合对象的toArray方法

// 返回Object类型数组
Object[] toArray();
// 返回指定类型数组
<T> T[] toArray(T[] a);

将Integer集合转换为Integer类型数组,这里的数组也必须是包装类

// 声明数组
List<Integer> list = new ArrayList<>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);

Integer[] array = list.toArray(new Integer[list.size()]);
for (Integer result : array) {
    System.out.println(result);
}


这就是数组和集合之间转换的几种常见方式和隐患,尤其是asList一定不要被坑了

希望可以帮助你少走一些弯路,节约一些时间,解决一些实质问题,更多干货可持续关注[啤酒]

最近发表
标签列表