目录
一、Lambda表达式
1、简介
Lambda表达式也可称为闭包,是推动 Java 8 发布的最重要新特性。lambda表达式本质上是一个匿名方法。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据。使用 Lambda 表达式可以使代码变的更加简洁紧凑。在最简单的形式中,一个lambda可以由:用逗号分隔的参数列表、–>符号、函数体三部分表示,在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。
如何使现有的函数友好地支持lambda。最终采取的方法是:增加函数式接口的概念。函数式接口就是接口里面必须有且只有一个抽象方法的普通接口,像这样的接口可以被隐式转换为lambda表达式成为函数式接口。 在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。 任何函数式接口都可以使用lambda表达式替换,例如:ActionListener、Comparator、Runnable。
函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface,但是默认方法与静态方法并不影响函数式接口的契约,可以任意使用。
2、语法
Java8中引入了一个新的操作符 "->" 该操作符称为箭头操作符或Lambda 操作符
箭头操作符将Lambda表达式拆分成两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所需要执行的功能 即Lambda体
语法格式:
- 无参数,无返回值
() -> System.out.println("hello");
- 有一个参数,并且无返回值
(x) -> System.out.println(x);
- 若只有一个参数,小括号可以省略不写
x -> System.out.println(x);
- 有两个以上的参数,有返回值,并且Lambda体中有多条语句
Comparator<Integer> com = (x, y) -> { System.out.println("函数式接口"); return Integer.compare(x, y); };
- 若Lambda体中只有一条语句,return 和大括号就可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x,y);
- Lambda表达式的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即"类型推断"
Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x,y);
语法小窍门:
- 左右遇一括号省
- 左侧推断类型省
3、案例
使用Lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。通过() -> {}代码块替代了整个匿名类。
package com.wts.mybatisplus.jdk8.Lambda;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.*;
@SpringBootTest
public class TestLambda {
/**
* 原来的匿名内部类
*/
@Test
public void Test() {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
/**
* Lambda表达式
*/
@Test
public void test2() {
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
/*============================================================*/
List<Employee> employees = Arrays.asList(
new Employee("wts1", 21, 22222.2),
new Employee("wts2", 22, 32222.2),
new Employee("wts3", 23, 42222.2),
new Employee("wts4", 24, 52222.2)
);
/**
* 需求:获取当前公司中员工年龄大于35的员工信息
*/
@Test
public void test3() {
List<Employee> employees = filterEmployee1(this.employees);
for (Employee emp : employees) {
System.out.println(emp);
}
}
public List<Employee> filterEmployee1(List<Employee> list) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : emps) {
if (emp.getAge() >= 22) {
emps.add(emp);
}
}
return emps;
}
/**
* 需求:获取当前公司中员工年工资大于5000
*
* @param list
* @return
*/
public List<Employee> filterEmployee2(List<Employee> list) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : emps) {
if (emp.getSalary() < 5000) {
emps.add(emp);
}
}
return emps;
}
/**
* 优化方式一:策略设计模式
*/
@Test
public void test4() {
List<Employee> employees = filterEmployee3(this.employees, new FilterEmployeeByAge());
for (Employee employee : employees) {
System.out.println(employee);
}
List<Employee> employees1 = filterEmployee3(this.employees, new FilterEmployeeBySalary());
for (Employee employee1 : employees1) {
System.out.println(employee1);
}
}
public List<Employee> filterEmployee3(List<Employee> employees, MyPredicate<Employee> mp) {
List<Employee> emps = new ArrayList<>();
for (Employee employee : employees) {
if (mp.test(employee)) {
emps.add(employee);
}
}
return emps;
}
/**
* 优化方式二:匿名内部类
*/
@Test
public void test5() {
List<Employee> employees = filterEmployee3(this.employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 3000;
}
});
for (Employee employee : employees) {
System.out.println(employee);
}
}
/**
* 优化方式三:Lambda表达式
*/
@Test
public void test6() {
List<Employee> list = filterEmployee3(employees, (e) -> e.getSalary() > 3000);
list.forEach(System.out::println);
}
/**
* 优化方式四:Stream API
*/
@Test
public void test7() {
employees.stream().filter((e) -> e.getSalary() > 3000)
.limit(2)
.forEach(System.out::println);
System.out.println("-----------------------------------------");
employees.stream().map(Employee::getName)
.forEach(System.out::println);
}
}
package com.wts.mybatisplus.jdk8.Lambda;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.junit.Test;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Employee {
private String name;
private Integer age;
private double salary;
}
package com.wts.mybatisplus.jdk8.Lambda;
public interface MyPredicate<T> {
public boolean test(T t);
}
package com.wts.mybatisplus.jdk8.Lambda;
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 35;
}
}
package com.wts.mybatisplus.jdk8.Lambda;
public class FilterEmployeeBySalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee employee) {
return employee.getSalary() >3000;
}
}
二、函数式接口
函数式接口(Functional Interface):有且只有一个抽象方法的接口,称之为函数式接口。但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式,如创建线程 实现的 Runnable 接口
1、语法:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
2、特征:
- 接口中标注了@FunctionalInterface注解
- 作用:可以检测接口是否是一个函数式接口 是:编译成功 否:编译失败(接口中没有抽象方法,接口中的抽象方法个数多余1个)
- 接口中只有一个抽象方法会被编译器自动认识成函数式接口
3、Java内置的四大核心函数式接口
其实lambda离不开函数式接口,我们来看下JDK8常用的几个函数式接口:
-
Function<T, R>
(转换型): 接受一个输入参数,返回一个结果 -
Consumer<T>
(消费型): 接收一个输入参数,并且无返回操作 -
Predicate<T>
(判断型): 接收一个输入参数,并且返回布尔值结果 -
Supplier<T>
(供给型): 无参数,返回结果
案例一
1、Function<T, R>
是一个功能转换型的接口,可以把将一种类型的数据转化为另外一种类型的数据
// 常用函数式接口
@Test
void functionTest() {
//获取每个字符串的长度,并且返回
Function<String, Integer> function = String::length;
Stream<String> stream = Stream.of("程序员田螺", "捡田螺的小男孩", "捡瓶子的小男孩");
Stream<Integer> resultStream = stream.map(function);
resultStream.forEach(System.out::println);
}
2、Consumer<T>
是一个消费性接口,通过传入参数,并且无返回的操作
// Consumer<T>是一个消费性接口,通过传入参数,并且无返回的操作
@Test
void consumerTest() {
Consumer<String> consumer = System.out::println;
Stream<String> stream = Stream.of("程序员田螺", "捡田螺的小男孩", "捡瓶子的小男孩");
stream.forEach(consumer);
}
3、Predicate<T>
是一个判断型接口,并且返回布尔值结果.
// Predicate<T>是一个判断型接口,并且返回布尔值结果.
@Test
void testPredicate() {
//获取每个字符串的长度,并且返回
Predicate<Integer> predicate = a -> a > 18;
UserInfo userInfo = new UserInfo(2L, "程序员田螺", 27);
System.out.println(predicate.test(userInfo.getAge()));
}
4、Supplier<T>
是一个供给型接口,无参数,有返回结果。
// Supplier<T>是一个供给型接口,无参数,有返回结果。
@Test
void testSupplier() {
Supplier<Integer> supplier = () -> Integer.valueOf("666");
System.out.println(supplier.get());
}
这几个函数在日常开发中,也是可以灵活应用的,比如我们DAO操作完数据库,是会有个result的整型结果返回。我们就可以用Supplier<T>
来统一判断是否操作成功。如下:
private void saveDb(Supplier<Integer> supplier) {
if (supplier.get() > 0) {
System.out.println("插入数据库成功");
}else{
System.out.println("插入数据库失败");
}
}
@Test
public void add() throws Exception {
Course course=new Course();
course.setCname("java");
course.setUserId(100L);
course.setCstatus("Normal");
saveDb(() -> courseMapper.insert(course));
}
案例二
@SpringBootTest
public class TestLambda4 {
//Predicate<T> : 断言型接口
@Test
public void test4(){
List<String> list = Arrays.asList("hello","atguigu","Lambda","www","ok0");
List<String> strings = filterStr(list, (s) -> s.length() > 3);
for (String str : strings) {
System.out.println(str);
}
}
//需求:将满足条件的字符串放到集合当中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strlist = new ArrayList<>();
for (String str: strlist) {
if (pre.test(str)){
strlist.add(str);
}
}
}
@Test
public void test3(){
String newStr = strHandler("\t\t\t 我是哈哈哈 ", (str) -> str.trim());
System.out.println(newStr);
}
//Function<T,R> :函数型接口
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
//Supplier<T>:供给型接口
@Test
public void test02(){
getNumList(10,() -> (int)(Math.random() * 100));
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer e = sup.get();
list.add(e);
}
return list;
}
//Consumer<T> 消费型接口
@Test
public void test01(){
happy(10000, (m) -> System.out.println("大哥们每次消费:"+ m +"元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
}
三、方法引用与构造器引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
1、方法引用
若Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用"。(可以理解为方法引用是Lambda 表达式的另一种表现形式)
主要有三种语法格式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
Lambda体中调用方法的参数列表与返回值类型,要与函数式接口
Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
2、构造器引用
格式:
- ClassName::new
注意:需要调研的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
3、数组数组引用
type::new
4、案例
定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数
cars.forEach( Car::collide );
第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。
cars.forEach( Car::repair );
第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
public class TestMethodRef {
//1、对象::实例方法名
@Test
public void test01(){
Consumer<String> con = (x) -> System.out.println(x);
PrintStream ps = System.out;
Consumer<String> con1 = ps::println;
Consumer<String> con2 = System.out::println;
con2.accept("abcdef");
}
@Test
public void test02(){
Employee employee =new Employee();
Supplier<String> sup = () -> employee.getName();
String str = sup.get();
System.out.println(str);
Supplier<Integer> sup2 = employee::getAge;
Integer num = sup2.get();
System.out.println(num);
}
//2、类::静态方法名
@Test
public void test03(){
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
}
//类:静态方法名
public void test04(){
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
}
@Test
public void test05(){
Supplier<Employee> sup = () -> new Employee();
sup.get();
//构造器引用方式
Supplier<Employee> sup2 = Employee::new;
Employee employee = sup2.get();
System.out.println(employee);
}
@Test
public void test06(){
Function<Integer,Employee> fun2 = Employee::new;
Employee employee = fun2.apply(23);
System.out.println(employee);
}
//数组引用
public void test07(){
Function<Integer,String[]> fun = (x) -> new String[x];
String[] strs = fun.apply(10);
System.out.println(strs.length);
Function<Integer,String[]> fun2 = String[]::new;
String[] strs2 = fun2.apply(20);
System.out.println(strs2.length);
}
}
四、Stream API
1、简介
Java 8 API添加了一个新的抽象称为流Stream把真正的函数式编程风格引入到Java中,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API极大简化了集合框架的处理,这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
2、Stream流特性
- Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
- 这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
- Stream不保存数据,故每个Stream流只能使用一次。
所以这边有两个概念:流、管道。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。这里有2个操作:中间操作、最终操作。
- 中间操作:返回结果都是Stream,故可以多个中间操作叠加。
- 终止操作:用于返回我们最终需要的数据,只能有一个终止操作。
使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以更加专注于业务逻辑。默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。
3、Stream的操作步骤
- 创建Stream
- 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换)
- 对Stream进行聚合操作,获取想要的结果
4、流的生成方法
- Collection接口的stream()或parallelStream()方法
- 静态的Stream.of()、Stream.empty()方法
- Arrays.stream(array, from, to)
- 静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
- 静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
- Pattern接口的splitAsStream(input)方法
- 静态的Files.lines(path)、Files.lines(path, charSet)方法
- 静态的Stream.concat()方法将两个流连接起来
5、流的Intermediate方法(中间操作)
- filter(Predicate) :将结果为false的元素过滤掉
- map(fun) :转换元素的值,可以用方法引元或者lambda表达式
- flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换
- limit(n) :保留前n个元素
- skip(n) :跳过前n个元素
- distinct() :剔除重复元素
- sorted(Comparator) :将流元素按Comparator排序
- peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试
6、流的Terminal方法(终结操作)
6.1、约简操作
- reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
- reduce(a, fun) :a为幺元值,作为累积器的起点
- reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并
6.2、收集操作
- iterator():
- forEach(fun):
- forEachOrdered(fun) :可以应用在并行流上以保持元素顺序
- toArray():
- toArray(T[] :: new) :返回正确的元素类型
- collect(Collector):
- collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
6.3、查找与收集操作
- max(Comparator):返回流中最大值
- min(Comparator):返回流中最小值
- count():返回流中元素个数
- findFirst() :返回第一个元素
- findAny() :返回任意元素
- anyMatch(Predicate) :任意元素匹配时返回true
- allMatch(Predicate) :所有元素匹配时返回true
- noneMatch(Predicate) :没有元素匹配时返回true
7、案例
7.1、创建Stream
public class TestStream01 {
//创建stream
@Test
public void test01(){
//1、可以通过Collection 系列集合提供的stream() 或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2、通过Arrays中的静态方法stream() 获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
//3、通过Stream 类中的静态方法的of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
//4、创建无线流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10).forEach(System.out::println);
//生成
Stream.generate(() -> Math.random())
.forEach(System.out::println);
}
}
7.2、中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则,中间操作不会执行任何的处理!而在终止操作是一次性全部处理,称为“惰性求值”;
筛选与切片
filter---接收Lambda,从流中排除某些元素
limit---截断流,使其元素不超过给定数量
skip(n)---跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
distinct---筛选,通过流所生成元素的hasCode() 和 equals()去除重复元素
7.2.1、映射
map--接收Lambda, 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatmap--接收一个函数作为参数,将流中的每个值换成另一个流,然后把所有的流连接成一个流。
@Test
public void test06(){
List<String> list = Arrays.asList("aaa","bbb","cccc","dddd");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
System.out.println("-----------------------------------------");
employees.stream()
.map(Employee::getAge)
.forEach(System.out::println);
System.out.println("-----------------------------------------");
Stream<Stream<Character>> stream = list.stream()
.map(TestStreamAPI02::filterCharacter);//{{a,a,a},{b,b,b}}
stream.forEach((sm) ->{
sm.forEach(System.out::println);
});
System.out.println("-----------------------------------------");
Stream<Character> characterStream = list.stream()
.flatMap(TestStreamAPI02::filterCharacter);//{a,a,a,b,b,b}
characterStream.forEach(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();
}
7.2.2、排序
sorted()-自然排序
sorted(Comparator com) --定制排序
@Test
public void test07(){
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);
}
7.3、终止操作(终端操作)
7.3.1、查找与匹配
allMatch--检查是都匹配所有元素
anyMatch--检查是否至少匹配一个元素
noneMatch--检查是否没有匹配所有元素
findFrist--返回第一个元素
findAny--返回当前流中的任意元素
count--返回流中元素的总个数
max--返回流中最大值
min--返回流中最小值
List<Employee> employees = Arrays.asList(
new Employee("wts1",21,22222.2, Employee.Status.BUSY),
new Employee("wts2",22,32222.2, Employee.Status.FREE),
new Employee("wts3",23,42222.2, Employee.Status.VOCATION),
new Employee("wts4",24,52222.2,Employee.Status.VOCATION)
);
@Test
public void test01(){
boolean b = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b);
boolean b1 = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
boolean b2 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(b2);
boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.VOCATION));
System.out.println(b3);
Optional<Employee> first = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(first.get());
Optional<Employee> any = employees.stream()
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(any);
}
@Test
public void test02(){
long count = employees.stream().count();
System.out.println(count);
Optional<Employee> max = employees.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(min.get());
}
7.3.2、归约
reduce(T identity , BinaryOperator)/reduce(BinaryOperator) ---可以将流中元素反复结合起来,得到一个值。
@Test
public void test03(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("----------------------------------------");
Optional<Double> op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
}
7.3.3、收集
collect---将流转化为其他形式。接收一个Collector接口的实现,用于给Stream中元素汇总的方法。
@Test
public void test04(){
List<String> collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
System.out.println("----------------------------------");
Set<String> collect1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
collect1.forEach(System.out::println);
System.out.println("---------------------------------");
HashSet<String> collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
collect2.forEach(System.out::println);
}
@Test
public void test05(){
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count);
System.out.println("------------------------------------");
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//总和
DoubleSummaryStatistics sum = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(sum);
//最大值
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
//最小值
Optional<Double> collect = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(collect.get());
}
7.3.4、分组
//分组
@Test
public void test06(){
Map<Employee.Status, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
}
//多级分组
@Test
public void test07(){
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() <= 50) {
return "老年";
} else {
return "老年";
}
})));
System.out.println(map);
}
7.3.5、分区
@Test
public void test08(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
System.out.println(map);
}
@Test
public void test09(){
DoubleSummaryStatistics collect = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(collect.getMax());
System.out.println(collect.getAverage());
System.out.println(collect.getMin());
System.out.println(collect.getSum());
}
@Test
public void test10(){
String collect = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",","==","=="));
System.out.println(collect);
}
五、并行流与顺序流
并行流:就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据流的流.
Java8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。stream API 可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换
package com.wts.mybatisplus.jdk8.forkjoin;
import java.util.concurrent.RecursiveTask;
public class ForkJoinCalculate extends RecursiveTask<Long> {
private long start;
private long end;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
private static final long THRESHOLD = 10000;
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD){
long sum = 0;
for (long i = start; i<= end; i++){
sum += i;
}
return sum;
}else {
long middle = (start + end)/2;
ForkJoinCalculate left = new ForkJoinCalculate(start,middle);
left.fork();//拆分子任务,同时压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle+1,end);
right.fork();
return left.join() + right.join();
}
}
}
package com.wts.mybatisplus.jdk8.forkjoin;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class TestForkJoin {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinCalculate(0,100000000L);
Long invoke = pool.invoke(task);
System.out.println(invoke);
Instant end = Instant.now();
System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());
}
//普通
@Test
public void test02(){
Instant start = Instant.now();
long sum = 0L;
for (long i = 0; i <= 100000000L; i++){
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());
}
//java8并行流
@Test
public void test3(){
Instant start = Instant.now();
LongStream.rangeClosed(0,100000000L)
.parallel()
.reduce(0,Long::sum);
Instant end = Instant.now();
System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());
}
}
六、Optional类
Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或者不存在,原来null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常
常用方法:
- Optional.of(T t): 创建一个Optional实例 * Optional.empty(): 创建一个空的Optional实例
- Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
- isPresent(): 判断是否包含值 * orElse(T t):如果调用对象包含值,返回该值,否则返回t
- orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s的获取值
- map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
- flatMap(Function mapper):与map 类似,要求返回值必须是Optional
1、创建optional对象,一般用ofNullable()而不用of():
(1)empty() :用于创建一个没有值的Optional对象:Optional<String> emptyOpt = Optional.empty();
(2)of() :使用一个非空的值创建Optional对象:Optional<String> notNullOpt = Optional.of(str);
(3)ofNullable() :接收一个可以为null的值:Optional<String> nullableOpt = Optional.ofNullable(str);
2、判断Null:
(1)isPresent():如果创建的对象实例为非空值的话,isPresent()返回true,调用get()方法会返回该对象,如果没有值,调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。
3、获取对象:
(1)get()
4、使用map提取对象的值,如果我们要获取User对象中的roleId属性值,常见的方式是先判断是否为null然后直接获取,但使用Optional中提供的map()方法可以以更简单的方式实现
5、使用orElse方法设置默认值,Optional类还包含其他方法用于获取值,这些方法分别为:
(1)orElse():如果有值就返回,否则返回一个给定的值作为默认值;
(2)orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;
(3)orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。
6、使用filter()方法过滤,filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤,在代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。
@SpringBootTest
public class TestOptional {
@Test
public void test1(){
Optional<Employee> op = Optional.of(new Employee());
System.out.println(op.get());
}
@Test
public void test2(){
Optional<Employee> op = Optional.empty();
System.out.println(op.get());
}
@Test
public void test3(){
Optional<Employee> op = Optional.ofNullable(new Employee());
/*if (op.isPresent()){
System.out.println(op.get());
}*/
Employee emp = op.orElse( new Employee("wts1",21,22222.2));
System.out.println(emp);
System.out.println("--------------------------");
Employee emp1 = op.orElseGet(() -> new Employee());
System.out.println(emp1);
}
@Test
public void test04(){
Optional<Employee> op = Optional.ofNullable(new Employee("wts1",21,22222.2));
Optional<String> s = op.map((e) -> e.getName());
System.out.println(s.get());
System.out.println("---------------------------");
Optional<String> s1 = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(s1.get());
}
}
七、接口中的默认方法与静态方法
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求,就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个default关键字即可实现默认方法。为什么要有这个特性?以前当需要修改接口的时候,需要修改全部实现该接口的类。而引进的默认方法的目的是为了解决接口的修改与现有的实现不兼容的问题。
public interface MyFun {
default String getName(){
return "哈哈哈";
}
public static void show(){
System.out.println("接口中的静态方法");
}
}
当出现这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,这种情况的解决方法:
- 是创建自己的默认方法,来覆盖重写接口的默认方法
- 可以使用 super 来调用指定接口的默认方法
java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。
八、新时间日期API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,比如:
- 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
- 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
- Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
package com.wts.mybatisplus.jdk8.date;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.xml.bind.SchemaOutputResolver;
import java.time.*;
import java.util.function.DoubleToIntFunction;
@SpringBootTest
public class TestLocalDateTime {
//1、LocalDate 2、LocalTime 3、LocalDateTime
@Test
public void test(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
LocalDateTime ldt2 = LocalDateTime.of(2019,10,19,13,22,33);
System.out.println(ldt2);
LocalDateTime ldt3 = ldt.plusYears(2);
System.out.println(ldt3);
LocalDateTime ldt4 = ldt.minusMonths(2);
System.out.println(ldt4);
System.out.println(ldt.getYear());
System.out.println(ldt.getMonth());
System.out.println(ldt.getDayOfMonth());
System.out.println(ldt.getHour());
System.out.println(ldt.getMinute());
System.out.println(ldt.getSecond());
}
//2、Instant:时间戳(以Unix元年 1970年1月1日00:00:00到某个时间之间的毫秒值)
@Test
public void test2(){
Instant now = Instant.now();//默认获取UTC时区
System.out.println(now);
OffsetDateTime time = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(time);
}
//3、Duration : 计算两个“时间”之间的间隔
//Period: 计算两个日期之间的间隔
@Test
public void test3(){
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println(duration.toMillis());
System.out.println("------------------------");
LocalTime lt1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalTime lt2 = LocalTime.now();
Duration duration1 = Duration.between(ins1, ins2);
System.out.println(duration1.toMillis());
}
}
九、注解相关
1、可以进行重复注解
自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。
重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。
2、扩展注解的支持
Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。
十、Collectors类常用的静态工厂方法
工厂方法 | 返回类型 | 用于 | 示例 |
---|---|---|---|
toList | List<T> | 把流中所有项目收集到一个List | List<Dish> dishes = menuStream. collect(toList()); |
toSet | Set<T> | 把流中所有项目收集到一个Set,删除重复项 | Set<Dish> dishes = menuStream. collect (toSet()); |
toCollection | Collection<T> | 把流中所有项目收集到给定的供应源创建的集合 | Collection<Dish> dishes = menuStream.collect (toCollection() ,ArrayList::new); |
counting | Long | 计算流中元素的个数 | long howManyDishes = menuStream.collect (counting()); |
summingInt | Integer | 对流中项目的一个整数属性求和 | int totalCalories =menuStream.collect(summingInt (Dish::getCalories) ); |
averagingInt | Double | 计算流中项目Integer属性的平均值 | double avgCalories =menuStream.collect(averagingInt(Dish::getCalories)); |
summarizingInt | IntSummaryStatistics | 收集美于流中項目Integer 属性的統竍値,例如最大、最小、总和与平均値 | IntSummaryStatistics menuStatistics =menuStream.collect(summarizingInt(Dish::getCalories)); |
joining | String | 连接对流中毎个項目调用toString方法所生成的字符串 | String shortMenu =menuStream.map(Dish::getName).collect (joining(", ")); |
maxBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最大元素的Optional,或如果流为空则为optional.empty() | Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories))); |
minBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最小元素的Optional,或如果流为空则为optional.empty() | Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories))); |
reducing | 归约操作产生的类型 | 从一个作为累加器的初始値幵始,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值 | int totalCalories =menuStream.collect(reducing(0,Dish::getCalories, Integer::sum)); |
collectingAndThen | 转换函数的类型 | 包裹另一个收集器,对其结果应用转换函数 | int howManyDishes =menuStream.collect(collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List<T>> | 根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果Map的键 | Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType)); |
mapping | 是一个收集器,可以传入两个函数, 一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来 ,目的是在累加之前对每个元素应用一个映射函数 | Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> { if (dish.getCalories() <=400) return CaloricLevel.DIET;else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;else return CaloricLevel.FAT; },toSet() ))); | |
partitioningBy | Map<Boolean, List<T> > | 相据对流由每个顶日应田谓词的结里求对顶日井行分区 | Map<Boolean, List<Dish>> vegetarianDishes =menuStream. collect (partitioningBy (Dish::isVegetarian) ) ; |
十一、Stream流常用的操作案例
1、list转map
在实际项目中我们经常会用到 List 转 Map 操作,在过去我们可能使用的是 for 循环遍历的方式。现在Java8 中新增了 Stream 特性,使得我们在处理集合操作时更方便了。
Collectors.toMap三个重载方法:
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);
参数含义分别是:
-
keyMapper:Key 的映射函数
-
valueMapper:Value 的映射函数
-
mergeFunction:当 Key 冲突时,调用的合并方法
-
mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
案例
先定义类:
// 简单对象
@Accessors(chain = true) // 链式方法
@lombok.Data
class User {
private String id;
private String name;
}
然后有这样一个 List:
List<User> userList = Lists.newArrayList(
new User().setId("A").setName("张三"),
new User().setId("B").setName("李四"),
new User().setId("C").setName("王五")
);
我们希望转成 Map 的格式为:
A-> 张三
B-> 李四
C-> 王五
过去的做法(循环):
Map<String, String> map = new HashMap<>();
for (User user : userList) {
map.put(user.getId(), user.getName());
}
我们使用Java8 中Stream 特性编写
1.1.1、方法一
以上述例子为例,我们可以一句话搞定:
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
当然,如果希望得到 Map 的 value 为对象本身时,可以这样写:
userList.stream().collect(Collectors.toMap(User::getId, t -> t));
或:
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
Function.identity()的含义
Java 8允许在接口中加入具体方法。接口中的具体方法有两种,default方法和static方法,identity()就是Function接口的一个静态方法。
Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t
形式的Lambda表达式
1.1.2、方法二
还是用上面的例子,如果 List 中 userId 有相同的,使用上面的写法会抛异常:
List<User> userList = Lists.newArrayList(
new User().setId("A").setName("张三"),
new User().setId("A").setName("李四"), // Key 相同
new User().setId("C").setName("王五")
);
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
// 异常:
java.lang.IllegalStateException: Duplicate key 张三
at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1245)
at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Test.toMap(Test.java:17)
...
这时就需要调用第二个重载方法,传入合并函数,如:
userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2));
// 输出结果:
A-> 张三李四
C-> 王五
1.1.3、方法三
第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序的,可以使用如下写法:
List<User> userList = Lists.newArrayList(
new User().setId("B").setName("张三"),
new User().setId("A").setName("李四"),
new User().setId("C").setName("王五")
);
userList.stream().collect(
Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new)
);
// 输出结果:
A-> 李四
B-> 张三
C-> 王五
类似的,还有Collectors.toList()
、Collectors.toSet()
,表示把对应的流转化为list
或者Set
。
2、filter()过滤
从数组集合中,过滤掉不符合条件的元素,留下符合条件的元素。
// 2、filter过滤
@Test
void filterTest() {
List<UserInfo> userInfoList = new ArrayList<>();
userInfoList.add(new UserInfo(1L, "捡田螺的小男孩", 18));
userInfoList.add(new UserInfo(2L, "程序员田螺", 27));
userInfoList.add(new UserInfo(3L, "捡瓶子的小男孩", 26));
/**
* filter 过滤,留下超过18岁的用户
*/
List<UserInfo> userInfoResultList = userInfoList.stream().filter(user -> user.getAge() > 18).collect(Collectors.toList());
userInfoResultList.forEach(a -> System.out.println(a.getUserName()));
}
3、foreach遍历
foreach 遍历list,遍历map,真的很丝滑。
// 3、foreach遍历
@Test
void foreachTest() {
/**
* forEach 遍历集合List列表
*/
List<String> userNameList = Arrays.asList("捡田螺的小男孩", "程序员田螺", "捡瓶子的小男孩");
userNameList.forEach(System.out::println);
System.out.println("===================");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("公众号", "捡田螺的小男孩");
hashMap.put("职业", "程序员田螺");
hashMap.put("昵称", "捡瓶子的小男孩");
/**
* forEach 遍历集合Map
*/
hashMap.forEach((k, v) -> System.out.println(k + ":\t" + v));
}
4、groupingBy分组
提到分组,相信大家都会想起SQL
的group by
。我们经常需要一个List做分组操作。比如,按城市分组用户。在Java8之前,是这么实现的:
// 4、groupingBy
@Test
void groupingByTest() {
List<UserInfo> originUserInfoList = new ArrayList<>();
originUserInfoList.add(new UserInfo(1L, "捡田螺的小男孩", 18, "深圳"));
originUserInfoList.add(new UserInfo(3L, "捡瓶子的小男孩", 26, "湛江"));
originUserInfoList.add(new UserInfo(2L, "程序员田螺", 27, "深圳"));
Map<String, List<UserInfo>> result = new HashMap<>();
for (UserInfo userInfo : originUserInfoList) {
String city = userInfo.getCity();
List<UserInfo> userInfos = result.get(city);
if (userInfos == null) {
userInfos = new ArrayList<>();
result.put(city, userInfos);
}
userInfos.add(userInfo);
}
result.forEach((k, v) -> System.out.println(k + ":\t" + v));
}
而使用Java8的groupingBy
分组器,清爽无比:
Map<String, List<UserInfo>> result = originUserInfoList.stream()
.collect(Collectors.groupingBy(UserInfo::getCity));
5、sorted+Comparator 排序
工作中,排序的需求比较多,使用sorted+Comparator
排序,真的很香。
// 5、sorted + Comparator 排序
@Test
void sorted() {
List<UserInfo> userInfoList = new ArrayList<>();
userInfoList.add(new UserInfo(1L, "捡田螺的小男孩", 18));
userInfoList.add(new UserInfo(3L, "捡瓶子的小男孩", 26));
userInfoList.add(new UserInfo(2L, "程序员田螺", 27));
/**
* sorted + Comparator.comparing 排序列表,
*/
userInfoList = userInfoList.stream().sorted(Comparator.comparing(UserInfo::getAge)).collect(Collectors.toList());
userInfoList.forEach(a -> System.out.println(a.toString()));
System.out.println("开始降序排序");
/**
* 如果想降序排序,则可以使用加reversed()
*/
userInfoList = userInfoList.stream().sorted(Comparator.comparing(UserInfo::getAge).reversed()).collect(Collectors.toList());
userInfoList.forEach(a -> System.out.println(a.toString()));
}
6、去重
6.1、distinct
可以去除重复的元素:
// 6.distinct去重
@Test
void distinctTest() {
List<String> list = Arrays.asList("A", "B", "F", "A", "C");
List<String> temp = list.stream().distinct().collect(Collectors.toList());
temp.forEach(System.out::println);
}
6.2、根据集合对象里的某个字段去重
import static java.util.Comparator.comparingLong;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
// 根据id去重
List<Person> unique = appleList.stream().collect(
collectingAndThen(
toCollection(() -> new TreeSet<>(comparingLong(Apple::getId))), ArrayList::new)
);
7、findFirst 返回第一个
7.1、返回集合的第一个元素
findFirst
很多业务场景,我们只需要返回集合的第一个元素即可:
// 7、findFirst
@Test
void findFirstTest(){
List<String> list = Arrays.asList("A", "B", "F", "A", "C");
list.stream().findFirst().ifPresent(System.out::println);
}
7.2、在集合中查询出第一个名称为苹果1的人
Person person = list.stream().filter(per -> "苹果1".equals(per.getName())).findFirst().orElse(null);
工作流程:
- filter为过滤,user代表userList中的一个User;
- Person person = list.stream().filter(per -> "苹果1".equals(per.getName()))表示过滤出list中名字为苹果1的person;
- findAny()表示将其中任意一个返回;【注意:在Java 8 Stream中, findFirst()返回Stream中的第一个元素,而findAny()返回Stream中的任何元素。】
- orElse(null)表示如果一个都没找到返回null。【orElse()中可以塞默认值。如果找不到就会返回orElse中你自己设置的默认值。】
8、anyMatch是否至少匹配一个元素
anyMatch
检查流是否包含至少一个满足给定谓词的元素。
// 8、anyMatch
@Test
void anyMatchTest() {
Stream<String> stream = Stream.of("A", "B", "C", "D");
boolean match = stream.anyMatch(s -> s.contains("C"));
System.out.println(match);
}
9、allMatch 匹配所有元素
allMatch
检查流是否所有都满足给定谓词的元素。
// 9、 allMatch 匹配所有元素
@Test
void allMatchTest() {
Stream<String> stream = Stream.of("A", "B", "C", "D");
boolean match = stream.allMatch(s -> s.contains("C"));
System.out.println(match);
}
10、map转换
map
方法可以帮我们做元素转换,比如一个元素所有字母转化为大写,又或者把获取一个元素对象的某个属性,demo
如下:
// 10、map转换
@Test
void mapConvert() {
List<String> list = Arrays.asList("jay", "tianluo");
//转化为大写
List<String> upperCaselist = list.stream().map(String::toUpperCase).collect(Collectors.toList());
upperCaselist.forEach(System.out::println);
}
11、Reduce
Reduce可以合并流的元素,并生成一个值
- T reduce(T identity, BinaryOperator accumulator);
- 该方法第一个参数表示默认值
- 在2的基础上加上 1,2,3,4,5
- 代码示例
@Test
void reduceTest() {
int sum = Stream.of(1, 2, 3, 4).reduce(2, (a, b) -> a + b);
System.out.println(sum);
}
12、peek 打印个日志
peek()
方法是一个中间Stream
操作,有时候我们可以使用peek
来打印日志。
// 12、peek 打印个日志
// peek()方法是一个中间Stream操作,有时候我们可以使用peek来打印日志。
@Test
void peekTest() {
List<String> result = Stream.of("程序员田螺", "捡田螺的小男孩", "捡瓶子的小男孩")
.filter(a -> a.contains("田螺"))
.peek(a -> System.out.println("公众号:" + a)).collect(Collectors.toList());
System.out.println(result);
}
13、Max,Min最大最小
使用lambda流求最大,最小值,非常方便。
// 13、Max,Min最大最小
@Test
void maxTest() {
List<UserInfo> userInfoList = new ArrayList<>();
userInfoList.add(new UserInfo(1L, "捡田螺的小男孩", 18));
userInfoList.add(new UserInfo(3L, "捡瓶子的小男孩", 26));
userInfoList.add(new UserInfo(2L, "程序员田螺", 27));
Optional<UserInfo> maxAgeUserInfoOpt = userInfoList.stream().max(Comparator.comparing(UserInfo::getAge));
maxAgeUserInfoOpt.ifPresent(userInfo -> System.out.println("max age user:" + userInfo));
Optional<UserInfo> minAgeUserInfoOpt = userInfoList.stream().min(Comparator.comparing(UserInfo::getAge));
minAgeUserInfoOpt.ifPresent(userInfo -> System.out.println("min age user:" + userInfo));
}
14、count统计
一般count()
表示获取流数据元素总数。
// 14、count统计
@Test
void countTest() {
List<UserInfo> userInfoList = new ArrayList<>();
userInfoList.add(new UserInfo(1L, "捡田螺的小男孩", 18));
userInfoList.add(new UserInfo(3L, "捡瓶子的小男孩", 26));
userInfoList.add(new UserInfo(2L, "程序员田螺", 27));
long count = userInfoList.stream().filter(user -> user.getAge() > 18).count();
System.out.println("大于18岁的用户:" + count);
}