Java8 中Stream API介绍
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
流(Stream)的概念:流是数据渠道,用于操作数据(集合、数组等)所生成的元素序列。
注意:
- Stream自己不会存储元素。
- Stream不会改便源对象,相反,它们会返回一个持有结果的新Stream。
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候执行。
Stream的操作三个步骤:
- 创建Stream:一个数据源(如数组、集合),获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果
创建Stream
创建流的方式有如下几种方式:
代码语言:javascript复制 //创建Stream
@Test
public void test01(){
//1.可以通过Collection系列集合提供的stream() 或 parallelStream()
List<String> list = new ArrayList();
Stream<String> stream01 = list.stream();
//2、通过Arrays中的静态方法stream() 获取数组流
Emp[] emps = new Emp[10];
Stream<Emp> stream02 = Arrays.stream(emps);
//3.通过Stream类中的静态方法of()
Stream<String> stream03 = Stream.of("aa","bb","cc");
//4.创建无限流
//迭代
Stream<Integer> stream04 = Stream.iterate(0,(x) -> x 2);
stream04.forEach(System.out::println);
//只要10个数据
stream04.limit(10).forEach(System.out::println);
//生成5个随机数
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
}
Stream中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
筛选与切片:
- filter -------- 接受Lambda ,从流中排除某些元素
- limit --------- 截断流,使其元素不超过给定数量
- skip(n) ------- 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
- distinct ------ 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
映射:
- map ------ 接收Lambda,将元素转换为其他形式或提取信息(接受一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素)
- flatMap ---- 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有的流连凑成一个流。
排序:
- sorted() ---- 自然排序
- sorted(Comparator comparator) ------ 定制排序(Comparator )
下面通过代码来练习这些中间操作,先创建一个Employee实体类:
代码语言:javascript复制public class Employee {
private String name;
private Integer age;
private Double salary;
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) &&
Objects.equals(age, employee.age) &&
Objects.equals(salary, employee.salary);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
@Override
public String toString() {
return "Employee{"
"name='" name '''
", age=" age
", salary=" salary
'}';
}
}
测试中间操作filter的用法:
代码语言:javascript复制 List<Employee> emps = Arrays.asList(
new Employee("张三",21,4500.00),
new Employee("李四",25,6000.00),
new Employee("王五",56,3500.00),
new Employee("王五",56,3500.00),
new Employee("田七",30,8000.00),
new Employee("田七",30,8000.00)
);
@Test
public void test02(){
//中间操作:不会执行任何操作
Stream<Employee> stream = employeeList.stream().filter(e -> e.getAge()>25);
//终止操作:一次型执行全部内容,即”惰性求值“
//内部迭代:迭代操作由Stream API 完成
stream.forEach(System.out::println);
}
//外部迭代
@Test
public void test03(){
Iterator<Employee> it = emps.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
中间操作:limit --只要找到符合条件的指定条数数据,就不会执行后面的数据过滤操作了,可以提高效率
代码语言:javascript复制 @Test
public void test04(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.limit(2) //短路 : 只要找到符合条件的两条数据,就不会执行后面的数据过滤操作了,可以提高效率
.forEach(System.out::println);
}
中间操作:skip(n) ----跳过n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
代码语言:javascript复制 @Test
public void test05(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.skip(2)
.forEach(System.out::println);
}
中间操作: distinct ---- 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
代码语言:javascript复制 @Test
public void test06(){
emps.stream().filter(e -> e.getSalary()>3000.00)
.distinct ()
.forEach(System.out::println);
}
中间操作: map ------ 接受一个函数作为参数,该函数或被应用到每个元素上,并将其映射成一个新的元素
代码语言:javascript复制 @Test
public void test07(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
list.stream()
//toUpperCase()函数被应用到流中每个元素上,并将其映射成一个新的元素
.map((str) -> str.toUpperCase())
.forEach(System.out::println); //输出结果:AAA BBB CCC DDD HELLO
System.out.println("------------------------------");
emps.stream()
.map(Employee::getName)
.forEach(System.out::println);//输出结果:张三 李四 王五 王五 田七 田七
System.out.println("------------------------------");
Stream<Stream<Character>> stream01 = list.stream()
//调用filterCharacter(),将流中的字符串元素都转为字符流,返回值类型为Stream<Stream<Character>>
.map(StreamApiTest::filterCharacter);
stream01.forEach((sm) -> {
sm.forEach(System.out::println);
});
System.out.println("------------------------------");
}
//字符串转为字符流
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}
中间操作: flatMap — 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连凑成一个流
代码语言:javascript复制 @Test
public void test08(){
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
Stream<Character> stream02 = list.stream()
//调用filterCharacter(),将流中的字符串元素都转为字符流,并将这些流加入到一个新流中,返回值类型为Stream<Character>
.flatMap(StreamApiTest::filterCharacter);
stream02.forEach(System.out::println);
}
中间操作: sorted() ---- 自然排序
代码语言:javascript复制 @Test
public void test09(){
List<String> list = Arrays.asList("ccc","aaa","ddd","bbb","eee");
list.stream().sorted().forEach(System.out::println);
}
中间操作: sorted(Comparator comparator) ------ 定制排序(Comparator ) 自定义排序规则,这个根据员工年龄排序,若员工年龄相同,则根据员工姓名排序 — 升序
代码语言:javascript复制 @Test
public void test10(){
emps.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);
}
终止操作
查找与匹配:
- allMatch ----- 检查是否匹配所有元素
- anyMatch ------ 检查是否至少匹配一个元素
- noneMatch --------- 检查是否没有匹配所有元素
- findFirst ------- 返回第一个元素
- findAny -------- 返回流中的任意元素
- count ----------- 返回流中元素的总个数
- max ------- 返回流中的最大值
- min ------ 返回流中的最小值
归约:可以将流中元素反复结合起来,得到一个值
- reduce(T indentity,BinaryOperator bin) ---- indentity 为起始值
- reduce(BinaryOperator bin)
收集:
- collect ----- 将流装换为其它形式,接受一个Collector接口的实现,用于给Stream中元素汇总的方法
终止操作练习:在此之前,我们先创建一个员工实体类,方便测试效果
代码语言:javascript复制public class Employee {
private String name;
private Integer age;
private Double salary;
private Status status;
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, Integer age, Double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.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;
}
@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 instanceof Employee)) return false;
Employee employee = (Employee) o;
return Objects.equals(getName(), employee.getName()) &&
Objects.equals(getAge(), employee.getAge()) &&
Objects.equals(getSalary(), employee.getSalary()) &&
getStatus() == employee.getStatus();
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge(), getSalary(), getStatus());
}
public enum Status{
BUSY,FREE,VACATION;
}
}
接下来我们先对 查找与匹配 中的几个终止操作进行代码测试:
代码语言:javascript复制 List<Employee> employees = Arrays.asList(
new Employee("张三",21,4500.00, Employee.Status.BUSY),
new Employee("李四",25,6000.00,Employee.Status.VACTION),
new Employee("王五",56,3500.00,Employee.Status.BUSY),
new Employee("王五",56,3500.00,Employee.Status.BUSY),
new Employee("田七",30,8000.00,Employee.Status.FREE),
new Employee("田七",30,8000.00,Employee.Status.FREE)
);
@Test
public void test11(){
//allMatch ----- 检查是否匹配所有元素
boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(b1); //输出结果: false
//anyMatch ------ 检查是否至少匹配一个元素
boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2); //输出结果: true
//noneMatch --------- 检查是否没有匹配所有元素
boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3); //输出结果: false
//findFirst ------- 返回第一个元素
//Optional --- 是Java8 提供的处理空指针异常的类
Optional<Employee> employee = employees.stream()
//按员工的薪资排序
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
//获取第一个员工信息,即薪资最新的员工信息
.findFirst();
System.out.println(employee.get());
//findAny -------- 返回流中的任意元素
//parallelStream --- 获取并行流
Optional<Employee> any = employees.parallelStream()
//获取状态为 FREE 的任意一个员工信息
.filter(e -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(any.get());
}
测试终止操作中,count,max,min的运用
代码语言:javascript复制 @Test
public void test12(){
//返回流中元素的总个数
long count = employees.stream().count();
System.out.println(count); //输出结果为:6
Optional<Employee> max = employees.stream().
//获取年龄最大的员工信息
max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(max.get()); // 输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY}
Optional<Double> min = employees.stream()
.map((e) -> e.getSalary())
//获取最低的薪资
.min(Double::compare);
System.out.println(min.get());//输出结果为:3500.0
}
终止操作:归约 ---- reduce(T indentity,BinaryOperator) / reduce(BinaryOperator),可以将流中元素反复结合起来,得到一个值
代码语言:javascript复制 @Test
public void test13(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list.stream()
//0为初始值,将流中的元素按照 lambda体中的方式进行汇总,这里即是通过求和的方式汇总
.reduce(0, (x, y) -> x y);
System.out.println(reduce); // 输出结果为:55
Optional<Double> sum = employees.stream()
//获取员工的薪资信息
.map((e) -> e.getSalary())
//调用Double的sum(),对员工薪资进行求和
.reduce(Double::sum);
System.out.println(sum.get()); //输出结果为:33500.0
}
终止操作:收集:collect ------- 将流装换为其它形式,接收一个Collector 接口的实现,用于给Stream中元素汇总的方法
代码语言:javascript复制 @Test
public void test14(){
//Collectors工具类对Collector接口提供了很多实现
List<String> list = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
System.out.println(list);//输出结果为:[张三, 李四, 王五, 王五, 田七, 田七]
Set<Integer> set = employees.stream()
.map(e -> e.getAge())
.collect(Collectors.toSet());
System.out.println(set);//输出结果为:[21, 56, 25, 30]
HashSet<String> hashSet = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);//输出结果为:[李四, 张三, 王五, 田七]
}
因为collect收集使用是很常见的,接下来我们通过使用collect进行统计、求平均值、总和、最大值、最小值,更加熟悉collect的使用,并了解工具类Collectors中常用的方法
代码语言:javascript复制 @Test
public void test15(){
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count); // 输出结果为:6
//获取员工薪资的平均值
Double avgSalary = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);// 输出结果为:5583.333333333333
//获取员工薪资的总和
Double total = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(total); // 输出结果为:33500.0
//获取最高薪资的员工信息
Optional<Employee> maxSalary = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(maxSalary.get()); //输出结果为:Employee{name='田七', age=30, salary=8000.0, status=FREE}
//获取最低薪资的员工信息
Optional<Employee> minSalary = employees.stream()
.collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(minSalary.get()); //输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY}
}
通过使用collect,对流中元素进行分组、多级分组、分区操作。
代码语言:javascript复制 @Test
public void test16(){
//通过员工状态进行分组
Map<Employee.Status, List<Employee>> statusListMap = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(statusListMap);
}
/**
* 多级分组
*/
@Test
public void test17(){
//通过员工状态进行分组
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
if (e.getAge() < 30) {
return "青年";
} else if (e.getAge() < 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
/**
* 分区
*/
@Test
public void test18(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
System.out.println(map);
}
/**
* 将流中的元素,按照指定格式连接
*/
@Test
public void test19(){
String str = employees.stream()
.map(e -> e.getName())
.collect(Collectors.joining(","));
System.out.println(str); //输出结果为: 张三,李四,王五,王五,田七,田七
}
并行流与顺序流
并行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。 Java8中将并行流进行了优化,我们可以很容易地对数据进行并行操作。Stream API可以声明性地通过Parallel()与sequential()在并行流与顺序流之间进行切换。 以下实例我们使用 parallelStream 来输出空字符串的数量:
代码语言:javascript复制 @Test
public void test20(){
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println(count); //输出结果为:2
}
Stream API应用
Java8中的Stream API可以极大提高我们的的生产力,让我们写出高效率、干净、简洁的代码。 例如:使用Java8来求两个集合的交集、差集、并集
代码语言:javascript复制 @Test
public void test(){
//准备两个集合
List<String> list1 = new ArrayList<String>();
list1.add("aa");
list1.add("bb");
list1.add("cc");
list1.add("dd");
list1.add("ee");
List<String> list2 = new ArrayList<String>();
list2.add("bb");
list2.add("cc");
list2.add("ff");
list2.add("gg");
// 交集
List<String> intersection = list1.stream().filter(item -> list2.contains(item)).collect(toList());
System.out.println("---交集 intersection---");
intersection.parallelStream().forEach(System.out :: println);
// 差集
List<String> reduce = list2.stream().filter(item -> !list1.contains(item)).collect(toList());
System.out.println("---差集 reduce2 (list2 - list1)---");
reduce.parallelStream().forEach(System.out :: println);
//并集
list1.addAll(list2);
List<String> collect = list1.stream().distinct().collect(toList());
System.out.println("并集----去重");
collect.stream().forEach(System.out::println);
}