0%

Java中Integer[], int[]和Collection的相互转换

1. 前言

今天刷leetcode 349题的时候, 发现了一个有趣的事情.

原题是求两个数组的交集, public int[] intersection(int[] nums1, int[] nums2). 我觉得你们应该也不会觉得这个题难. 转化为两个集合之后很简单就做出来了. 但是, 题目要求返回的是int[].

一开始我也没当回事, 以为调用一个toArray()方法就可以的. 但是这种方法根本不行, 只能得到Integer[] 而不是int[]. 并且Integer[]没法强制转换成为int[].

事实上, 如果我们想将一个Set<Integer>转化成int[], 还是需要一番操作的.

首先最笨的方法就是new一个int[]然后逐个元素添加. 但是这种方法显得太古老了一些.

在网上查了一番资料之后, 找到了解决这个问题的办法, 可以实现Integer[]int[]互转. 那就是用Java 8中新引入的特性, Stream.

2. Stream

其实Stream这个引入并不是为了数据转换用的, 而是流式计算. 里面的各种方法和Spark RDD的操作方法极其一致. 可以将Java中的Stream看成Spark中的RDD. 都支持map, reduce, count, filter等各种操作. 其实这些操作也很像SQL.

有了Stream之后, 我们在java中就可以实现链式编程了.

例如, 在一个数组中打印出小于10的, 偶数, 降序排序的, 前2个元素平方字符串表示. 可以这样实现,

1
2
3
4
5
6
7
8
Integer[] a = new Integer[]{1,2,3,4,5,6,7,8,9,10};
Arrays.stream(a).filter((x) -> x < 10)
.filter((x) -> x % 2 == 0)
.sorted((i1, i2) -> i2.compareTo(i1))
.limit(2)
.map(i -> i * i)
.map(String::valueOf)
.forEach(System.out::println);

3. 数组或集合转为流

3.1 集合或引用类型数组转化为流

在Java中, 每一个集合或者引用类型数组都能转化为对应的流Stream<?>. 如果是集合的话, 直接调用stream()方法就可以获得对应的流, 如果是引用类型数组的话, 可以使用Arrays类中的静态方法Arrays.stream()

1
2
3
4
5
Set<Integer> set = new HashSet<>();                //Collection转化为Stream<?>
for (int i = 0; i < 5; i++) {
set.add(i);
}
Stream<Integer> integerStream1 = set.stream();
1
2
Integer[] integers = new Integer[]{0,1,2,3,4};     //引用类型数组转化为Stream<?>
Stream<Integer> integerStream2 = Arrays.stream(integers);

3.2 基本类型数组转化为流

对于基本类型数组, 转化成流也可以使用Arrays类中的静态方法Arrays.stream().

但是要注意, 基本数据类型数组转化的流就不是Stream<?>这样的类型了. 因为int这样的不算一个类, 不能出现在泛型中. 但是为了避免计算的时候频繁的装箱拆箱, Java特意设置了3个基本类型流IntStream, LongStream, DoubleStream.

这样, int[], long[], double[]也可以转化成对应的基本类型流了. 但是要注意, 对于这3种基本类型之外的基本类型数组char[], float[]这样的还是没法转化成流.

1
2
3
4
5
6
7
8
9
10
int[] ints = new int[]{0,1,2,3,4};                   
IntStream intStream = Arrays.stream(ints); //正确
char[] chars = new char[]{'a','b'};
CharStream = Arrays.stream(chars); //错误, 没有CharStream这个类型

//我们查看Arrays.stream()的各个重载方法也能发现这个结果, 即只有引用类型数组和int, long, double这三种基本类型数组才能调用Arrays.stream()
public static <T> Stream<T> stream(T[] array);
public static IntStream stream(int[] array);
public static LongStream stream(long[] array);
public static DoubleStream stream(double[] array);

3.3 注意点

1
2
3
4
5
//注意: 上文说的类型Stream<?>, IntStream等严格来说并不是一个类型, 而是一个接口.
//Stream<?>真正的实现类是抽象类ReferencePipeline下面的一个静态内部类ReferencePipeline.Head,
//IntStream真正的实现类是抽象类IntPipeline下面的一个静态内部类IntPipeline.Head,
//LongStream, DoubleStream也有类似的实现类.
//但是为了下文描述方便, 仍然采用Stream<?>类型, IntStream类型这样的描述.

4. Stream<Integer>和IntStream

刚才说了这么多, 我们已经拿到Integer[]转换的流Stream<Integer>int[]转化的流IntStream了, 但是这两者怎么互相转化呢?

4.1 Stream<Integer> 转化为 IntStream

查看类型Stream的源码, 发现有Stream<?>转化为IntStream, LongStream, DoubleStream的方法

1
2
3
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

所以可以用这些方法实现转化.

1
IntStream intStream2 = integerStream2.mapToInt(x -> x);

4.2 IntStream转化为Stream<Integer>

查看类IntStream的源码, 发现也有一个方法box()转化成对应的包装类.

1
Stream<Integer> boxed();

所以可以用这个方法转化成包装类的流.

1
Stream<Integer> integerStream3 = intStream.boxed();

LongStream转化为Stream<Long>, DoubleStream转化为Stream<Double>同理

5. 流转化为数组或集合

流转化为数组, 也有对应的方法. 两种流都为toArray()方法. 流转为集合, 为collect()方法.

5.1 引用类型的流转化成数组

对于引用类型的流Stream<?>, 转化成数组有2种方法

1
2
Object[] toArray();     //无参方法, 返回Object[], 但是Object[]强制转化为Integer[]的时候会出现运行时异常, 所以一般不用
<A> A[] toArray(IntFunction<A[]> generator); //要提供返回数组类型的构造器, 不需要强制类型转化
1
2
Integer[] integers3 = integerStream2.toArray(Integer[]::new);
Integer[] integers4 = integerStream2.toArray((i)->{return new Integer[i];}); //lambda表达式形式, 和上一行的方法引用等价

5.2 基本类型流转化成数组

对于基本类型的流IntStream, 转化成数组也是用的toArray()方法

1
int[] toArray();                            //对于基本类型的流的toArray()方法, 既不用传参, 也不用强制类型转换
1
int[] ints2 = intStream.toArray();

5.3 引用类型流转化成集合

引用类型流Stream<?>转为集合, 为collect()方法. 有2个重载的collect()方法

1
2
3
4
5
6
7
8
<R, A> R collect(Collector<? super T, A, R> collector);
//这个方法的参数为Collectors类中定义的一些函数式接口, 可以将流转换成对应的集合.
Integer[] integers = new Integer[]{1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(integers);
//如果测试的话, 下面这三行代码只能保留一个. 否则多次使用一个流会出现异常.
List<Integer> list = stream.collect(Collectors.toList());
Set<Integer> set1 = stream.collect(Collectors.toSet());
Map<Integer, Integer> map = stream.collect(Collectors.toMap((i)->i, (i)->i+1));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
//这三个参数很有mapreduce的感觉. 对于第i个线程, 先用Supplier创建一个类型为R的对象ri, 然后对于流中的每个元素a, 和创建的对象ri, 执行accumulator操作(ri.accumulator(a)). 最后, 把各个线程创建的ri用combiner结合起来,得到新的对象作为返回结果.
//可能举个例子会更容易理解.
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 5; i++) {
set.add(i);
}
Stream<Integer> stream = set.parallelStream();
List<Integer> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
//一个2个线程的并行流[1,2,3,4,5]. [1,2,3]在线程1中, [4,5]在线程2中.
//第一步, 这两个线程分别使用supplier参数创建两个新的对象, 这里创建的是两个new ArrayList<>(). 记他们为r1和r2
//第二步, 对于第i个线程, 流中的每个元素都使用accumulator作用到ri上. 在这里就是将每个元素add到新创建的ArrayList ri上面
//第三步, 对于所有线程生成的ri, 两两执行combiner操作. 在这里r1 = [1,2,3], r2 = [4,5]. 执行addAll操作(r1.addAll(r2)). 返回最后结果.

//也可以写成lambda表达式形式, 只不过更复杂.
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 5; i++) {
set.add(i);
}
Stream<Integer> stream = set.parallelStream();
List<Integer> list = stream.collect(()->{return new ArrayList<>();},
(r, element)->{r.add(element);},
(r1, r2)->{r1.addAll(r2);});

5.4 基本类型流转化成集合

基本类型的流转成集合只有一种collect()方法, 原理和上述引用类型的collect()相同. 不再赘述.

1
2
3
4
5
6
7
8
9
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner);
//例如
int[] ints = new int[]{1,2,3,4,5};
IntStream intStream = Arrays.stream(ints);
Map<Integer, Integer> map = intStream.collect(HashMap::new,
(r, element) -> r.put(element, element * element),
HashMap::putAll);

6. 总结

了解上面的知识之后, 我们就可以在集合, 引用类型数组, 基本类型数组之间通过流进行转化了, 转化的关键分为三步,

  • 将集合或数组转化成流,
  • 再利用引用类型流和基本类型流进行转化,
  • 最后将流再转化回集合或数组.

6.1 int[] 与 Integer[]互相转化

1
2
3
4
5
// int[] => Integer[]
int[] ints = new int[]{1,2,3,4,5};
IntStream intStream = Arrays.stream(ints);
Stream<Integer> integerStream = intStream.boxed();
Integer[] integers = integerStream.toArray(Integer[]::new);
1
2
3
4
5
// Integer[] => int[]
Integer[] integers = new Integer[]{1,2,3,4,5};
Stream<Integer> integerStream = Arrays.stream(integers);
IntStream intStream = integerStream.mapToInt(x -> x);
int[] ints = intStream.toArray();

6.2 int[] 与 Collection<Integer> 互相转化

为了简单起见, 这里的Collection都用List来代替, Set, Queue等集合同理

1
2
3
4
// int[] => Collection<Integer>
int[] ints = new int[]{1,2,3,4,5};
IntStream intStream = Arrays.stream(ints);
List<Integer> intList = intStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
1
2
3
4
5
// Collection<Inetger> => int[]
List<Integer> intList = new ArrayList<>(Arrays.asList(1,2,3,4,5));
Stream<Integer> integerStream = intList.stream();
IntStream intStream = integerStream.mapToInt(x -> x);
int[] ints = intStream.toArray();

6.3 Integer[] 与 Collection<Integer> 互相转化

为了简单起见, 这里的Collection都用List来代替, Set, Queue等集合同理

1
2
3
4
5
// Integer[] => Collection<Integer>
Integer[] integers = new Integer[]{1,2,3,4,5};
Stream<Integer> integerStream = Arrays.stream(integers);
List<Integer> intList = integerStream.collect(Collectors.toList());
List<Integer> intList = integerStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll); // also ok
1
2
3
4
// Collection<Integer> => Integer[]
List<Integer> intList = new ArrayList<>(Arrays.asList(1,2,3,4,5));
Stream<Integer> integerStream = intList.stream();
Integer[] integers = integerStream.toArray(Integer[]::new);

同时, 这两者的转化还可以不需要Stream<?>流. 这也是唯二不需要流就能互相转化的了.

1
2
3
4
5
// Integer[] => Collection<Integer>
Integer[] integers = new Integer[]{1,2,3,4,5};
List<Integer> intList = new ArrayList<>(Arrays.asList(integers));
/*注意, 不能使用以下的方法创建一个ArrayList, 因为这样创建出来的intList只能进行读和修改元素操作, 不能进行添加元素和删除元素操作!!!*/
List<Integer> intList = Arrays.asList(integers);
1
2
3
4
5
// Collection<Integer> => Integer[]
List<Integer> intList = new ArrayList<>(Arrays.asList(1,2,3,4,5));
Integer[] integers = intList.toArray(new Integer[0]);
/*注意, 不能使用下面的方法从ArrayList<Integer>得到Integer[], 因为java中的类型转化只能针对单独的对象, Object[]直接转换到Integer[]是不行的!!!*/
Integer[] integers = (Integer[]) intList.toArray();