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 | Integer[] a = new Integer[]{1,2,3,4,5,6,7,8,9,10}; |
3. 数组或集合转为流
3.1 集合或引用类型数组转化为流
在Java中, 每一个集合或者引用类型数组都能转化为对应的流Stream<?>
. 如果是集合的话, 直接调用stream()
方法就可以获得对应的流, 如果是引用类型数组的话, 可以使用Arrays类中的静态方法Arrays.stream()
1 | Set<Integer> set = new HashSet<>(); //Collection转化为Stream<?> |
1 | Integer[] integers = new Integer[]{0,1,2,3,4}; //引用类型数组转化为Stream<?> |
3.2 基本类型数组转化为流
对于基本类型数组, 转化成流也可以使用Arrays类中的静态方法Arrays.stream()
.
但是要注意, 基本数据类型数组转化的流就不是Stream<?>
这样的类型了. 因为int
这样的不算一个类, 不能出现在泛型中. 但是为了避免计算的时候频繁的装箱拆箱, Java特意设置了3个基本类型流IntStream
, LongStream
, DoubleStream
.
这样, int[], long[], double[]
也可以转化成对应的基本类型流了. 但是要注意, 对于这3种基本类型之外的基本类型数组char[], float[]
这样的还是没法转化成流.
1 | int[] ints = new int[]{0,1,2,3,4}; |
3.3 注意点
1 | //注意: 上文说的类型Stream<?>, IntStream等严格来说并不是一个类型, 而是一个接口. |
4. Stream<Integer>和IntStream
刚才说了这么多, 我们已经拿到Integer[]
转换的流Stream<Integer>
和int[]
转化的流IntStream
了, 但是这两者怎么互相转化呢?
4.1 Stream<Integer>
转化为 IntStream
查看类型Stream
的源码, 发现有Stream<?>
转化为IntStream, LongStream, DoubleStream
的方法
1 | IntStream mapToInt(ToIntFunction<? 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 | Object[] toArray(); //无参方法, 返回Object[], 但是Object[]强制转化为Integer[]的时候会出现运行时异常, 所以一般不用 |
1 | Integer[] integers3 = integerStream2.toArray(Integer[]::new); |
5.2 基本类型流转化成数组
对于基本类型的流IntStream
, 转化成数组也是用的toArray()
方法
1 | int[] toArray(); //对于基本类型的流的toArray()方法, 既不用传参, 也不用强制类型转换 |
1 | int[] ints2 = intStream.toArray(); |
5.3 引用类型流转化成集合
引用类型流Stream<?>
转为集合, 为collect()
方法. 有2个重载的collect()
方法
1 | <R, A> R collect(Collector<? super T, A, R> collector); |
1 | <R> R collect(Supplier<R> supplier, |
5.4 基本类型流转化成集合
基本类型的流转成集合只有一种collect()
方法, 原理和上述引用类型的collect()
相同. 不再赘述.
1 | <R> R collect(Supplier<R> supplier, |
6. 总结
了解上面的知识之后, 我们就可以在集合, 引用类型数组, 基本类型数组之间通过流进行转化了, 转化的关键分为三步,
- 将集合或数组转化成流,
- 再利用引用类型流和基本类型流进行转化,
- 最后将流再转化回集合或数组.
6.1 int[] 与 Integer[]互相转化
1 | // int[] => Integer[] |
1 | // Integer[] => int[] |
6.2 int[] 与 Collection<Integer> 互相转化
为了简单起见, 这里的Collection都用List来代替, Set, Queue等集合同理
1 | // int[] => Collection<Integer> |
1 | // Collection<Inetger> => int[] |
6.3 Integer[] 与 Collection<Integer> 互相转化
为了简单起见, 这里的Collection都用List来代替, Set, Queue等集合同理
1 | // Integer[] => Collection<Integer> |
1 | // Collection<Integer> => Integer[] |
同时, 这两者的转化还可以不需要Stream<?>
流. 这也是唯二不需要流就能互相转化的了.
1 | // Integer[] => Collection<Integer> |
1 | // Collection<Integer> => Integer[] |