Java基础/引用类型数组与集合之间的相互转换

3,090 阅读4分钟

数组转集合

数组转集合按照基础数据类型和引用类型来分大致可以分为两类来介绍,但是其中有特殊情况以及与此相关的一些坑也值得深度挖掘下。

基本数据类型(int, double, long)数组转集合

对于这3种包装类型,Arrays工具类中提供了相应的API可以将其转化成IntStream, DoubleStream和LongStream,再进行装箱操作便可以转换成相应的集合。

        int[] intArr = {1, 2, 3, 4, 5};
        List<Integer> intList = Arrays.stream(intArr).boxed().collect(Collectors.toList());

        double[] doubleArr = {1d, 2d, 3d, 4d, 5d};
        List<Double> doubleList = Arrays.stream(doubleArr).boxed().collect(Collectors.toList());

        long[] longArr = {1L, 2L, 3L, 4L, 5L};
        List<Long> longList = Arrays.stream(longArr).boxed().collect(Collectors.toList());

其他基础数据类型数组转集合

对于short,float等其他基础数据类型,由于JDK1.8没有提供相应的ShortStream及FloatStream类型的接口,同时Arrays也没有相应的API接口,所以这里本人目前想到的只能用新初始化一个包装类型的数组然后Arrays.asList来进行。

        float[] floatArr = {1f, 2f, 3f, 4f, 5f};
        Float[] lFloatArr = new Float[floatArr.length];
        for (int i=0; i<floatArr.length; i++) {
            lFloatArr[i] = floatArr[i];
        }
        List<Float> floatList = Arrays.asList(lFloatArr);

这里需要注意的是这个Arrays.asList的API,这里我先放上JDK源码里面的java doc

Returns a fixed-size list backed by the specified array. (Changes to the returned list "write through" to the array.) This method acts as bridge between array-based and collection-based APIs, in combination with {@link Collection#toArray}. The returned list is serializable and implements {@link RandomAccess}.

其中大致有以下几点需要注意:

  1. 返回的是一个固定大小的继承AbstractList的Arrays内部类ArrayList(与java.util.ArrayList不一样)
  2. Arrays.asList(E[] array)中E需要是一个引用类型,所以基础类型不能使用这个方法
  3. Arrays.ArrayList里面仅Arrays.asList(E[] array)维护了一个入参E[] array的引用E[] a和实现了抽象类AbstractList的部分方法,如下图可以发现array和a在内存里面指向同一个位置
  4. Collection toArray API一起提供了介于数组类型和集合类型API之间的桥梁

        // Fixed Size List
        List<Float> floatList = Arrays.asList(lFloatArr);
        // (1) 修改值会引起原数组的值的变动,内存一样
        floatList.set(0, 11f);
        System.out.println(floatList);  // [11.0, 2.0, 3.0, 4.0, 5.0]
        System.out.println(Arrays.toString(lFloatArr));  // [11.0, 2.0, 3.0, 4.0, 5.0]
        // (2) UnsupportedOperationException,调用了AbstractList.add()方法。
        // 而ArrayList.add()调用的是ArrayList重载的add方法
         floatList.add(11f);

引用类型数组转集合

引用类型的数组转集合就直接使用Arrays.asList API即可,由于Arrays.asList的潜在问题,这里最好通过一个具体集合类实例化的方式进行包装。

        String[] stringArr = {"A", "C", "E"};
        List<String> stringList = new ArrayList<>(Arrays.asList(stringArr));

        Point[] pointArr = {new Point(1,1), new Point(2, 2), new Point(3, 3)};
        List<Point> pointList = new ArrayList<>(Arrays.asList(pointArr));

集合转数组

集合转数组可以直接使用Collection toArray API。

        int[] intArr = {1, 2, 3, 4, 5};
        List<Integer> intList = Arrays.stream(intArr).boxed().collect(Collectors.toList());

        Integer[] intArr2 = intList.toArray(new Integer[]{});
        System.out.println(Arrays.toString(intArr2));

what's the difference?

使用Arrays.stream来进行数组转集合和遍历有什么区别呢?难道仅仅是代码更简洁,使用了stream API逼格更高端? No, No, No,性能上也有区别。以基础类型为例,由于多了装箱的操作,性能也会有一点影响。

下面有两组3种情况的对比测试用例:

  • case 1: 遍历转换
  • case 2: Arrays.stream转换
  • case 3: 内部stream parallell API(Arrays.stream的内部实现方式)

通过下面程序的测试结果,我们可以发现:

  1. 仅转换情况下,case 1 < case 2 < case 3,个人以为这个是装箱(boxed)以及并行流(parallelStream)的多线程额外开销,不过装箱导致的额外开销比较小
  2. 转换+复杂计算的情况下,case 1 约等于 case 2 远大于 case 3,可以发现在加上复杂运算的情况下,Arrays.stream不会导致性能的下降,甚至有些许提升;而并行流多线程运算方式能大幅增加速度。

So what's the difference ???

  1. 性能影响较小,大多数场景下可以忽略不计
  2. 代码行数减少,代码更加简洁
  3. 逼格提升了一点
10次累计耗时 case 1(ms) case 2(ms) case 3(ms)
仅转换 96 107 123
转化+计算 6129 6106 751
        int size = 1000000;
        int[] intArr = new int[size];

        List<Long> time1 = new ArrayList<>();
        List<Long> time2 = new ArrayList<>();
        List<Long> time3 = new ArrayList<>();

        Function<Integer, Integer> func = it -> (int)(Math.pow(it, 3) * Math.PI);
//        Function<Integer, Integer> func = it -> it;
        int iter = 10;

        for (int i=0; i<iter; i++) {
            long start1 = System.currentTimeMillis();
            // case 1:
            List<Integer> intList1 = new ArrayList<>();
            for (int item : intArr) {
                intList1.add(func.apply(item));
            }
            long start2 = System.currentTimeMillis();
            time1.add(start2 - start1);

            // case 2:
            List<Integer> intList3 = Arrays.stream(intArr).boxed().map(func).collect(Collectors.toList());
            long start3 = System.currentTimeMillis();
            time2.add(start3 - start2);

            // case 3:
            List<Integer> intList4 = StreamSupport.intStream(spliterator(intArr, 0, size), true)
                    .boxed().map(func).collect(Collectors.toList());
            long start4 = System.currentTimeMillis();
            time3.add(start4 - start3);
        }

        System.out.println(time1.stream().reduce(0L, Long::sum));
        System.out.println(time2.stream().reduce(0L, Long::sum));
        System.out.println(time3.stream().reduce(0L, Long::sum));

写个小博客,表情包都用上了,如果觉得总结的有点用的,点个赞呗~