什么是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 //休假
}
public static List<Employee> getData() {
return Arrays.asList(
new Employee("张三", 18, 9999.99, Status.FREE),
new Employee("李四", 58, 5555.55, Status.VOCATION),
new Employee("王五", 26, 3333.33, Status.VOCATION),
new Employee("赵六", 36, 6666.66, Status.BUSY),
new Employee("赵六", 36, 6666.66, Status.FREE),
new Employee("田七", 12, 8888.88, Status.BUSY)
);
}
}
中间操作与终止操作的一些说明
- 如果没有终止操作,那么中间操作不会执行
- 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,
- 否则中间操作不会执行,任何的处理
- 而在终止操作时一次性全部处理,成为惰性求值
例子:
@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 = *****张三,李四,王五,赵六,赵六,田七###
}