映射系列
方法 | 描述
| :-: | -: map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f)| 同上
mapToInt(ToIntFunction f)| 同上
mapToLong(ToLongFunction f)| 同上
flatMap(Function f)| 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
public static void main(String[] args) { final long count = 10; List<Long> list = new ArrayList<>(); for (long i = 0; i < count; i++) { list.add(i); } //使用mapToLong来处理 list.stream().mapToLong(x -> x + 10).forEach(System.out::println); }
普通的Map映射相对来说比较简单,因此这里也先一笔带过了。现在重点讲解一下flatMap的使用和场景:
//给定一个需求:给定一个列表{"aaa","bbb","ddd","eee","ccc"}。需要在控制台直接输出aaabbbdddeeeccc字样
看如下代码实现,对比下map和flatMap的区别
public static void main(String[] args) { List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc"); //采用map来做(这里采用了两次forEach循环进行输出,显然不太优雅) list.stream().map(x -> { List<Character> characterList = new ArrayList<>(); char[] chars = x.toCharArray(); for (char c : chars) { characterList.add(c); } return characterList.stream(); }).forEach(xStream -> xStream.forEach(System.out::print)); //aaabbbdddeeeccc //采用flatMap来做 体会一下flatMap的魅力吧 list.stream().flatMap(x -> { List<Character> characterList = new ArrayList<>(); char[] chars = x.toCharArray(); for (char c : chars) { characterList.add(c); } return characterList.stream(); }).forEach(System.out::print); //aaabbbdddeeeccc }
再看一个例子
//给定一个需求:给定单词列表["Hello","World"],要返回列表["H","e","l", "o","W","r","d"]
对于这样的需求,我们可能想到的第一个版本可能是这样子的:
public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world"); List<String[]> collect = list.stream().map(word -> word.split("")) .distinct() .collect(Collectors.toList()); }
不用输出结果,一看返回值的结构就肯定不是我们想要的结果.
这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列表)。因此, map 返回的流实际上是Stream<String[]> 类型的。你真正想要的是用Stream来表示一个字符流。因此,这是行不通的。
正确的姿势:
public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world"); list.stream().flatMap(x -> Arrays.stream(x.split(""))) .distinct().forEach(System.out::print); //helowrd }
其实map和flatMap的差别特别像List的add方法和addAll方法的差异,可参照理解一下,看下面这个例子
public static void main(String[] args) { List list = new ArrayList(); list.add(1); list.add(2); List list1 = new ArrayList(); list1.add(3); list1.add(4); //注意add和addAll输出的区别 //list.add(list1); //System.out.println(list); //[1, 2, [3, 4]] list.addAll(list1); System.out.println(list); //[1, 2, 3, 4] }
- 排序
方法 | 描述
- | :-: | -:
sorted() | 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序
这个比较简单,这里就不举例子了
Stream的终止操作
终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,例如 : List、 Integer,甚至是 void
查找与匹配
方法 | 描述
| :-: | -:
allMatch(Predicate p) | 检查是否匹配所有元素
anyMatch(Predicate p) | 检查是否至少匹配一个元素
noneMatch(Predicate p) | 检查是否没有匹配所有元素
findFirst() | 返回第一个元素
findAny() | 返回当前流中的任意元素
count() | 返回流中元素总数
max(Comparator c) | 返回流中最大值
min(Comparator c) | 返回流中最小值
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, Stream API 使用内部迭代)
forEachOrdered(Consumer c) | 基本同forEach,后面会有示例比较
toArray() toArray(IntFunction g) | 这个使用起来和List的toArray差不多
其余方法使用起来都比较简单,下面通过一个案例对比foreach等:
public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); //因为并行 所以输出完全无序 list.stream().parallel().forEach(x -> System.out.print(x)); //65387421 System.out.println(); //使用了forEachOrdered 所以是顺序输出的 即使你是并行流也是顺序的 list.stream().parallel().forEachOrdered(x -> System.out.print(x)); //12345678 System.out.println(); //使用了toList,然后其实也是顺序输出了 内部原理同forEachOrdered(可当面试题哟) List<Integer> collect = list.stream().parallel().collect(Collectors.toList()); System.out.println(collect); //[1, 2, 3, 4, 5, 6, 7, 8] }
除了使用forEachOrdered保证顺序外,Collectors.toList()也可以保证顺序,二都最终都是通过ForEachOrderedTask类来实现的,具体可以参看ForEachOp.ForEachOrderedTask类中的代码。
注 : 流进行了终止操作后,不能再次使用
归约:
reduce是很重要的一种变成思想。这里重点介绍一下。reduce的作用是把stream中的元素给组合起来。至于怎么组合起来:
它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终产出,这就是reduce的算法最通俗的描述;
所以运用reduce我们可以做sum,min,max,average,所以这些我们称之为针对具体应用场景的reduce,这些常用的reduce,stream api已经为我们封装了对应的方法。
//求和 sum List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); // 没有起始值时返回为Optional类型 Optional<Integer> sumOptional = integers.stream().reduce(Integer::sum); System.out.println(sumOptional.get()); //15 // 可以给一个起始种子值 Integer sumReduce = integers.stream().reduce(0, Integer::sum); System.out.println(sumReduce); //15 //直接用sum方法 Integer sum = integers.stream().mapToInt(i -> i).sum(); System.out.println(sum); //15
重点说说三个参数的Reduce
三个参数时是最难以理解的。 分析下它的三个参数:
identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等;后面会说到这些用法。
accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的
combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作
第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。
因此针对这个方法的分析需要分并行与非并行两个场景。
就是因为U和T不一样,所以给了我们更多的发挥。比如设U的类型是ArrayList,那么可以将Stream中所有元素添加到ArrayList中再返回了,如下示例:
public static void main(String[] args) { ArrayList<String> result = Stream.of("aa", "ab", "c", "ad").reduce(new ArrayList<>(), (u, s) -> { u.add(s); return u; }, (strings, strings2) -> strings); System.out.println(result); //[aa, ab, c, ad] }
注意由于是非并行的,第三个参数实际上没有什么意义,可以指定r1或者r2为其返回值,甚至可以指定null为返回值。下面看看并行的情况:
当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。注意由于采用了并行计算,前两个参数与非并行时也有了差异! 看个例子:
public static void main(String[] args) { Integer reduce = Stream.of(1, 2, 3).parallel().reduce( 4, (integer, integer2) -> integer + integer2, (integer, integer2) -> integer + integer2); System.out.println(reduce); //18 } 输出:18
omg,结果竟然是18。显然串行的话结果是10;这个不太好理解,但是我下面写一个等价的方式,可以帮助很好的理解这个结果:
public static void main(String[] args) { Optional<Integer> reduce = Stream.of(1, 2, 3).map(n -> n + 4).reduce((s1, s2) -> s1 + s2); System.out.println(reduce.get()); //18 }
这种方式有助于理解并行三个参数时的场景,实际上就是第一步使用accumulator进行转换(它的两个输入参数一个是identity, 一个是序列中的每一个元素),由N个元素得到N个结果;第二步是使用combiner对第一步的N个结果做汇总。
好了,三个参数的reduce先介绍到这。下面继续看看reduce能为我们做什么?
public static void main(String[] args) { //构造字符串流 List<String> strs = Arrays.asList("H", "E", "L", "L", "O"); // reduce String concatReduce = strs.stream().reduce("", String::concat); System.out.println(concatReduce); //HELLO Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); Integer minReduce = integerStream.reduce(Integer.MAX_VALUE, Integer::min); System.out.println(minReduce); //1 }
收集(collect),很多时候和reduce很像,但collect更加强大
方法 | 描述
| :-: | -:
collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
Collectors里常用搜集器介绍: