Java8特性总结——恕我直言你可能真的不会java系列学习笔记
Java8特性总结
- Java8特性总结
——恕我直言你可能真的不会java系列学习笔记
字母哥讲:恕我直言你可能真的不会java系列的学习笔记,以下是学习资料直达地址
视频:https://www.bilibili.com/video/BV1sE411P7C1
文章:https://www.kancloud.cn/hanxt/javacrazy/1568811
主讲: java8基础特性
恕我直言你可能真的不会java系列-lambda、streamAPI、文本块等特性深入讲解
学习笔记源码:https://github.com/gaogushenling/lambda-StreamAPI
01.lambda表达式会用了么
lambda表达式表达接口函数的实现
一、接口定义
1、经典OOP的实现样式
public class LambdaDemo {
//函数定义
public void printSomething(String something) {
System.out.println(something);
}
//通过创建对象调用函数
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
demo.printSomething(something);
}
}
2、创建功能接口,并对该接口定义抽象方法
public class LambdaDemo {
//抽象功能接口
interface Printer {
void print(String val);
}
//通过参数传递功能接口
public void printSomething(String something, Printer printer) {
printer.print(something);
}
}
二、传统的接口函数实现方式
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am using a Functional interface";
//实现Printer接口
Printer printer = new Printer() {
@Override
public void print(String val) {
//控制台打印
System.out.println(val);
}
};
demo.printSomething(something, printer);
}
三、lambda表示式实现方式
lambda表达式的语法:
(param1,param2,param3 ...,paramN)- > { //代码块; }
首先我们知道lambda表达式,表达的是接口函数
箭头左侧是函数的逗号分隔的形式参数列表
箭头右侧是函数体代码
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
//实现Printer接口(请关注下面这行lambda表达式代码)
Printer printer = (String toPrint)->{System.out.println(toPrint);};
//调用接口打印
demo.printSomething(something, printer);
}
例子
package main.java;
public class LambdaDemo1 {
interface Printer{
void printer(String val);
}
public void pringSomething(String something,Printer printer){
printer.printer(something);
}
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = new LambdaDemo1();
String some = "asdasasa";
//不使用lambda表达式
Printer printer = new Printer() {
@Override
public void printer(String val) {
System.out.println(val);
}
};
lambdaDemo1.pringSomething(some,printer);
/*1、使用lambda表达式
*接口匿名实现类,简化函数
* ①参数
* ②函数体
* */
Printer printer1 = (String val) ->{
System.out.println(val);
};
//1.1、进一步简化,去掉参数类型
Printer printer2 = (val) ->{
System.out.println(val);
};
//1.2、进一步简化,去掉参数括号(前提:只有一个参数)
Printer printer3 = val ->{
System.out.println(val);
};
//1.3、进一步简化,去掉函数体大括号(前提:函数体只有一行代码)
Printer printer4 = val -> System.out.println(val);
//1.4、最后可精简为如下,会自动推断lambda传入的参数类型
lambdaDemo1.pringSomething(some,val -> System.out.println(val));
//1.5、无参数传入的话,可以简写成
// () -> System.out.println("无参");
/*
* 总结:
* 省略类型:自动推断
* 省略括号:一个参数
*
* lambda表达式表达的是接口函数,
* 箭头左侧是函数参数,箭头右侧是函数体。
* 函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。
* */
}
}
02.Stream代替for循环(初识Stream-API)
认识
Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。
管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。
例子 Stream代替for循环
package main.java;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamDemo1 {
public static void main(String[] args) {
List<String> letters = Arrays.asList("e", "c", "cbn", "zxc", "caz", "d", "b", "a");
//不使用StreamAPI
/* for (String letter : letters) {
//转换大写
String temp = letter.toLowerCase();
//排序
//转
//总之非常麻烦
}*/
//1、集合 使用StreamAPI
//数组转换成流
List<String> lettertsNew = letters.stream()
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
System.out.println(lettertsNew);
//2、数组转换成流
String[] letters2 = {"e", "c", "cbn", "zxc", "caz", "d", "b", "a"};
Stream.of(letters2).filter(s -> s.startsWith("c")).map(String::toUpperCase).sorted().toArray(String[]::new);
//3、set转成流:集合类是一样的
Set<String> lettersSet = new HashSet<>(letters);
List<String> lettertsNewSet = lettersSet.stream()
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
//4、文本文件转换成流
Paths.get("file.text");
try {
Stream<String> stringStream = Files.lines(Paths.get("file.text"));
List<String> f = stringStream
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
}
}
题外话 介绍双冒号操作符
双冒号运算就是Java中的[方法引用] Method
[方法引用]的格式是
类名::方法名
例如
1.表达式:
person -> person.getName();
可以替换成:
Person::getName
2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new
方法引用的种类
方法引用有四种,分别是:
指向静态方法的引用
指向某个对象的实例方法的引用
指向某个类型的实例方法的引用
指向构造方法的引用
其实,JVM 本身并不支持指向方法引用,过去不支持,现在也不支持。
Java 8 对方法引用的支持只是编译器层面的支持,虚拟机执行引擎并不了解方法引用。编译器遇到方法引用的时候,会像上面那样自动推断出程序员的意图,将方法引用还原成接口实现对象,或者更形象地说,就是把方法引用设法包装成一个接口实现对象,这样虚拟机就可以无差别地执行字节码文件而不需要管
什么是方法引用了。
需要注意的是,方法引用是用来简化接口实现代码的,并且凡是能够用方法引用来简化的接口,都有这样的特征:有且只有一个待实现的方法。这种接口在 Java 中有个专门的名称: 函数式接口。当你用试图用方法引用替代一个非函数式接口时,会有这样的错误提示: xxx is not a functional interface。
3.Stream的filter与谓语逻辑
什么是谓词逻辑?
WHERE 和 AND 限定了主语employee是什么,那么WHERE和AND语句所代表的逻辑就是谓词逻辑
SELECT *
FROM employee
WHERE age > 70
AND gender = \'M\'
谓词逻辑的复用
filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。
比如:将下面的谓词逻辑定义在Employee实体class中。
public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
and语法(并集)
or语法(交集)
negate语法(取反)
例子
package model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.function.Predicate;
@Data
@AllArgsConstructor
//员工
public class Employee {
private Integer id;
private Integer age; //年龄
private String gender; //性别
private String firstName; //名字
private String lastName; //姓氏
//Predicate接口,在英语中这个单词的意思是:谓词
//谓词逻辑的复用如下
//e -> e.getAge() > 70 && e.getGender().equals("M")
public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
}
package model;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamFilterPredicate {
public static void main(String[] args){
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
List<Employee> filtered = employees.stream()
//filter(写谓词逻辑) 年龄大于70 并且是男性
.filter(e -> e.getAge() > 70 && e.getGender().equals("M"))
.collect(Collectors.toList());
//使用可复用谓词逻辑
List<Employee> filteredAnd = employees.stream()
.filter(Employee.ageGreaterThan70
.and(Employee.genderM))
.collect(Collectors.toList());
//or
List<Employee> filteredOr = employees.stream()
.filter(Employee.ageGreaterThan70
.or(Employee.genderM))
.collect(Collectors.toList());
//negate 取反
List<Employee> filteredNegate = employees.stream()
.filter(
Employee.ageGreaterThan70
.or(Employee.genderM)
.negate()
)
.collect(Collectors.toList());
System.out.println("filtered="+filtered);
System.out.println("filteredAnd="+filteredAnd);
System.out.println("filteredOr="+filteredOr);
System.out.println("filteredNegate="+filteredNegate);
}
}
4.Stream的map数据转换(Stream管道流的map操作)
map函数的作用就是针对管道流中的每一个数据元素进行转换操作。
例子
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamMap1 {
public static void main(String[] args) {
List<String> alpha = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
//不使用Stream管道流
List<String> alphaUpper = new ArrayList<>();
for (String s : alpha) {
alphaUpper.add(s.toUpperCase());
}
System.out.println(alphaUpper); //[MONKEY, LION, GIRAFFE, LEMUR]
// 1、Stream管道流map的基础用法:使用Stream管道流
List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
//上面使用了方法引用,和下面的lambda表达式语法效果是一样的
//List<String> collect = alpha.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
System.out.println(collect); //[MONKEY, LION, GIRAFFE, LEMUR]
//2、处理非字符串类型集合元素
List<Integer> lengths = alpha.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); //[6, 4, 7, 5]
Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
//除了mapToInt。还有maoToLong,mapToDouble等等用法
.mapToInt(String::length)
.forEach(System.out::println);
}
}
import model.Employee;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamMap2 {
public static void main(String[] args) {
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
//每人涨一岁,性别换全词
/*List<Employee> maped = employees.stream()
.map(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
return e;
}).collect(Collectors.toList());*/
List<Employee> maped = employees.stream()
.peek(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
}).collect(Collectors.toList());
System.out.println(maped);
}
}
import java.util.Arrays;
import java.util.List;
public class StreamFlatMap {
public static void main(String[] args) {
List<String> words = Arrays.asList("hello", "word");
words.stream()
.map(w -> Arrays.stream(w.split(""))) //[[h,e,l,l,o],[w,o,r,l,d]]
.forEach(System.out::println);
//flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理
words.stream()
.flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
.forEach(System.out::println);
}
}
5.Stream的状态与并行操作
Stream管道流的基本操作
- 源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理
- 中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等
- 终端操作:作用就是将Stream管道流转换为其他的数据类型。
中间操作:有状态与无状态
状态通常代表公用数据,有状态就是有“公用数据”
Limit与Skip管道数据截取
Distinct元素去重
Sorted排序
串行、并行与顺序
例子
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamState {
public static void main(String[] args) {
//1、Limit与Skip管道数据截取
List<String> limitN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.limit(2)
.collect(Collectors.toList());
List<String> skipN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.skip(2)
.collect(Collectors.toList());
//2、Distinct元素去重
List<String> uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.distinct()
.collect(Collectors.toList());
//3、Sorted排序
List<String> alphabeticOrder = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.sorted()
.collect(Collectors.toList());
//4、串行、并行与顺序
//默认串行
//并行操作parallel,操作无状态操作
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEach(System.out::println);
}
}
结果
Giraffe
Lion
Lemur
Lion
Monkey
Process finished with exit code 0
6.Stream性能差问号 不要人云亦云
7.像使用SQL一样排序集合
一、字符串List排序
二、整数类型List排序
三、按对象字段对List
例子
import model.Employee;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class SortList {
public static void main(String[] args) {
//Collections.sort();
//1、字符串List排序
List<String> cities = Arrays.asList(
"Milan",
"london",
"San Francisco",
"Tokyo",
"New Delhi"
);
System.out.println(cities);
//[Milan, london, San Francisco, Tokyo, New Delhi]
//大小写不敏感的排序
cities.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(cities);
//[london, Milan, New Delhi, San Francisco, Tokyo]
//大小写敏感的排序
cities.sort(Comparator.naturalOrder());
System.out.println(cities);
//[Milan, New Delhi, San Francisco, Tokyo, london]
//同样我们可以把排序器Comparator用在Stream管道流中。
cities.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);
//Milan
//New Delhi
//San Francisco
//Tokyo
//london
//2、整数类型List排序
List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9);
System.out.println(numbers); //[6, 2, 1, 4, 9]
numbers.sort(Comparator.naturalOrder()); //自然排序
System.out.println(numbers); //[1, 2, 4, 6, 9]
numbers.sort(Comparator.reverseOrder()); //倒序排序
System.out.println(numbers); //[9, 6, 4, 2, 1]
//3、按对象字段对List<Object>排序
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
//4、Comparator链对List<Object>排序
employees.sort(Comparator.comparing(Employee::getAge));
employees.forEach(System.out::println);
//先按性别,再按年龄倒序
//如果我们希望List按照年龄age的倒序排序,就使用reversed()方法
employees.sort(
Comparator.comparing(Employee::getGender)
.thenComparing(Employee::getAge)
.reversed()
);
employees.forEach(System.out::println);
//都是正序 ,不加reversed
//都是倒序,最后面加一个reserved
//先是倒序(加reserved),然后正序
//先是正序(加reserved),然后倒序(加reserved)
}
}
结果
[Milan, london, San Francisco, Tokyo, New Delhi]
[london, Milan, New Delhi, San Francisco, Tokyo]
[Milan, New Delhi, San Francisco, Tokyo, london]
Milan
New Delhi
San Francisco
Tokyo
london
[6, 2, 1, 4, 9]
[1, 2, 4, 6, 9]
[9, 6, 4, 2, 1]
Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)
Employee(id=10, age=45, gender=M, firstName=Naveen, lastName=Jain)
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=4, age=26, gender=M, firstName=Jon, lastName=Lowman)
Employee(id=1, age=23, gender=M, firstName=Rick, lastName=Beethovan)
Employee(id=6, age=15, gender=M, firstName=David, lastName=Feezor)
Employee(id=7, age=68, gender=F, firstName=Melissa, lastName=Roy)
Employee(id=5, age=19, gender=F, firstName=Cristine, lastName=Maria)
Employee(id=9, age=15, gender=F, firstName=Neetu, lastName=Singh)
Employee(id=2, age=13, gender=F, firstName=Martina, lastName=Hengis)
Process finished with exit code 0
8.函数式接口Comparator
含义
所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口。
二、函数式接口的特点
- 接口有且仅有一个抽象方法,如上图的抽象方法compare
- 允许定义静态非抽象方法
- 允许定义默认defalut非抽象方法(default方法也是java8才有的,见下文)
- 允许java.lang.Object中的public方法,如上图的方法equals。
- FunctionInterface注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
甚至可以说:函数式接口是专门为lambda表达式准备的,lambda表达式是只实现接口中唯一的抽象方法的匿名实现类。
三、default关键字
顺便讲一下default关键字,在java8之前
接口是不能有方法的实现,所有方法全都是抽象方法
实现接口就必须实现接口里面的所有方法
这就导致一个问题:当一个接口有很多的实现类的时候,修改这个接口就变成了一个非常麻烦的事,需要修改这个接口的所有实现类。
这个问题困扰了java工程师许久,不过在java8中这个问题得到了解决,没错就是default方法
default方法可以有自己的默认实现,即有方法体。
接口实现类可以不去实现default方法,并且可以使用default方法。
四、JDK中的函数式接口举例
java.lang.Runnable,
java.util.Comparator,
java.util.concurrent.Callable
java.util.function包下的接口,如Consumer、Predicate、Supplier等
五、自定义Comparator排序
//8、自定义Comparator排序
/* employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee em1, Employee em2) {
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? -1:1;
}
});*/
//简化
employees.sort((em1,em2) -> {
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? -1:1;
});
employees.forEach(System.out::println);
9.Stream查找与匹配元素
问题
在我们对数组或者集合类进行操作的时候,经常会遇到这样的需求,比如:
是否包含某一个“匹配规则”的元素
是否所有的元素都符合某一个“匹配规则”
是否所有元素都不符合某一个“匹配规则”
查找第一个符合“匹配规则”的元素
查找任意一个符合“匹配规则”的元素
例子
import model.Employee;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class Matvhfind {
public static void main(String[] args) {
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
//不用Stream API实现,查找员工列表中是否包含年龄大于70的员工
boolean isExistAgeThan70 = false;
for(Employee employee:employees){
if(employee.getAge() > 70){
isExistAgeThan70 = true;
break;
}
}
System.out.println(isExistAgeThan70);
//1、第一个匹配规则函数:anyMatch,判断Stream流中是否 包含 某一个“匹配规则”的元素。
// 这个匹配规则可以是lambda表达式或者谓词。
//使用Stream API
boolean isExistAgeThan702 = employees.stream().anyMatch(Employee.ageGreaterThan70);
System.out.println(isExistAgeThan702);
//将谓词逻辑换成lambda表达式
boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 72);
System.out.println(isExistAgeThan72);
//2.1、allMatch匹配规则函数:判断是够Stream流中的 所有元素都 符合某一个"匹配规则"。
//是否所有员工的年龄都大于10岁
boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10);
System.out.println(isExistAgeThan10);
//2.2、noneMatch匹配规则函数:判断是否Stream流中的 所有元素都不 符合某一个"匹配规则"。
//是否不存在小于18岁的员工
boolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);
System.out.println(isExistAgeLess18);
/**
* 3、元素查找与Optional
* Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。
*
* isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。
* ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。我们在第3章
* 介绍了 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda
* 表达式。
* T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。
* T orElse(T other) 会在值存在时返回值,否则返回一个默认值。
* 关于Optinal的各种函数用法请观看视频!B站观看地址
*
* findFirst用于查找第一个符合“匹配规则”的元素,返回值为Optional
* findAny用于查找任意一个符合“匹配规则”的元素,返回值为Optional
*/
//从列表中按照顺序查找第一个年龄大于40的员工
Optional<Employee> employeeOptional
= employees.stream().filter(e -> e.getAge() > 40).findFirst();
System.out.println(employeeOptional.get());
//isPresent是否存在
boolean is
= employees.stream().filter(e -> e.getAge() > 40).findFirst().isPresent();
System.out.println(is);
//ifPresent如果存在
employees.stream().filter(e -> e.getAge() > 40).findFirst().ifPresent(
e -> System.out.println(e)
);
//orElse不存在给默认值
Employee employeeOptionalOrElse =
employees.stream().filter(e -> e.getAge() > 90).findFirst().orElse(
new Employee(0,0,"F","","")
);
System.out.println(employeeOptionalOrElse);
//findAny 找任意一个
Employee employeeOptionalOrElseFindAny =
employees.stream().filter(e -> e.getAge() > 90).findAny().orElse(
new Employee(0,0,"F","","")
);
System.out.println(employeeOptionalOrElseFindAny);
}
}
结果
true
true
true
true
false
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
true
Employee(id=3, age=43, gender=M, firstName=Ricky, lastName=Martin)
Employee(id=0, age=0, gender=F, firstName=, lastName=)
Employee(id=0, age=0, gender=F, firstName=, lastName=)
Process finished with exit code 0
10.Stream集合元素归约
Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:
- Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
- Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
- Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
注意观察上面的图,我们先来理解累加器:
- 阶段累加结果作为累加器的第一个参数
- 集合遍历元素作为累加器的第二个参数
Integer类型归约
String类型归约
复杂对象归约
Combiner合并器的使用
对于大数据量的集合元素归约计算,更能体现出Stream并行流计算的威力。
例子
import model.Employee;
import java.util.Arrays;
import java.util.List;
/**
* Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:
*
* Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
* Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
* Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
* 理解累加器:
* 阶段累加结果作为累加器的第一个参数
* 集合遍历元素作为累加器的第二个参数
*/
public class MatchFindDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
//1、Integer类型归约
//reduce初始值为0,累加器可以是lambda表达式,也可以是方法引用。
int result = numbers
.stream()
.reduce(0, (subtotal, element) -> subtotal + element);
System.out.println(result); //21
int result12 = numbers
.stream()
.reduce(0, Integer::sum);
System.out.println(result12); //21
//2、String类型归约
//不仅可以归约Integer类型,只要累加器参数类型能够匹配,可以对任何类型的集合进行归约计算。
List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
String result21 = letters
.stream()
.reduce("", (partialString, element) -> partialString + element);
System.out.println(result21); //abcde
String result22 = letters
.stream()
.reduce("", String::concat);
System.out.println(result22); //ancde
//3、复杂对象归约
//计算所有的员工的年龄总和。
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
//先用map将Stream流中的元素由Employee类型处理为Integer类型(age)。
//然后对Stream流中的Integer类型进行归约
Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum);
System.out.println(total); //346
//4、Combiner合并器的使用
/*
* 除了使用map函数实现类型转换后的集合归约,我们还可以用Combiner合并器来实现,这里第一次使用到了Combiner合并器。
* 因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。
* 这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换
* */
Integer total3 = employees.stream()
.reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); //注意这里reduce方法有三个参数
System.out.println(total3); //346
//5、*并行流数据归约(使用合并器)
//在进行并行流计算的时候,可能会将集合元素分成多个组计算。为了更快的将分组计算结果累加,可以使用合并器。
Integer total2 = employees
.parallelStream()
.map(Employee::getAge)
.reduce(0,Integer::sum,Integer::sum); //注意这里reduce方法有三个参数即合并器
System.out.println(total2); //346
}
}
结果
21
21
abcde
abcde
346
346
346
Process finished with exit code 0