Stream 总览
什么是流
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。Java 的并行 API 演变历程基本如下:
1.0-1.4 中的 java.lang.Thread
5.0 中的 java.util.concurrent
6.0 中的 Phasers
等
7.0 中的 Fork/Join
框架
8.0 中的 Lambda
Stream 的另外一大特点是,数据源本身可以是无限的。
流的构成
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。
图 1. 流管道 (Stream Pipeline) 的构成
Stream有三点非常重要的特性:
- Stream 是不会存储元素的。
- Stream 不会改变原对象,相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。意味着它们会等到需要结果的时候才执行。
这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。(延迟执行特性?)
Stream生成
List<String> list = new ArrayList<> ();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
//通过Arrays.stram()
Stream<String> stream1 = Arrays.stream(new String[10]);
//通过Stream.of()
Stream<Integer> stream2 = Stream.of(1, 2, 3,4,5,6,7,8,9,10);
//通过Stream.iterate()生成无限流
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);
//通过Stream.generate()
Stream<Double> generate = Stream.generate(() -> Math.random());
Stream中间操作
中间操作包括过滤、切片、排序等一些中间环节;
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!
而在终止操作时一次性全部处理,称为“惰性求值”.
筛选与切片
方法名 | 方法作用 |
---|---|
filter(Predicate p) |
过滤,接收 Lambda,从流中排除某些元素 |
distinct() |
去重,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) |
截断流,使其元素不超过给定数量 |
skip(long n) |
跳过元素,返回一个扔掉了前 n 个元素的流;若流中元素不足 n 个,则返回一个空流,与 limit(n) 互补 |
stream2.map(s -> s + 1) //映射
.flatMap(s -> Stream.of(s)) //和map差不多,但返回类型为Stream,类似list.add()和list.addAll()的区别
.filter(s -> s <=5 ) //过滤
.limit(5) //限制
.skip(1) //跳过
.distinct() //去重
.sorted() //自然排序
.sorted(Integer::compareTo).forEach (System.out::println); //自定义排序*/
映射
方法名 | 方法 |
---|---|
map(Function f) |
接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
flatMap(Function f) |
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
mapToDouble(ToDoubleFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream 。 |
mapToInt(ToIntFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream 。 |
mapToLong(ToLongFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream 。 |
import org.westos.demo2.Employee;
import java.util.Arrays;
import java.util.List;
public class MyTest {
public static void main(String[] args) {
// map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
//提前集合中的元素,应用到一个方法上。
List<Employee> list2 = Arrays.asList(
new Employee(102, "李四", 59, 6666.66),
new Employee(101, "张三", 18, 9999.99),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "赵六", 8, 7777.77),
new Employee(104, "赵六", 8, 7777.77),
new Employee(104, "赵六", 8, 7777.77),
new Employee(105, "田七", 38, 5555.55)
);
//以上几步,链式编程
list2.stream().distinct().map(employee -> employee.getName()).forEach(System.out::println);
/*李四
张三
王五
赵六
田七*/
}
}
排序
方法名 | 方法作用 |
---|---|
sorted() |
产生一个新流,其中按自然顺序排序 元素实现Compareble 接口 |
sorted(Comparator comp) |
产生一个新流,其中按比较器顺序排序 传入一个比较 |
终止操作
Stream的终止操作 (不能对同一个流一遍进行运行操作,一遍终止操作)
一个终止操作,执行中间操作链,并产生结果, Stream流属于管道流,只能被消费(使用)一次, 第一个Stream流调用完毕方法,数据就会流转到下一个Stream上, 而这时第一个Stream流已经使用完毕,就会关闭了, 所以第一个Stream流就不能再调用方法了;
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void;
查找与匹配
/*
allMatch(Predicate p)
检查是否匹配所有元素,比如判断所有员工的年龄都是17岁,如果有一个不是,就返回false;
anyMatch(Predicate p)
检查是否至少匹配一个元素,比如判断是否有姓王的员工,如果至少有一个就返回true;
noneMatch(Predicate p)
检查是否没有匹配所有元素 employee.getSalary() < 3000; 每个员工的工资如果都高于3000就返回true 如果有一个低于3000 就返回false
findFirst()
返回第一个元素,比如获取工资最高的人或者获取工资最高的值
findAny()
返回当前流中的任意元素,比如随便获取一个姓王的员工
count()
返回流中元素总数
max(Comparator c)
返回流中最大值,比如:获取最大年龄值
min(Comparator c)
返回流中最小值,比如:获取最小年龄的值
forEach(Consumer c)
内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
*/
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class MyTest {
public static void main(String[] args) {
//终止操作,当我们执行完了中间环节,就想要执行终止操作,来得到中间环节流持有的结果
//终止操作中,最常用的一个操作,就是遍历,forEach();
//有的时候,并不是只想打印看,我们是想要获取中间环节操作完之后的结果。
// allMatch(Predicate p) 检查是否匹配所有元素 比如判断 所有员工的年龄都是17岁 如果有一个不是, 就返回false
List<Employee> list = Arrays.asList(
new Employee(102, "李四", 59, 6666.66),
new Employee(101, "张三", 18, 9999.99),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "赵六", 17, 7777.77),
new Employee(104, "赵六", 8, 7777.77),
new Employee(104, "赵六", 17, 7777.77),
new Employee(105, "田七", 38, 5555.55)
);
Stream<Employee> stream = list.stream();
//终止操作
boolean b = stream.allMatch(new Predicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge() == 17;
}
});
System.out.println(b);//false
System.out.println("====================================");
//中间操作。把所有年龄提取出出来 15 28 36
Stream<Employee> stream2 = list.stream();
Stream<Integer> integerStream = stream2.map(new Function<Employee, Integer>() {
@Override
public Integer apply(Employee employee) {
return employee.getAge();
}
});
//终止操作做判断
boolean b1 = integerStream.allMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer == 17;
}
});
System.out.println(b1);//false
}
}
System.out.println (stream2.allMatch((x) -> x == 10)); // 检查是否匹配所有元素 boolean
System.out.println(stream2.anyMatch(((x) -> x>5))); // 检查是否至少匹配一个元素
System.out.println(stream2.noneMatch((x) -> x>500)); //检查是否没有匹配所有元素
System.out.println (stream2.findFirst()); // 返回第一个元素Optional[1]
System.out.println (stream2.findAny()); // 返回当前流中的任意一个元素;
System.out.println (stream2.count()); // 返回流中元素的总个数);
Optional<Integer> max = stream2.max(Integer::compareTo); // 返回流中最大值
System.out.println("max "+max.get());
Optional<Integer> min = stream2.min(Integer::compareTo);//返回流中最小值
System.out.println("min "+min.get());
reduce (归约)
reduce(T iden, BinaryOperator b)
参1 是起始值, 参2 二元运算 可以将流中元素反复结合起来,得到一个值。返回 T 比如: 求集合中元素的累加总和
reduce(BinaryOperator b)
这个方法没有起始值,可以将流中元素反复结合起来,得到一个值。返回Optional
, 比如你可以算所有员工工资的总和;备注:map
和reduce
的连接通常称为map-reduce
模式,因
//collect(收集):将流转换为其他形式。需要Collectors类的一些方法。
Set<Integer> collect = stream2.collect(Collectors.toSet());
List<Integer> collect2 = stream2.collect(Collectors.toList());
HashSet<Integer> collect1 = stream2.collect(Collectors.toCollection(HashSet::new));
//分组 {group=[1, 2, 3, 4...]}
Map<String, List<Integer>> collect3 = stream2.collect(Collectors.groupingBy((x) -> "group"));//将返回值相同的进行分组
//分区 {false=[1, 2, 3, 4], true=[5, 6, 7, 8, 9, 10, 10]}
Map<Boolean, List<Integer>> collect5 = stream2.collect(Collectors.partitioningBy((x) -> x >= 5));
//汇总 最大值、最小值、平均值、个数
DoubleSummaryStatistics collect6 = stream2.collect(Collectors.summarizingDouble((x) -> x));
System.out.println(collect6.getMax());
System.out.println(collect6.getCount());
Collectors 中的方法
List toList()
把流中元素收集到List 比如把所有员工的名字通过map()方法提取出来之后,在放到List集合中去;
Set toSet()
把流中元素收集到Set 比如把所有员工的名字通过map()方法提取出来之后,在放到Set集合中去
Collection toCollection()
把流中元素收集到创建的集合 比如把所有员工的名字通过map()方法提取出来之后,在放到自己指定的集合中去
Long counting()
计算流中元素的个数
Integer summingInt()
对流中元素的整数属性求和
Double averagingInt()
计算流中元素Integer属性的平均值
IntSummaryStatistics summarizingInt()
收集流中Integer属性的统计值。
String joining()
连接流中每个字符串 比如把所有人的名字提取出来,在通过"-"横杠拼接起来
Optional maxBy()
根据比较器选择最大值 比如求最大工资
Optional minBy()
根据比较器选择最小值 比如求最小工资
归约产生的类型 reducing()从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
转换函数返回的类型 collectingAndThen()
包裹另一个收集器,对其结果转换函数
Map<K, List> groupingBy()
根据某属性值对流分组,属性为K,结果为V 比如按照 状态分组
Map<Boolean, List> partitioningBy()
根据true或false进行分区 比如 工资大于等于6000的一个区,小于6000的一个区
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MyTest {
public static void main(String[] args) {
//收集流中的结果
List<Employee> list2 = Arrays.asList(
new Employee(102, "李四", 59, 6666.66),
new Employee(101, "王三", 18, 9999.99),
new Employee(101, "王三", 18, 9999.99),
new Employee(101, "王三", 18, 9999.99),
new Employee(101, "王三", 18, 9999.99),
new Employee(101, "王三", 18, 9999.99),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "王六", 17, 7777.77),
new Employee(104, "王六", 17, 7777.77),
new Employee(104, "王六", 17, 7777.77),
new Employee(104, "王六", 17, 7777.77),
new Employee(104, "赵六", 8, 7777.77),
new Employee(104, "赵六", 17, 7777.77),
new Employee(105, "田七", 38, 0.55)
);
Stream<String> stringStream = list2.stream().map(e -> e.getName()).distinct();
// stringStream.forEach(System.out::println);
// Collector<Object, ?, List<Object>> objectListCollector = Collectors.toList();
//把结果收集到List集合
List<String> collect = stringStream.collect(Collectors.toList());
System.out.println(collect);
//[李四, 王三, 王五, 王六, 赵六, 田七]
//把结果收集到Set集合
Stream<String> stringStream2 = list2.stream().map(e -> e.getName()).distinct();
Set<String> collect1 = stringStream2.collect(Collectors.toSet());
System.out.println(collect1);
//[王三, 李四, 王五, 赵六, 王六, 田七]
//收集到指定集合里面
Stream<String> stringStream3 = list2.stream().map(e -> e.getName()).distinct();
LinkedHashSet<String> collect2 = stringStream3.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println(collect2);
//[李四, 王三, 王五, 王六, 赵六, 田七]
//求平均工资
Double collect3 = list2.stream().collect(Collectors.averagingDouble(e -> e.getSalary()));
System.out.println(collect3);
//7619.079285714286
System.out.println("======================================");
String collect4 = list2.stream().map(Employee::getName).collect(Collectors.joining("-"));
System.out.println(collect4);
//李四-王三-王三-王三-王三-王三-王五-王六-王六-王六-王六-赵六-赵六-田七
String collect5 = list2.stream().map(Employee::getName).collect(Collectors.joining(",","[","]"));
System.out.println(collect5);
//[李四,王三,王三,王三,王三,王三,王五,王六,王六,王六,王六,赵六,赵六,田七]
}
}
工作常用示例
package JDK8常用特性;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Heian
* @time 19/04/01 14:15
* 用途:
*/
public class Demo1 {
class Student{
private int id;
private String name;
private int age;
public Student(int id,String name,int age){
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1 ();
List<Student> list = new ArrayList<> ();
list.add (demo1.new Student (1,"张三",18));
list.add (demo1.new Student (2,"李四",20));
list.add (demo1.new Student (3,"王二",22));
list.add (demo1.new Student (4,"小明",19));
list.add (demo1.new Student (5,"小红",17));
list.add (null);
//常用操作一:防空处理
//Optional.ofNullable (list).orElse (Collections.emptyList ()).forEach (System.out::println); 会存在null
list.stream ().filter (Objects::nonNull).forEach (student -> System.out.println (student));
//常用操作二:集合变形 业务代码最为常见(假设我要取得某个集合中某个属性作为单独的一个集合)
System.out.println ("=======================集合变形分割线===================");
List<String> nameList = list.stream ().filter (Objects::nonNull).map (Student::getName).collect(Collectors.toList());
nameList.forEach (System.out::println);//集合转化流--->filter过滤--->map对集合做业务操作(返回的仍然是Stream<R>)--->流转化为集合
//limit 就像mysql的limit num 关键字 这里是查出前3条
List<String> nameList2 = list.stream ().filter (Objects::nonNull).map (Student::getName).limit (3).collect(Collectors.toList());
nameList2.forEach (s -> System.out.println ("limit" + s));
//加上skip 类似于mysql分页 比如查看第1 到 10 条记录 select * from emp limit 0,9 这里也类似:这就是第2到第4条 也可以用于分页
List<String> nameList3 = list.stream ().filter (Objects::nonNull).map (Student::getName).skip (1).limit (3).collect(Collectors.toList());
nameList3.forEach (s -> System.out.println ("skip limit" + s));
List<Integer> list2 = Arrays.asList(1,2,3);
//不改变原有元素
list2.forEach(i -> i = i*2);
list2.forEach(integer -> System.out.println ("不改变原有元素"+integer));
//改变对象
list2.stream().map(i -> i * 2).forEach(integer -> System.out.println ("改变原有元素"+integer));
//常用操作三:list转map
System.out.println ("=======================list转map分割线===================");
Map<Integer,Student> map = list.stream ().filter (Objects::nonNull).collect (Collectors.toMap (Student::getId,student -> student));
for (Map.Entry<Integer, Demo1.Student> entrySet: map.entrySet ()){
System.out.println (entrySet.getKey () +":"+ entrySet.getValue ());
}
System.out.println ("=======================排序后分割线===================");
//常用操作四:集合排序
//方法1:利用Comparable 接口实现排序 String和Integer都是实现了此接口,需要覆写其
List<Student> notNullList = list.stream ().filter (Objects::nonNull).collect (Collectors.toList ());
Collections.sort (notNullList,(o1, o2) -> {
return Integer.compare (o1.age,o2.age);
});
//利用Comparator 接口实现排序
Collections.sort (notNullList,Comparator.comparing (o -> o.age));
notNullList.forEach (student -> System.out.println (student));
//当然觉得性能差 可以用并行方式输出,但结果不一定是顺序执行 1234...9
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream().forEach(System.out::println);
numbers.parallelStream ().forEachOrdered (System.out::println);//顺序执行
//list 转map
Map<String, List<CollegeMajorEnum>> majorEnumMap = userService.getMajorEnum().stream().collect(Collectors.groupingBy(CollegeMajorEnum::getMajorCode));
//对list某个变量进行+1
List<HqQuestionnairePunch> collect = list.stream().map(hqQuestionnairePunch -> hqQuestionnairePunch.setPunchTimes(hqQuestionnairePunch.getPunchTimes() + 1)).collect(Collectors.toList());
//对某个list对象取出某个属性
List<Integer> exists = list.stream().map(HqQuestionnairePunch::getUid).collect(Collectors.toList());
}
}
原文出处
https://blog.csdn.net/qq_40826106/article/details/88964794