什么是Stream?
是数据渠道,用于操作数据源(集合数组等)所生成的元素序列
“集合讲的是数据,流讲的是计算”
注意:
- Strram自己不会存储元素。
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream操作时延迟执行的。这意味着他们会等到需要结果的时候才执行
Stream的三个操作步骤:
- 创建Stream
- 中间操作
- 终止操作(终端操作)
创建Stream
-
可以通过Collection系列集合提供的stream()或parallelStream()
-
stream():串行流
-
parallelStream():并行流
-
List<Object> list = new ArrayList<>(); Stream<Object> stream = list.stream();//得到一个流
-
-
通过Arrays中的静态方法stream()获取数组流
-
String[] emps = new String[10]; Stream<String> stream1 = Arrays.stream(emps);
-
-
通过Stream类中的静态方法 of()
-
Stream<String> stream2 = Stream.of("aa", "bb", "cc"); //可变数组
-
-
创建无限流
-
迭代
-
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2); //stream3.forEach(System.out::println); //按照一元运算的规律 从0开始,无限+2 //添加限制操作 限制只打印10个 stream3.limit(10).forEach(System.out::println);
-
-
生成
-
Stream<Double> generate = Stream.generate(Math::random); //生成随机数 generate.limit(2).forEach(System.out::println);
-
-
创建实体类
public class Employee {
private String name;
private Integer age;
private double salary;
private Status status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public Employee(String name, Integer age, double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Employee() {
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee2 = (Employee) o;
return Double.compare(employee2.salary, salary) == 0 &&
Objects.equals(name, employee2.name) &&
Objects.equals(age, employee2.age) &&
status == employee2.status;
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary, status);
}
public enum Status{
FREE, //空闲
BUSY, //忙
VOCATION //休假
}
}
中间操作与终止操作的一些说明
- 如果没有终止操作,那么中间操作不会执行
- 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,
- 否则中间操作不会执行,任何的处理
- 而在终止操作时一次性全部处理,成为惰性求值
例子:
@Test
public void test3() {
Stream<Employee> stream = employees.stream()//创建流 获取流
.filter((x) -> {
System.out.println("中间操作");
return x.getSalary() > 6000;
});
}
这段代码没有任何运行结果,因为没有终止操作。
中间操作
筛选与切片:
方法 | 介绍 |
---|---|
filte | 接收Lambda ,从流中排除某些元素 |
limit | 截断流,使其元素不超过给定数量。 |
skip(n) | 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
distinct | 筛选,通过流所生成元素的hashCode()和equals()去重复元素 |
filter
- 接收Lambda ,从流中排除某些元素
@Test
public void test2() {
employees.stream()//创建流 获取流
.filter((x) -> x.getSalary() > 6000)
//入参断言型接口 boolean TraderTest(T t);
.forEach(System.out::println); //终止操作
}
结果:
Employee{name='张三', age=18, salary=9999.99, status=FREE}
Employee{name='赵六', age=36, salary=6666.66, status=BUSY}
Employee{name='赵六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
limit
/**
* limit —— 截断流,使其元素不超过给定数量。
*/
@Test
public void test5() {
employees.stream()
.filter((x) ->
{
System.out.println("短路");
return x.getSalary() > 6000;
}
)
.limit(2) //筛选完后只打印两个
.forEach(System.out::println);
//这里找到符合条件的两个就停止,后续的操作就没有了,
//说明所有的中间操作都是同步进行的,增加了效率
}
结果:
短路
Employee{name='张三', age=18, salary=9999.99, status=FREE}
短路
短路
短路
Employee{name='赵六', age=36, salary=6666.66, status=BUSY}
skip(n)
/**
* skip(n) —— 跳过元素,返回一个扔掉了前n个元素的流。
* 若流中元素不足n个,则返回一个空流。与limit(n)互补
*/
@Test
public void test6() {
employees.stream()
.filter((x) ->
{
System.out.println("skip");
return x.getSalary() > 6000;
}
)
.skip(2)
.forEach(System.out::println);
}
结果:
skip
skip
skip
skip
skip
Employee{name='赵六', age=36, salary=6666.66, status=FREE}
skip
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
distinct
/**
* distinct —— 筛选,通过流所生成元素的hashCode()和equals()去重复元素
*
* 需要重写hashCode和equals
*/
@Test
public void test7() {
employees.stream()
.distinct()
.forEach(System.out::println);
}
结果:
Employee{name='张三', age=18, salary=9999.99, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='赵六', age=36, salary=6666.66, status=BUSY}
Employee{name='赵六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
映射
方法 | 介绍 |
---|---|
map | 接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
flatMap | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连成一个流 |
map
/**
* map——接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,
* 该函数会被应用到每个元素上,并将其映射成一个新的元素。
*/
@Test
public void test8(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
list.stream()
.map((x) -> x.toUpperCase()) //把集合所有内容转大写
.forEach(System.out::println);
//AAA
//BBB
//CCC
//DDD
System.out.println("---------------------");
employees.stream()
.map((x) -> x.getName())
.forEach(System.out::println);
//张三
//李四
//王五
//赵六
//赵六
//田七
}
flatMap
先声明一个方法
//穿进去一个字符串,把一个字符串中所有的字符一个一个提取出来
static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character character : str.toCharArray()) {
list.add(character);
}
return list.stream();
}
使用flapmap
/**
* flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连成一个流
*/
@Test
public void test9(){
System.out.println("\n\n---------普通字符串---------");
//普通字符串
String str = "helloworld";
filterCharacter(str).forEach(System.out::print);
//helloworld
System.out.println("\n\n---------集合---------");
//集合
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
Stream<Stream<Character>> stream = list.stream()
.map(demo1::filterCharacter); //{{a,a,a},{b,b,b},{c,c,c},...}
//得到的是流的集合,集合里还是个流,然后二层遍历
stream.forEach(
(x) -> x.forEach(System.out::print)
);
//结果:aaabbbcccddd
System.out.println("\n\n---------flatMap---------");
//扁平化,平铺的意思
Stream<Character> stream1 = list.stream()//{a,a,a,b,b,b,c,c,c,...}
.flatMap((x) -> filterCharacter(x));
stream1.forEach(System.out::print);
//结果:aaabbbcccddd
}
map和flatmap的区别:
- map是把多个流放入一个流 形成{ { }, { }, { } }的形式
- flapmap是把每个流中的元素,作为单独的元素加入到新的流中
- 相当于集合add() 和 addAll()方法
测试集合add() 和 addAll()方法
/**
* function:测试集合add() 和 addAll()方法
*/
@Test
public void test10(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
List list2 = new ArrayList<>();
list2.add(111);
list2.add(222);
list2.add(list);
System.out.println(list2);
//结果:[111, 222, [aaa, bbb, ccc, ddd]]
List list3 = new ArrayList<>();
list3.add(111);
list3.add(222);
list3.addAll(list);
System.out.println(list3);
//结果:[111, 222, aaa, bbb, ccc, ddd]
}
结论:
- add是整个集合传进来
- addAll()是将集合中的元素拆分,作为单独的元素传进来
- 所以map相当于把流加到当前的流中
- flatMap相当于把流中的元素,加到新流中
排序
方法 | 介绍 |
---|---|
sorted() | 自然排序 |
sorted(Comparator com) | 定制排序 |
sorted()
/**
排序
sorted() 自然排序
sorted(Comparator com) 定制排序
*/
@Test
public void test11(){
List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
list.stream()
.sorted()
.forEach(System.out::println);
//结果:
//aaa
//bbb
//ccc
//ddd
//eee
}
sorted(Comparator com)
/**
* sorted(Comparator com) 定制排序
*/
@Test
public void test111(){
List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
list.stream()
.sorted((x,y) -> -x.compareTo(y))
.forEach(System.out::println);
//结果:
//eee
//ddd
//ccc
//bbb
//aaa
//定制排序:按照年龄排序,年龄一样按照姓名排
employees.stream()
.sorted((x,y) -> {
if(x.getAge().equals(y.getAge())){
return x.getName().compareTo(y.getName());
} else {
return x.getAge().compareTo(y.getAge());
}
}).forEach(System.out::println);
}
结果:
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
Employee{name='张三', age=18, salary=9999.99, status=FREE}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='赵六', age=36, salary=6666.66, status=BUSY}
Employee{name='赵六', age=36, salary=6666.66, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}
终止操作
查找与匹配
方法 | 介绍 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素的总个数 |
max() | 返回流中最大值 |
min() | 返回流中最小值 |
allMatch(Predicate p)
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
/**
allMatch(Predicate p)
anyMatch(Predicate p)
noneMatch(Predicate p)
findFirst()
findAny()
*/
@Test
public void test12(){
//判断是否所有员工都是BUSY状态
boolean b = employees.stream()
.allMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
System.out.println("b = " + b); //b = false
//判断是否至少有一位员工是BUSY状态
boolean b1 = employees.stream()
.anyMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
System.out.println("b1 = " + b1);//b1 = true
//判断是否没有休假的员工
boolean b2 = employees.stream()
.noneMatch((x) -> x.getStatus().equals(Employee.Status.VOCATION));
System.out.println("b2 = " + b2);//b2 = false
//返回工资最低的员工 findFirst()
Optional<Employee> first = employees.stream()
.sorted((x,y) -> Double.compare(x.getSalary(),y.getSalary()))
.findFirst();
System.out.println("first = " + first.get());
//first = Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
//任意找一个空闲状态的员工(串行流,挨个儿找)
Optional<Employee> any = employees.stream()
.filter((x) -> x.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println("any = " + any.get());
//any = Employee{name='赵六', age=36, salary=6666.66, status=FREE}
//任意找一个空闲状态的员工(并行流,多个线程一起找,谁先找到算谁)
Optional<Employee> any2 = employees.parallelStream()
.filter((x) -> x.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println("any2 = " + any2.get());
//any = Employee{name='赵六', age=36, salary=6666.66, status=FREE}
}
count()
max()
min()
/**
* count() 返回流中元素的总个数
* max() 返回流中最大值
* min() 返回流中最小值
*/
@Test
public void test13(){
long count = employees.stream()
.count();
System.out.println("count = " + count); //count = 6
//获取工资最高的
Optional<Employee> max = employees.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println("max = " + max.get().getSalary()); //max = 9999.99
//获取工资最低的
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compareTo);
System.out.println("min = " + min.get()); //min = 3333.33
}
归约
- 可以将流中元素反复结合起来,得到一个值
方法 | 介绍 |
---|---|
reduce(T identity, BinaryOperator) | 返回T identity 起始值 BinaryOperator二元运算 |
reduce(BinaryOperator) | 返回Optional |
reduce(T identity, BinaryOperator)
reduce(BinaryOperator)
/**
* function:
*/
@Test
public void test14(){
List<Integer> list = Arrays.asList(1,2,3,4,5);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println("sum = " + sum); //sum = 15
Integer sum2 = list.stream()
.reduce(10, (x, y) -> x + y);
System.out.println("sum2 = " + sum2); //sum2 = 25
//首先将起始值作为x,然后从流中取出一个元素给y,
//然后把运算的结果给x,再从集合中取值运算,直到最后
System.out.println("----------工资的总和-------------");
Optional<Double> sumSalary = employees.stream()
.map((x) -> x.getSalary())
.reduce((x, y) -> x + y);
System.out.println("sumSalary= " + sumSalary.get());
//sumSalary = 41111.07
}
这里想一下,为什么上面的返回值是原始值,而工资的返回值是Optional
上面的有初始值,一定不会为空,,而下面的工资没有初始值,可能为空
Optional的原理就是,只要返回的对象有可能为空,那么就封装到Optional中去
备注:map和reduce的连接称为map-reduce模式,因Google用它类进行网络搜索而出名
收集
collect(Collector c)——将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
Collector接口中方法的实现决定了如何对流执行收集操作(如搜集到List,Set,Map)
但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,
Collectors.toList()
/**
* function:把当前公司员工的名字提取出来,并放到一个集合中
*/
@Test
public void test15(){
//收集到list集合中
List<String> list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("list = " + list);
//list = [张三, 李四, 王五, 赵六, 赵六, 田七]
//收集到set集合中,可去重
Set<String> set = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
System.out.println("set = " + set);
//set = [李四, 张三, 王五, 赵六, 田七]
//可以在Collectors.toCollection()中指定任意集合
HashSet<String> hashSet = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
System.out.println("hashSet = " + hashSet);
}
操作数字
/**
* function:操作数字
*/
@Test
public void test16(){
//总数
Long size = employees.stream()
.collect(Collectors.counting());
System.out.println("集合总数 = " + size);
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble((x) -> x.getSalary()));
System.out.println("工资平均值"+avg);
//计算总和
Double total = employees.stream()
.collect(Collectors.summingDouble((x) -> x.getSalary()));
System.out.println("工资总数: = " + total);
//最大值 方法一、
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((x, y) -> Double.compare(x.getSalary(), y.getSalary())));
System.out.println("max = " + max.get());
//max = Employee{name='张三', age=18, salary=9999.99, status=FREE}
//最大值 方法二、
Optional<Employee> max1 = employees.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println("max1 = " + max1.get());
//max = Employee{name='张三', age=18, salary=9999.99, status=FREE}
//最小值
Optional<Double> min = employees.stream()
.map((x) -> x.getSalary())
.collect(Collectors.minBy(Double::compare));
System.out.println("min = " + min.get());
//min = 3333.33
}
Collectors.groupingBy()
/**
* function: 分组
*/
@Test
public void test17(){
//按照状态分组 返回map,key-状态 value-状态下的内容
//map没有foreach方法
Map<Employee.Status, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy((x) -> x.getStatus()));
System.out.println("map = " + map);
}
结果:
map = {
BUSY=[Employee{name='赵六', age=36, salary=6666.66, status=BUSY}, Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
VOCATION=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}, Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
FREE=[Employee{name='张三', age=18, salary=9999.99, status=FREE}, Employee{name='赵六', age=36, salary=6666.66, status=FREE}]
}
多级分组
/**
* function:多级分组
* 可以在Collectors.groupingBy后再跟一个Collectors.groupingBy
* 先按照状态分,分完之后按照年龄分
*/
@Test
public void test18(){
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus,
Collectors.groupingBy((e) -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() > 35 && e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})
));
System.out.println("map = " + map);
/*
结果:
map = {
BUSY={
青年=[Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
中年=[Employee{name='赵六', age=36, salary=6666.66, status=BUSY}]
},
VOCATION={
青年=[Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
老年=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}]
},
FREE={
青年=[Employee{name='张三', age=18, salary=9999.99, status=FREE}],
中年=[Employee{name='赵六', age=36, salary=6666.66, status=FREE}]
}
}
*/
}
分片,分区
/**
* function: 分片,分区
*
* 符合条件的一个区,不满足条件的一个区
*/
@Test
public void test19(){
Map<Boolean, List<Double>> map = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.partitioningBy((x) -> x > 6000));
System.out.println(map);
//结果:
//{
// false=[5555.55, 3333.33],
// true=[9999.99, 6666.66, 6666.66, 8888.88]
// }
}
Collectors.summarizingDouble
/**
* function: 计算数字总和平均值最大最小
*/
@Test
public void test20(){
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
double sum = dss.getSum();
System.out.println("sum = " + sum); //sum = 41111.07
double max = dss.getMax();
System.out.println("max = " + max); //max = 9999.99
}
joining()
/**
* function:连接 joining
*/
@Test
public void test21(){
//连接字符串
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining());
System.out.println("collect = " + collect);
//collect = 张三李四王五赵六赵六田七
//还可以加逗号,去除前后的逗号
String collect1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println("collect1 = " + collect1);
//collect1 = 张三,李四,王五,赵六,赵六,田七
//如果要首尾添加
String collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",", "*****", "###"));
System.out.println("collect2 = " + collect2);
//collect2 = *****张三,李四,王五,赵六,赵六,田七###
}