List<Person> adults = people.stream() .filter(p -> p.Age > 18) .collect(Collectors.toList()) ;
F3源代码演示:
首先,Collection中定义了stream()默认方法,由此,所有集合(Map先转成Set)都可以转换成Stream;
然后,Stream中定义了大量的方法,这些方法接收匿名方法/Lambda表达式做参数;
最后,调用这些方法,就会(不严谨,但先这样粗糙的理解):
一般有三种方式:
Stream<Person> result = people.stream(); Stream<Map.Entry<String, Person>> entries = new HashMap<String, Person>().entrySet().stream();
//三个元素形成的Stream Stream<Integer> ages = Stream.of(28, 16, 23); //一个元素的Stream Stream<Integer> defaultAge = Stream.of(25); //一个空的Stream Stream<Integer> empty = Stream.empty(); //Stream还可以进行连接 Stream<Integer> concated = Stream.concat(ages, empty);
另外,Stream的很多方法返回的仍然是Stream对象,这样,就可以形成“方法连缀”,极大的提高了代码的流畅性。
3个学生2个老师:
Teacher fg = new Teacher(); fg.Name = "飞哥"; Teacher xy = new Teacher(); xy.Name = "小鱼"; Student atai = new Student(); atai.Age = 16; atai.Score = 85.5; atai.Name = "阿泰"; atai.Teachers = new ArrayList<>(); atai.Teachers.add(fg); atai.Teachers.add(xy); Student bo = new Student(); bo.Age = 17; bo.Name = "波仔"; bo.Score = 58; bo.Teachers = new ArrayList<>(); bo.Teachers.add(fg); Student lang = new Student(); lang.Age = 17; lang.Name = "浪仔"; lang.Score = 69; lang.Teachers = new ArrayList<>(); lang.Teachers.add(xy); List<Student> students = new ArrayList<>(); students.add(atai); students.add(bo); students.add(lang); Student dsx = new Student(); dsx.Age = 23; dsx.Score = 88; dsx.Name = "大师兄"; Student lw = new Student(); lw.Age = 23; lw.Score = 89; lw.Name = "炜哥";
注意:这里为了演示清晰,我们直接使用了字段,同学们完成作业时还是应该使用getter和setter属性。
所有能返回bool值的表达式都可以:
同时还可以使用多个filter连缀:
students.stream() .filter(s -> s.Score > 80 && s.Age < 18); students.stream() .filter(s -> s.Score > 80) .filter(s -> s.Age < 18);
@猜一猜@:两个filter连缀,和一个filter使用逻辑组合运算,运行时有区别么?
students.stream() //遍历每一个元素,输出到控制台 .forEach(s -> System.out.println(s.Name));注意你有可能会看到这种写法(复习:方法引用):
students.stream() //不要不知道这是啥意思,^_^ .forEach(System.out::println);
从原有结果集中取出若干属性重新组合成新的集合。比如拿到学生的姓名:
students.stream().map(s->s.Name).forEach(System.out::println);比如我们要得到:每个老师上了多少门课,怎么办?
students.stream() .map(s-> new Map.Entry<String, Integer>() { //匿名对象 @Override public String getKey() { return s.Name; //学生姓名为:键 } @Override public Integer getValue() { return s.Teachers.size(); //老师数量为:值 } @Override public Integer setValue(Integer value) { return null; } } ) //这时候s不再指代Student,而是Map.Entry实例 .forEach(s-> System.out.println(s.getKey() + ":" + s.getValue()));
可能你会觉得使用匿名对象有点别扭,那就可以在collect()的时候进行map:
students.stream() .collect(Collectors.toMap(s->s.Name, s->s.Teachers.size())) //这里的forEach()方法不是Stream的,而是Map的 .forEach((k,v)->System.out.println(k + ":" + v));
检查stream中是否有元素满足匹配条件:
//所有的学生年龄小于18:false System.out.println(students.stream().allMatch(s-> s.Age < 18)); //有学生年龄小于18:true System.out.println(students.stream().anyMatch(s-> s.Age < 18)); //没有学生年龄小于18:false System.out.println(students.stream().noneMatch(s-> s.Age < 18));
students.stream().distinct().forEach(s->System.out.println(s.Name));
注意是否重复的依据是元素继承/重写的Object.equals()方法。所以如果元素是:
对结果集还可以按指定规则进行排序
students.stream() .sorted((a,b)->b.Age-a.Age) //根据年龄进行比较 .forEach(s->System.out.println(s.Name + ":" + s.Age));
students.stream() .sorted() //没有参数 .forEach(s->System.out.println(s.Name + ":" + s.Age));
public class Student implements Comparable<Student> { @Override public int compareTo(Student o) { // TODO Auto-generated method stub return this.Age-o.Age; }
排序之后还可以取第一个:findFirst()
@想一想@:分页的本质是什么?比如说:每页10个元素,取第3页的数据。
是不是就是:跳过(skip)20个,再依次取10(limit)个?Stream中庸skip()和limit()可以非常方便的实现:
students.stream() .sorted() .skip(20).limit(10);注意这个sorted(),如果没有它的话,出于性能考虑,skip()是以一种乱序的方法(in the encounter order)跳过若干元素。所以我们通常都是要首先进行排序的。
再归纳一下,每页size个元素,取第index页呢?
.skip((index-1)*size).limit(size)记住这个公式,以后会经常用到的哟……^_^
取元素个数:count()
System.out.println("count:" + students.stream().count());
取最大最小值:和sort()一样,需要提供比较方式(Comparator):
Optional<Student> max = students.stream().max((a,b)-> a.Age-b.Age)
Optional<Student> min = students.stream().min((a,b)-> a.Age-b.Age)
注意返回的是Optional泛型类(复习),要拿到值还需要调用orElse().get()方法……
取和:需要首先调用collect()方法,然后传入
int sum = students.stream().collect(Collectors.summingInt(s->s.Age));
double sum = students.stream() .collect(Collectors.summarizingInt(s->s.Age)) .getAverage(); // .getSum();summarize不是求和,而是统计,所以得到的是统计信息(<Type>SummaryStatistics)还不是具体的“和”或“平均值”,还需要进一步调用getSum()/getAverage()等方法。
求和还有一种方式:
System.out.println( Stream.of(1, 2, 9, 3, 10) // 生成整数流 .reduce((a, b) -> { // a和b代表相邻两个运算元素 System.out.println(a + "+" + b + "=" + (a + b)); //每一次都是把两个元素相加,以其结果作为下一次运算的第一个元素 return a + b; }).orElse(0)); //返回的是Optional
很难描述啊,o(╥﹏╥)o,输出的结果是:
1+2=3 3+9=12 12+3=15 15+10=25 25元素间如何运算,有reduce()的参数指定,可以加也可以减,其他任何运算也都行,只是要包装运算的结果和原来的元素是同一类型即可。
对于示例中的Student集合流,我们可以:
int sum = students.stream() .mapToInt(s->s.Age).reduce((a,b)->a+b) .getAsInt();
int sum = students.stream() .reduce((a, b) -> new Student(a.Age + b.Age)) .get().Age; //get()之后返回的是Student
将具有相同属性(比如年龄/授课老师/城市)的元素归为一组。
分组过后的结果默认是Map键值对集合:
分组需要在collect中完成,传入Collectors.groupingBy
//默认分组结果的值类型为List Map<Integer, List<Student>> aged = students.stream().collect( //按单个成员(Age)分组 Collectors.groupingBy(s->s.Age));我们可以用forEach把结果输出出来:
aged.forEach((k,v)->{ //K和V代表什么? System.out.println(k + ":"); for (Student student : v) { System.out.println(student.Name); } System.out.println("--------"); });
还有一种常见的需求:分组过后再进行
可以使用groupingBy的重载,传入一个通过Collectors.summingDouble()获取的Collector
students.stream().collect( Collectors.groupingBy(s->s.Age, //对每个小组再进行求职 Collectors.summingDouble(s->s.Score) )) .forEach((k,v)->System.out.println(k + ":" + v));
如果需要统计最大最小值等,就需要通过Collectors.summarizingDouble()获取的Collector了:
students.stream().collect( Collectors.groupingBy(s->s.Age, //对每个小组再进行统计(statistic) Collectors.summarizingDouble(s->s.Score))) //这时候v代表DoubleSummaryStatistics .forEach((k,v)->System.out.println(k + ":" + v.getMax()));
比如我们要把年龄和成绩都相同的分成一组。
可以想象,问题就在这里:
Collectors.groupingBy(s->???)); //写点啥呢?
Java里面没有C#中的匿名类对象,只有自己声明一个类:
class age2score { private int age; private double score; public age2score(int age, double score) { this.age = age; this.score = score; }
Collectors.groupingBy(s-> new age2score(s.Age, s.Score)));但是,运行结果让你失望了……
因为进行分组,就要对分组的classifier进行比较,而对象默认的比较方式是通过堆地址进行的,所以上面每次迭代都新new出来的对象一定不会相等。咋办呢?
得为age2score重写equals()和hashCode()方法(复习:Object)。
怎么写?八仙过海各显神通了哟,^_^
@Override public boolean equals(Object obj) { age2score as = (age2score)obj; return as.age == this.age && as.score == this.score; }
@Override public int hashCode() { return (String.valueOf(age) + "-" + String.valueOf(score)).hashCode(); }
按Java的官方文档,Stream的操作被分为两种/两个阶段:
但实际上更流行、更简单的说法,就是延迟执行(deferred execution),或者懒惰执行(lazy execution),或者惰性加载(lazy load)
我们可以把Stream理解成一个管道(pipeline),本身并不存储查询结果:
students.stream().filter(s->s.Age<18).sorted().findFirst();这在我们过滤/排序等条件动态拼接的时候尤其有用
Stream<Student> stream = students.stream(); if (onlyTeenage) { //用户指示是否只检索未成年人 stream = stream.filter(s->s.Age<18); }//else nothing stream.sorted().findFirst();
多快好省!前端后端,线上线下,名师精讲
更多了解 加: