什么是 Stream?
Stream 就好像一个高级的迭代器,但只能遍历一次,在流的过程中,对流中的元素执行一些操作,比如“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等。
要想操作流,首先需要有一个数据源,可以是数组或者集合。每次操作都会返回一个新的流对象,方便进行链式操作,但原有的流对象会保持不变。
流的操作可以分为两种类型:
- 中间操作,可以有多个,每次返回一个新的流,可进行链式操作。
- 终端操作,只能有一个,每次执行完,这个流也就用光光了,无法执行下一个操作,因此只能放在最后。
中间操作不会立即执行,只有等到终端操作的时候,流才开始真正地遍历,用于映射、过滤等。通俗点说,就是一次遍历执行多个操作,性能就大大提高了。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
以上的流程转换为 Java 代码为:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
案例演示
1、创建流
package demo;
import org.junit.Test;
import java.util.stream.Stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Stream的3个步骤
* 1、创建stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamDemo {
//创建stream
@Test
public void test1(){
//1、可以通过collection系列集合提供的串行流stream()或并行流parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//2、通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
//3、通过stream类中的静态方法of()
Stream<String> stream3 = Stream.of("a", "b", "c");
//4、创建无限流
//4.1迭代
Stream<Integer> stream4 = Stream.iterate(0,(x)-> x+2);
//stream4.forEach(System.out::println);
stream4.limit(10).forEach(System.out::println);
//4.2生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
}
查看 Stream 源码的话,你会发现 of() 方法内部其实调用了 Arrays.stream() 方法。
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
另外,集合还可以调用 parallelStream() 方法创建并发流,默认使用的是 ForkJoinPool.commonPool() 线程池。
List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();
创建Employee类
public class Employee {
private String name;
private Integer age;
private Double salary;
//无参构造、有参构造、set、get、tostring()、hashcode、equals
2操作流
Stream 类提供了很多有用的操作流的方法
1)筛选与切片
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
package demo;
import org.junit.Test;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream的3个步骤
* 1、创建stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamDemo2 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,100.12),
new Employee("lisi",20,200.12),
new Employee("wangwu",30,300.12),
new Employee("zhangyuting",40,500.12),
new Employee("tangerrhu",450,500.12),
new Employee("tangerrhu",450,500.12)
);
//中间操作
/**
* 筛选与切片
* filter-接收lambda,从流中排除某些元素
* limit—截断流,使其元素不超过给定数量
* skip(n)—跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流。与limit(n)互补
* distinct—筛选,通过流所生成元素的hashCode()和equals()去除重复元素
*/
//内部迭代:迭代操作由Stream api完成
@Test
public void test1(){
//中间操作:不会执行任何操作
Stream<Employee> stream = employees.stream()
.filter((e) -> {
System.out.println("Stream api 的中间操作");
return e.getAge()>30;
});
//终止操作:一次性执行全部内容,即"惰性求值”
stream.forEach(System.out::println);
}
//外部迭代:迭代操作由自己完成,自己创建迭代器
@Test
public void test2(){
Iterator<Employee> iterator = employees.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
//limit
@Test
public void test3(){
//先过滤再获取
employees.stream()
.filter((e) -> {
System.out.println("短路");
return e.getAge() > 10;
})
.limit(3)
.forEach(System.out::println);
}
//skip,没跳过之前符合条件是4个人,跳过前2个之后,符合条件的只剩下后2个人
@Test
public void test4(){
//先过滤再获取
employees.stream()
.filter((e) -> e.getAge() > 10)
.skip(2)
.forEach(System.out::println);
}
//distinct,要想去重成功,必须在泛型类Employee对象中重写hashcode和euqals才可以
@Test
public void test5(){
//先过滤再获取
employees.stream()
.filter((e) -> e.getAge() > 10)
.distinct()
.forEach(System.out::println);
}
}
filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,因此,我们可以直接将一个 Lambda 表达式传递给该方法,比如说 element -> element.contains(“皮”) 就是筛选出带有“皮”的字符串。
forEach() 方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数,类名 :: 方法名是 Java 8 引入的新语法,System.out 返回 PrintStream 类,println 方法你应该知道是打印的。
stream.forEach(System.out::println); 相当于在 for 循环中打印,类似于下面的代码:
for (String s : strs) {
System.out.println(s);
}
很明显,一行代码看起来更简洁一些。
2)映射
map 方法用于映射每个元素到对应的结果
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
package demo;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* Stream的3个步骤
* 1、创建stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamDemo3 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,100.12),
new Employee("lisi",20,200.12),
new Employee("wangwu",30,300.12),
new Employee("zhangyuting",40,500.12),
new Employee("tangerrhu",450,500.12),
new Employee("tangerrhu",450,500.12)
);
/**
* 映射
* map—接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
* flatMap—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test1(){
List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
list.stream()
.map((e)->e.toUpperCase())
.forEach(System.out::println);
}
@Test
public void test2(){
//method reference,Employee::getAge 和 (e)->e.getAge() 一样
employees.stream()
.map(Employee::getAge)
// .map((e)->e.getAge())
.forEach(System.out::println);
}
//遍历集合中的全部元素
@Test
public void test3(){
List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
Stream<Stream<Character>> stream = list.stream()
.map(StreamDemo3::filterCharacter);
stream.forEach((sm) -> {
sm.forEach(System.out::println);
});
}
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
//字符串转变为字符数组,并进行遍历
for (Character ch : str.toCharArray()){
//将遍历后的字符放入list列表中
list.add(ch);
}
return list.stream();
}
//flatMap,简化案例3
@Test
public void test4(){
List<String> list = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
Stream<Character> characterStream = list.stream()
.flatMap(StreamDemo3::filterCharacter1);
characterStream.forEach(System.out::println);
}
public static Stream<Character> filterCharacter1(String str){
List<Character> list = new ArrayList<>();
//字符串转变为字符数组,并进行遍历
for (Character ch : str.toCharArray()){
//将遍历后的字符放入list列表中
list.add(ch);
}
return list.stream();
}
/**
* map和flatmap区别
* map:将另一个集合整个添加到这个集合中
* flatMap:将另一个集合中的每一个元素分别添加到这个集合中
*
* 我们用add和addAll来演示
*/
//flatMap,简化案例3
@Test
public void test5(){
List<String> list1 = Arrays.asList("aaa", "bbb","ccc", "ddd", "eee");
List list2 = new ArrayList();
list2.add(11);
list2.add(22);
list2.add(list1);
System.out.println(list2);
//结果:[11, 22, [aaa, bbb, ccc, ddd, eee]]
// list2.addAll(list1);
// System.out.println(list2);
//结果:[11, 22, aaa, bbb, ccc, ddd, eee]
}
}
map() 方法接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数,此时参数 为 String 类的 length 方法,也就是把 Stream 的流转成一个 Stream 的流。
3)排序
sorted 方法用于对流进行排序
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):定制排序,自定义Comparator排序器
package demo;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* Stream的3个步骤
* 1、创建stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamDemo4 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,100.12),
new Employee("lisi",20,200.12),
new Employee("wangwu",30,300.12),
new Employee("zhangyuting",40,500.12),
new Employee("tangerrhu",450,500.12),
new Employee("tangerrhu",45,500.12)
);
/**
* 排序
* sorted()—自然排序(Comparable)
* sorted(Comparator com)—定制排序(Comparator)
*/
//自然排序
@Test
public void test1(){
List<String> list = Arrays.asList("cc", "bb", "ee", "dd", "aa");
list.stream()
.sorted()
// .sorted(String::compareTo)
.forEach(System.out::println);
}
//定制排序
@Test
public void test2(){
employees.stream()
.sorted((e1,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else {
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
}
4)查找与匹配
修改Employee类
public class Employee {
private String name;
private Integer age;
private Double salary;
private Status status;
//无参构造、有参构造、set、get、toString、hashcode、equals
//创建枚举类
public enum Status{
FREE,
BUSY,
VOCATION
}
举个栗子
Stream 类提供了三个方法可供进行元素匹配,它们分别是:
- anyMatch(),只要有一个元素匹配传入的条件,就返回 true。
- allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
- noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部匹配,则返回 true。
package demo;
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
/**
* Stream的3个步骤
* 1、创建stream
* 2、中间操作
* 3、终止操作(终端操作)
*/
public class StreamDemo5 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
new Employee("lisi",20,200.12, Employee.Status.BUSY),
new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
);
/**
* 查找与匹配
* allMatch—检查是否匹配所有元素
* anyMatch—检查是否至少匹配一个元素
* noneMatch—检查是否没有匹配所有元素
* findFirst—返回第一个元素
* findAny—返回当前流中的任意元素
* count—返回流中元素的总个数
* max—返回流中最大值
* min—返回流中最小值
*/
@Test
public void test1(){
boolean b1 = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
boolean b2 = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(b2);
boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(b3);
//查询工资最低的人
Optional<Employee> first = employees.stream()
// .sorted((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()))
.sorted(Comparator.comparingDouble(Employee::getSalary))
.findFirst();
System.out.println(first.get());
//找到Free状态中任意一个人,此处是并行流,多个线程同时过滤
Optional<Employee> any = employees.parallelStream()
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(any.get());
//获取总数
long count = employees.stream()
.count();
System.out.println(count);
//获取工资最多的人
Optional<Employee> max = employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary));
System.out.println(max.get());
//打印出最低工资
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(min.get());
}
}
5)规约和收集
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串
reduce() 方法的主要作用是把 Stream 中的元素组合起来,它有两种用法:
- Optional reduce(BinaryOperator accumulator)
没有起始值,只有一个参数,就是运算规则,此时返回 Optional。
- T reduce(T identity, BinaryOperator accumulator)
有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。
package demo;
import org.junit.Test;
import java.util.*;
import java.util.stream.Collectors;
public class StreamDemo6 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
new Employee("lisi",20,200.12, Employee.Status.BUSY),
new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
);
/**
* 规约
* reduce() (T identity,BinaryOperator) / reduce(BinaryOperator) —可以将流中元素反复结合起来,得到一个值
* 起始值作为x,集合中的元素作为y
* 此处0作为起始值x,集合中的元素作为y,不断的将y和x相加
*/
@Test
public void test1(){
List<Integer> list = Arrays.asList(2,5,9,3,5,1,8,4,7);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
}
/**
* 问题:为什么test1返回的就是List,而test2返回的是Optional?
* 因为test1不会是空值
* 而test2中有可能返回的是空值
* 有可能为空的值就封装到optional中,避免空指针
*/
//计算工资的总和
@Test
public void test2(){
Optional<Double> salSum = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(salSum.get());
}
/**
* 收集
* collect—将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法
*/
//将员工的名字提取出来
@Test
public void test3(){
List<String> list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
}
//将员工的名字提取出来,要去重,需要将数据收集到set里面
@Test
public void test3_2(){
Set<String> set = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
}
//将员工的名字提取出来,要去重,需要将数据收集到set里面,此处使用hashSet
@Test
public void test3_3(){
HashSet<String> hashSet = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
}
//求总数
@Test
public void test4(){
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count);
}
//求工资平均值
@Test
public void test5(){
Double avgSal = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSal);
}
//求工资总和
@Test
public void test6(){
Double avgSum = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(avgSum);
}
//获取工资最高的员工信息
@Test
public void test7(){
Optional<Employee> maxSal = employees.stream()
// .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
.collect(Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)));
System.out.println(maxSal.get());
}
//获取最低工资
@Test
public void test8(){
Optional<Double> minSal = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(minSal.get());
}
//按员工状态分组
@Test
public void test9(){
Map<Employee.Status, List<Employee>> status = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(status);
}
//多级分组,先按状态分,再按年龄分
@Test
public void test10(){
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (((Employee) e).getAge() <= 30) {
return "青年";
} else if (((Employee) e).getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
//以true和false分区查询,满足条件的一个区,不满足条件的另一个区
@Test
public void test11(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 480));
System.out.println(map);
}
//获取总和、平均值、最大最小等等的另一个方法 summarizingDouble
@Test
public void test12(){
DoubleSummaryStatistics map = employees.stream()
.collect(Collectors.summarizingDouble((e) -> e.getSalary()));
System.out.println(map.getMax());
System.out.println(map.getCount());
System.out.println(map.getMin());
System.out.println(map.getSum());
System.out.println(map.getAverage());
}
//字符串连接
@Test
public void test13(){
String s = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println(s);
}
}
运算规则可以是 Lambda 表达式(比如 (a, b) -> a + b),也可以是类名::方法名(比如 Integer::sum)。
转换流collect介绍
既然可以把集合或者数组转成流,那么也应该有对应的方法,将流转换回去——collect() 方法就满足了这种需求。
toArray() 方法可以将流转换成数组,那String[]::new,是什么东东呢?来看一下 toArray() 方法的源码。
<A> A[] toArray(IntFunction<A[]> generator);
也就是说 String[]::new 是一个 IntFunction,一个可以产生所需的新数组的函数,可以通过反编译字节码看看它到底是什么:
String[] strArray = (String[])list.stream().toArray((x$0) -> {
return new String[x$0];
});
System.out.println(Arrays.toString(strArray));
也就是相当于返回了一个指定长度的字符串数组。
当我们需要把一个集合按照某种规则转成另外一个集合的时候,就可以配套使用 map() 方法和 collect() 方法。
List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());
通过 stream() 方法创建集合的流后,再通过 map(String:length) 将其映射为字符串长度的一个新流,最后通过 collect() 方法将其转换成新的集合。
Collectors 是一个收集器的工具类,内置了一系列收集器实现,比如说 toList() 方法将元素收集到一个新的 java.util.List 中;比如说 toCollection() 方法将元素收集到一个新的 java.util.ArrayList 中;比如说joining() 方法将元素收集到一个可以用分隔符指定的字符串中。
Stream api 部分练习
package demo;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class StreamDemo7 {
List<Employee> employees = Arrays.asList(
new Employee("zhangsam",10,1000.12, Employee.Status.FREE),
new Employee("lisi",20,200.12, Employee.Status.BUSY),
new Employee("wangwu",30,300.12, Employee.Status.VOCATION),
new Employee("zhangyuting",40,500.12,Employee.Status.BUSY),
new Employee("tangerrhu",450,500.12,Employee.Status.FREE),
new Employee("tangerrhu",45,500.12,Employee.Status.VOCATION)
);
/**
* 1、给定一个数字列表,返回一个由每个数的平方构成的列表
* 给定[1,2,3,4,5],返回[1,4,9,16,25]
*/
@Test
public void test1(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> integerStream = list.stream()
.map((e) -> e * e);
integerStream.forEach(System.out::println);
}
/**
* 用map和reduce算出流中有多少employee
*/
@Test
public void test2(){
Optional<Integer> sum = employees.stream()
.map((e) -> 1)
.reduce(Integer::sum);
System.out.println(sum.get());
}
}