Java8 Stream

一. 什么是 Stream

Stream 中文称为 “流”,是Java8新特性主要是用来处理集合数据的,能够将其看做一个高级迭代器,经过将集合转换为这么一种叫作 “流” 的元素序列,经过声明性方式,可以对集合中的每一个元素进行一系列并行或串行的流水线操做html

换句话说,你只须要告诉流你的要求,流便会在背后自行根据要求对元素进行处理,而你只须要 “不劳而获”。java

二. 流操做

 
 

整个流操做就是一条流水线,将元素放在流水线上一个个地进行处理。程序员

其中数据源即是原始集合,而后将如 List<T> 的集合转换为 Stream<T> 类型的流,并对流进行一系列的中间操做,好比过滤保留部分元素、对元素进行排序、类型转换等;最后再进行一个终端操做,能够把 Stream 转换回集合类型,也能够直接对其中的各个元素进行处理,好比打印、好比计算总数、计算最大值等等数据库

很重要的一点是,不少流操做自己就会返回一个流,因此多个操做能够直接链接起来,咱们来看看一条 Stream 操做的代码: 编程

 

若是是之前,进行这么一系列操做,你须要作个迭代器或者 foreach 循环,而后遍历,一步步地亲力亲为地去完成这些操做;可是若是使用流,你即可以直接声明式地下指令,流会帮你完成这些操做。数组

有没有想到什么相似的?是的,就像 SQL 语句同样, select username from user where id = 1,你只要说明:“我须要 id 是 1 (id = 1)的用户(user)的用户名(username )”,那么就能够获得本身想要的数据,而不须要本身亲自去数据库里面循环遍历查找。数据结构

三. 流与集合

何时计算

Stream 和集合的其中一个差别在于何时进行计算。app

一个集合,它会包含当前数据结构中全部的值,你能够随时增删,可是集合里面的元素毫无疑问地都是已经计算好了的。dom

流则是按需计算,按照使用者的须要计算数据,你能够想象咱们经过搜索引擎进行搜索,搜索出来的条目并非所有呈现出来的,并且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。ide

再比方在线观看电影和你硬盘里面的电影,也是差很少的道理。

外部迭代和内部迭代

Stream 和集合的另外一个差别在于迭代。

咱们能够把集合比做一个工厂的仓库,一开始工厂比较落后,要对货物做什么修改,只能工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物放到一个新的仓库里面。在这个时期,咱们须要亲自去作迭代,一个个地找到须要的货物,并进行处理,这叫作外部迭代

后来工厂发展了起来,配备了流水线做业,只要根据需求设计出相应的流水线,而后工人只要把货物放到流水线上,就能够等着接收成果了,并且流水线还能够根据要求直接把货物输送到相应的仓库。这就叫作内部迭代,流水线已经帮你把迭代给完成了,你只须要说要干什么就能够了(即设计出合理的流水线)。

Java 8 引入 Stream 很大程度是由于,流的内部迭代能够自动选择一种合适你硬件的数据表示和并行实现;而以往程序员本身进行 foreach 之类的时候,则须要本身去管理并行等问题。

一次性的流

流和迭代器相似,只能迭代一次(流只能使用一次,使用结束以后,这个流也就废掉了)。

Stream<String> stream = list.stream().map(Person::getName).sorted().limit(10);         
List<String> newList = stream.collect(Collectors.toList());
List<String> newList2 = stream.collect(Collectors.toList());

上面代码中第三行会报错,由于第二行已经使用过这个流,这个流已经被消费掉了

四. 方法介绍

一.  通常方法

值得注意的是:学习 Stream 以前必须先学习 lambda 的相关知识。

首先咱们先定义一个类 Person 并建立一个 Person 类的泛型 List

List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));

Person 类包含年龄和姓名两个成员变量

private String name;
private int age;

1. stream() / parallelStream()

最经常使用到的方法,将集合转换为流

List list = new ArrayList();
// return Stream<E>
list.stream();

parallelStream() 是并行流方法,可以让数据集执行并行操做,后面会更详细地讲解

2. filter(T -> boolean)

保留 boolean 为 true 的元素

//保留年龄为 20 的 person 元素
list = list.stream()
            .filter(person -> person.getAge() == 20)
            .collect(Collectors.toList());
打印输出 [Person{name='jack', age=20}]

  //把list里知足某个条件的成员输出到新的list
  //输出用户名等于张三的
  List<User2> lstUsers = lstUsersAll.stream().filter(t -> "张三".equals(t.getName())).collect(Collectors.toList());

collect(Collectors.toList()) 能够把流转换为 List 类型 

long count = list.stream().filter(t -> "张三".equals(t.getName())).count();
if ((int) count == 0) {                                         
}

3. distinct()

去除重复元素,这个方法是经过类的 equals 方法来判断两个元素是否相等的

如例子中的 Person 类,须要先定义好 equals 方法,否则相似[Person{name='jack', age=20}, Person{name='jack', age=20}] 这样的状况是不会处理的

 List uniqueList = list.stream().distinct().collect(Collectors.toList());

对象去重

  List<User> userList = new ArrayList<User>();
  userList.add(new User("小黄",10));
  userList.add(new User("小红",23));
  userList.add(new User("小黄",78));
  userList.add(new User("小黄",10));

  //根据name属性去重
  List<User> unique1 = userList.stream().collect(                    
Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)); System.out.println(unique1.toString()); //根据name,age属性去重 List<User> unique2 = userList.stream().collect( Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o ->
{
return o.getName() + "," + o.getAge(); }))), ArrayList::new)); System.out.println(unique2.toString());

 输出以下:

4. sorted() / sorted((T, T) -> int)

若是流中的元素的类实现了 Comparable 接口,即有本身的排序规则,那么能够直接调用 sorted() 方法对元素进行排序,如 Stream

反之, 须要调用 sorted((T, T) -> int) 实现 Comparator 接口

根据年龄大小来比较:
list = list.stream()
           .sorted((p1, p2) -> p1.getAge() - p2.getAge())
           .collect(Collectors.toList());

固然这个能够简化为

list = list.stream()
           .sorted(Comparator.comparingInt(Person::getAge))
           .collect(Collectors.toList());

//升序(默认)
list = list.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
//降序
list = list.stream()
.sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());

注意:日期类型的排序需当心,由于若是日期为空,使用上面通常的方式排序会报错的。因此最好使用下面方式二排序 

 //方式一:排序(按【建立时间】升序排列)
 //若是建立时间有为空的就会报错
 list = list.stream().sorted(Comparator.comparing(Person::getCreatetime))
.collect(Collectors.toList());
//方式二:排序(按【建立时间】升序排列) //nullsLast方法表示若是建立时间为空就排列到最后面
list = list.stream().sorted(Comparator.comparing(t -> t.getCreatetime(), Comparator.nullsLast(Date::compareTo)))
.collect(Collectors.toList());
//nullsFirst方法表示若是建立时间为空就排列到最前面
list = list.stream().sorted(Comparator.comparing(t -> t.getCreatetime(), Comparator.nullsFirst(Date::compareTo)))
.collect(Collectors.toList());

 涉及到多字段组合排序时使用 thenComparing 方法

 //今日待跟踪(多字段组合排序)
 list = list.stream().sorted(Comparator.comparing(Person::getAge).thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo))))
.collect(Collectors.toList());

实现中文汉字按拼音排序

要实现汉字按首字母排序,主要是设置语言环境,以下语句设置语言环境:

这里用到了Collator类,此类实现了Comparator接口,用他的getInstance就能够用指定的语言环境来构造一个Collator对象:

而后用以下语句建立Comparator:

Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);

//获取中文环境
Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);  
String[] newArray={"中山","汕头","广州","安庆","阳江","南京","武汉","北京","安阳","北方"};  
List<String> list = Arrays.asList(newArray);
Collections.sort(list, com); 
for(String i:list){  
   System.out.print(i+"  ");  
}

输出结果是:
安庆  安阳  北方  北京  广州  南京  汕头  武汉  阳江  中山  

实现List集合中对象元素按其属性的中文拼音排序,这里重写compare方法

 //获取商户list集合
 List<Merch> merchList = xxxxService.queryMerchList();
  //Collections工具类的sort()方法对list集合元素排序 
  Collections.sort(merchList, new Comparator<Merch>() {
       @Override public int compare(Merch info1, Merch info2) {
        //获取中文环境
           Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);
           //按照汉语拼音排序
           return com.compare(info1.getMerchName(), info2.getMerchName());
       }
}.thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo))));

或者用下面这两种写法也能够实现

 //Collator类是用来执行区分语言环境的String比较的,这里是选择中文环境
 Comparator<Object> china_compare = Collator.getInstance(java.util.Locale.CHINA);

 //方式一
 Collections.sort(list, Comparator.comparing(Person::getNamefirst, china_compare)
                    .thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo))));

 //方式二
 list = list.stream().sorted(Comparator.comparing(Person::getNamefirst, china_compare)
                    .thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo)))).collect(Collectors.toList());

5. limit(long n)

返回前 n 个元素

list = list.stream()
            .limit(2)
            .collect(Collectors.toList());

打印输出 [Person{name='jack', age=20}, Person{name='mike', age=25}]

6. skip(long n)

去除前 n 个元素

list = list.stream()
            .skip(2)
            .collect(Collectors.toList());

打印输出 [Person{name='tom', age=30}]

说明:

  • 用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素
  • limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素
list = list.stream()
            .limit(2)
            .skip(1)
            .collect(Collectors.toList());

打印输出 [Person{name='mike', age=25}]

7. map(T -> R)

将流中的每个元素 T 映射为 R(相似类型转换)

  • map用来归类,结果通常是一组数据,好比能够将list中的学生姓名映射到一个新的stream中。
  //使用map方法获取list数据中的name
  List<String> newlist = list.stream().map(Student::getName).collect(Collectors.toList());
//使用map方法获取list数据中的name的长度 List<Integer> length = list.stream().map(Student::getName).map(String::length).collect(Collectors.toList()); //将每人的分数-10 List<Integer> score = list.stream().map(Student::getScore).map(i -> i - 10).collect(Collectors.toList());

newlist 里面的元素为 list 中每个 Student 对象的 name 变量

8. flatMap(T -> Stream)

将流中的每个元素 T 映射为一个流,再把每个流链接成为一个流

List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");

list = list.stream().map(s -> s.split(" ")).flatMap(Arrays::stream).collect(Collectors.toList());

上面例子中,咱们的目的是把 List 中每一个字符串元素以" "分割开,变成一个新的 List。

首先 map 方法分割每一个字符串元素,但此时流的类型为 Stream<String[ ]>,由于 split 方法返回的是 String[ ] 类型;因此咱们须要使用 flatMap 方法,先使用Arrays::stream将每一个 String[ ] 元素变成一个 Stream 流,而后 flatMap 会将每个流链接成为一个流,最终返回咱们须要的 Stream

9. anyMatch(T -> boolean)

流中是否有一个元素匹配给定的 T -> boolean 条件

是否存在一个 person 对象的 age 等于 20
boolean b = list.stream().anyMatch(person -> person.getAge() == 20);

10. allMatch(T -> boolean)

流中是否全部元素都匹配给定的 T -> boolean 条件

11. noneMatch(T -> boolean)

流中是否没有元素匹配给定的 T -> boolean 条件

12. findAny() 和 findFirst()

  • findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
  • findFirst():找到第一个元素

值得注意的是,这两个方法返回的是一个 Optional 对象,它是一个容器类,能表明一个值存在或不存在,这个后面会讲到

13. reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)

用于组合流中的元素,如求和,求积,求最大值等

  • reduce用来计算值,结果是一个值,好比计算最高分。
计算年龄总和:
int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
与之相同: int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);

其中,reduce 第一个参数 0 表明起始值为 0,lambda (a, b) -> a + b 即将两值相加产生一个新值

一样地:

计算年龄总乘积:
int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);

固然也能够 

  Integer total2 = listbo.stream().map(Student::getScore).reduce(Integer::sum).get();
  Optional<Integer> sum = list.stream().map(Student::getScore).reduce(Integer::sum);
计算学生总分,返回Optional类型的数据,该类型是java8中新增的,主要用来避免空指针异常 Optional
<Integer> totalScore2 = list.stream().map(Student::getScore).reduce((a,b) -> a + b); 计算最高分和最低分 Optional<Integer> max = list.stream().map(Student::getScore).reduce(Integer::max); Optional<Integer> min = list.stream().map(Student::getScore).reduce(Integer::min);

 即不接受任何起始值,但由于没有初始值,须要考虑结果可能不存在的状况,所以返回的是 Optional 类型

14. count()

返回流中元素个数,结果为 long 类型

15. collect()

收集方法,咱们很经常使用的是 collect(Collectors.toList()),固然还有 collect(Collectors.toSet()) 等,参数是一个收集器接口,这个后面会另外讲

16. forEach()

返回结果为 void,很明显咱们能够经过它来干什么了,比方说:

### 16. unordered()
还有这个比较不起眼的方法,返回一个等效的无序流,固然若是流自己就是无序的话,那可能就会直接返回其自己

打印各个元素:
list.stream().forEach(System.out::println);

再好比说 MyBatis 里面访问数据库的 mapper 方法:

向数据库插入新元素:
list.stream().forEach(PersonMapper::insertPerson);

二. 数值流

前面介绍的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 计算元素总和的方法其中暗含了装箱成本,

map(Person::getAge) 方法事后流变成了 Stream 类型,而每一个 Integer 都要拆箱成一个原始类型再进行 sum 方法求和,这样大大影响了效率。

针对这个问题 Java 8 有良心地引入了数值流 IntStream, DoubleStream, LongStream,这种流中的元素都是原始数据类型,分别是 int,double,long

1. 流与数值流的转换

流转换为数值流

  • mapToInt(T -> int) : return IntStream
  • mapToDouble(T -> double) : return DoubleStream
  • mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);

固然若是是下面这样便会出错

LongStream longStream = list.stream().mapToInt(Person::getAge);

由于 getAge 方法返回的是 int 类型(返回的若是是 Integer,同样能够转换为 IntStream)

数值流转换为流

很简单,就一个 boxed

Stream<Integer> stream = intStream.boxed();

2. 数值流方法

下面这些方法做用不用多说,看名字就知道:

  • sum()
  • max()
  • min()
  • average() 等...

3. 数值范围

IntStream 与 LongStream 拥有 range 和 rangeClosed 方法用于数值范围处理

  • IntStream : rangeClosed(int, int) / range(int, int)
  • LongStream : rangeClosed(long, long) / range(long, long)

这两个方法的区别在于一个是闭区间,一个是半开半闭区间:

  • rangeClosed(1, 100) :[1, 100]
  • range(1, 100) :[1, 100)

咱们能够利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的数值流

求 1 到 10 的数值总和:
IntStream intStream = IntStream.rangeClosed(1, 10);
int sum = intStream.sum();

三. Optional 类

NullPointerException 能够说是每个 Java 程序员都很是讨厌看到的一个词,针对这个问题, Java 8 引入了一个新的容器类 Optional,能够表明一个值存在或不存在,这样就不用返回容易出问题的 null。

Optional 类比较经常使用的几个方法有:

  • isPresent() :值存在时返回 true,反之 flase
  • get() :返回当前值,若值不存在会抛出异常
  • orElse(T) :值存在时返回该值,不然返回 T 的值

Optional 类还有三个特化版本 OptionalInt,OptionalLong,OptionalDouble,刚刚讲到的数值流中的 max 方法返回的类型即是这个

Optional 类其中其实还有不少学问,讲解它说不定也要开一篇文章,这里先讲那么多,先知道基本怎么用就能够。

http://www.javashuo.com/article/p-mabzvyvu-u.html

public class TestOptional {
    public static void main(String[] args) {
        List<Student> studentList = InitData.getStudent();
        //计算分数在60分一下的分数总和
        Optional<Integer> score = studentList.stream()
                .map(Student :: getScore)
                .filter(s -> s<60)
                .reduce((a,b) -> a+b); //没有60分如下的,之前不加判断就会出现空指针异常
        System.out.println(score.orElse(0));   //为null则返回0
 
        Map<Integer,String> map = new HashMap<>();
        map.put(20180001,"章子");
        map.put(20180002,"小米");
        map.put(20180003,"大黄");
        map.put(20180004,"靓妹");
 
        String name = Optional.ofNullable(map.get(20180005)).orElse("");
        System.out.println(name);  //
    }
}

四. 构建流

以前咱们获得一个流是经过一个原始数据源转换而来,其实咱们还能够直接构建获得流。

1. 值建立流

  • Stream.of(T...) : Stream.of("aa", "bb") 生成流
生成一个字符串流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
  • Stream.empty() : 生成空流

2. 数组建立流

根据参数的数组类型建立对应的流:

  • Arrays.stream(T[ ])
  • Arrays.stream(int[ ])
  • Arrays.stream(double[ ])
  • Arrays.stream(long[ ])

值得注意的是,还能够规定只取数组的某部分,用到的是Arrays.stream(T[], int, int)

只取索引第 1 到第 2 位的:
int[] a = {1, 2, 3, 4};
Arrays.stream(a, 1, 3).forEach(System.out :: println);

打印 23

3. 文件生成流

Stream<String> stream = Files.lines(Paths.get("data.txt"));

每一个元素是给定文件的其中一行

4. 函数生成流

两个方法:

  • iterate : 依次对每一个新生成的值应用函数
  • generate :接受一个函数,生成一个新的值
Stream.iterate(0, n -> n + 2)
生成流,首元素为 0,以后依次加 2

Stream.generate(Math :: random)
生成流,为 01 的随机双精度数

Stream.generate(() -> 1)
生成流,元素全为 1

五. collect 收集数据

coollect 方法做为终端操做,它离不开Collectors工具类。其实上面的代码已经涉及到了该方法,好比collect(Collectors.toList())转换成List集合,能对数据进行一些收集归总操做。

1. 收集

最经常使用的方法,把流中全部元素收集到一个 List, Set 或 Collection 中

  • Collectors.toList    转换成List集合
  • Collectors.toSet    转换成set集合
  • Collectors.toCollection(TreeSet::new)    转换成特定的set集合  
  • Collectors.toMap(x -> x, x -> x + 1)  转换成map
List newlist = list.stream.collect(Collectors.toList());
TreeSet
<Integer> collect2 = Stream.of(1, 3, 4).collect(Collectors.toCollection(TreeSet::new));
Map
<Integer, Integer> collect1 = Stream.of(1, 3, 4).collect(Collectors.toMap(x -> x, x -> x + 1));

2. 汇总

(1)counting

用于计算总和:

long l = list.stream().collect(Collectors.counting());

没错,你应该想到了,下面这样也能够: 

long l = list.stream().count();

 推荐第二种

(2)summingInt ,summingLong ,summingDouble

summing,没错,也是计算总和,不过这里须要一个函数参数

计算 Person 年龄总和:

int sum = list.stream().collect(Collectors.summingInt(Person::getAge));

固然,这个能够也简化为: 

int sum = list.stream().mapToInt(Person::getAge).sum();

 除了上面两种,其实还能够: 

int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();

 推荐第二种

因而可知,函数式编程一般提供了多种方式来完成同一种操做

(3)averagingInt,averagingLong,averagingDouble

看名字就知道,求平均数

Double average = list.stream().collect(Collectors.averagingInt(Person::getAge));

固然也能够这样写 

OptionalDouble average = list.stream().mapToInt(Person::getAge).average();

 不过要注意的是,这两种返回的值是不一样类型的

(4)summarizingInt,summarizingLong,summarizingDouble

这三个方法比较特殊,好比 summarizingInt 会返回 IntSummaryStatistics 类型

IntSummaryStatistics l = list.stream().collect(Collectors.summarizingInt(Person::getAge));

IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,能够经过下面这些方法得到相应的数据

3. 取最值

maxBy,minBy 两个方法,须要一个 Comparator 接口做为参数

Optional<Person> optional = list.stream().collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));

咱们也能够直接使用 max 方法得到一样的结果 

Optional<Person> optional = list.stream().max(Comparator.comparing(Person::getAge));

 4. joining 链接字符串

也是一个比较经常使用的方法,对流里面的字符串元素进行链接,其底层实现用的是专门用于字符串链接的 StringBuilder

String s = list.stream().map(Person::getName).collect(Collectors.joining()); 

结果:jackmiketom String s = list.stream().map(Person::getName).collect(Collectors.joining(",")); 
结果:jack,mike,tom

joining 还有一个比较特别的重载方法: 

String s = list.stream().map(Person::getName).collect(Collectors.joining(" and ", "Today ", " play games.")); 

结果:Today jack and mike and tom play games.

 即 Today 放开头,play games. 放结尾,and 在中间链接各个字符串

5. groupingBy 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型

Map<Integer, List<Person>> map = list.stream().collect(Collectors.groupingBy(Person::getAge));

例子中咱们按照年龄 age 分组,每个 Person 对象中年龄相同的归为一组

另外能够看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List 类型)

多级分组

groupingBy 能够接受一个第二参数实现多级分组:

Map<Integer, Map<T, List<Person>>> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.groupingBy(Person::getName)));

其中返回的 Map 键为 Integer 类型,值为 Map<T, List> 类型,即参数中 groupingBy(...) 返回的类型

在分组后,获取每一个分组的第一个元素 

Map<Integer, Person> resultList = list.stream().collect(
Collectors.groupingBy(Person::getAge,
Collectors.collectingAndThen(Collectors.toList(), value -> value.get(0))));

 须要借助Collectors.collectingAndThen方法,对组内的元素进行处理,这里是获取第一个元素

在分组后,获取每一个分组最大元素

Map<String, HitRuleConfig> configMap = list.stream().collect(
               Collectors.groupingBy(Person::getAge,  //先根据age分组
               Collectors.collectingAndThen(Collectors.reducing((c1,  c2) -> c1.getVersionSort() > c2.getVersionSort() ? c1 : c2), Optional::get)));

先按照某一字段分组,再按照另外字段排序取最大的那个

按组收集数据

Map<Integer, Integer> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.summingInt(Person::getAge)));

该例子中,咱们经过年龄进行分组,而后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map<Integer, Integer>

根据这个方法,咱们能够知道,前面咱们写的:

groupingBy(Person::getAge)

其实等同于:

groupingBy(Person::getAge, Collectors.toList())

6. partitioningBy 分区

把数据分红两部分,分区与分组的区别在于,分区是按照 true 和 false 来分的,所以 partitioningBy 接受的参数的 lambda 也是 T -> boolean

根据年龄是否小于等于20来分区
Map<Boolean, List<Person>> map = list.stream()
                                     .collect(Collectors.partitioningBy(p -> p.getAge() <= 20)); 

打印输出 
{
   false=[Person{name='mike', age=25}, Person{name='tom', age=30}], 
   true=[Person{name='jack', age=20}] 
}

一样地 partitioningBy 也能够添加一个收集器做为第二参数,进行相似 groupBy 的多重分区等等操做。

7. reducing

Collectors.reducing(0, x -> x + 1, (x, y) -> x + y)):在求累计值的时候,还能够对参数值进行改变,这里是都+1后再求和。跟reduce方法有点相似,但reduce方法没有第二个参数。 

System.out.println(Stream.of(1, 3, 4).collect(Collectors.reducing(0, x -> x + 1, (x, y) -> x + y)));

8. collectingAndThen

Collectors.collectingAndThen(Collectors.joining(","), x -> x + "d"):先执行collect操做后再执行第二个参数的表达式。这里是先拼接字符串,再在最后+ "d"。

String str= Stream.of("a", "b", "c").collect(Collectors.collectingAndThen(Collectors.joining(","), x -> x + "d"));

9. mapping

Collectors.mapping(...):跟map操做相似,只是参数有点区别。

System.out.println(Stream.of("a", "b", "c").collect(Collectors.mapping(x -> x.toUpperCase(), Collectors.joining(","))));

六. 并行

以前我就讲到了 parallelStream 方法能生成并行流,所以你一般可使用 parallelStream 来代替 stream 方法,可是并行的性能问题很是值得咱们思考

比方说下面这个例子

 int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);

咱们经过这样一行代码来计算 1 到 100 的全部数的和,咱们使用了 parallel 来实现并行。

但其实是,这样的计算,效率是很是低的,比不使用并行还低!一方面是由于装箱问题,这个前面也提到过,就再也不赘述,还有一方面就是 iterate 方法很难把这些数分红多个独立块来并行执行,所以无形之中下降了效率。

流的可分解性

这就说到流的可分解性问题了,使用并行的时候,咱们要注意流背后的数据结构是否易于分解。好比众所周知的 ArrayList 和 LinkedList,明显前者在分解方面占优。

咱们来看看一些数据源的可分解性状况

数据源 可分解性
ArrayList 极佳
LinkedList
IntStream.range 极佳
Stream.iterate
HashSet
TreeSet

顺序性

除了可分解性,和刚刚提到的装箱问题,还有一点值得注意的是一些操做自己在并行流上的性能就比顺序流要差,好比:limit,findFirst,由于这两个方法会考虑元素的顺序性,而并行自己就是违背顺序性的,也是由于如此 findAny 通常比 findFirst 的效率要高。

本身测试用的例子以下:

//返回流中元素个数
long count = listbo.stream().count();
//分页
List<userBO> newlist1 = listbo.stream().skip(2).limit(5).collect(Collectors.toList());
//排序(默认升序)
List<userBO> newlist2 = listbo.stream().sorted(Comparator.comparing(userBO::getId)).collect(Collectors.toList());
//排序(降序)
List<userBO> newlist21 = listbo.stream().sorted(Comparator.comparing(userBO::getId).reversed()).collect(Collectors.toList());
//多字段组合排序(日期为空排序处理)
List<userBO> newlist22 = listbo.stream().sorted(Comparator.comparing(userBO::getAge).thenComparing(Comparator.comparing(userBO::getCreatetime, Comparator.nullsLast(Date::compareTo)))).collect(Collectors.toList());
//取单个字段
List<Integer> newlist4 = listbo.stream().map(userBO::getId).collect(Collectors.toList());
List<String> newlist = listbo.stream().map(userBO::getName).sorted().skip(10).limit(5).collect(Collectors.toList());
//过滤并去重
List<userBO> newlist3 = listbo.stream().filter(a -> a.getName().equals("张三")).distinct().collect(Collectors.toList());
//过滤取第一个
Optional<userBO> firstA = listbo.stream().filter(a -> "张三".equals(a.getName())).findFirst();
if(firstA.isPresent()){
userBO A = firstA.get();
}

//求和
Optional<Integer> total3 = listbo.stream().map(userBO::getId).reduce((a,b) -> a + b);
Integer total1 = listbo.stream().map(userBO::getId).reduce(0,(a,b) -> a + b);
Integer total2 = listbo.stream().map(userBO::getId).reduce(Integer::sum).get();
//分组
Map<String, List<userBO>> mapBO = listbo.stream().collect(Collectors.groupingBy(userBO::getName));
mapBO.forEach((key,Value)->{
    IntStream intStream = Value.stream().mapToInt(userBO::getId);
    int sum = intStream.sum();//求和
});
相关文章
相关标签/搜索