【主要内容】
-
Lambda简介
-
Lambda语法
-
Lambda学用示例
-
Stream概述
-
Stream的基本使用
【学习目标】
知识点 | 要求 |
---|---|
Lambda简介 | 掌握 |
Lambda语法 | 掌握 |
Lambda常用示例 | 掌握 |
Stream概述 | 掌握 |
Stream的基本使用 | 掌握 |
1. Lambda表达式
1.1 Lambda简介
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
1.2 对接口的要求
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
1.3 @FunctionalInterface
修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
1.4 Lambda 基础语法
我们这里给出六个接口,后文的全部操作都利用这六个接口来进行阐述。
package com.huayu.lambda; public class LambdaInterface { } /** * 无参无返回值 */ @FunctionalInterface interface NoReturnNoParam { void method(); } /** * 一个参数无返回 */ @FunctionalInterface interface NoReturnOneParam { void method(int a); } /** * 多参数无返回 */ @FunctionalInterface interface NoReturnMultiParam { void method(int a, int b); } /** * 无参有返回 */ @FunctionalInterface interface ReturnNoParam { int method(); } /** * 一个参数有返回值 */ @FunctionalInterface interface ReturnOneParam { int method(int a); } /** * 多个参数有返回值 */ @FunctionalInterface interface ReturnMultiParam { int method(int a, int b); }
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
1.5 Lambda 基础语法测试
/* @Author xiangge @Date 2023/8/23 @Description Lambda表达式基础语法测试 */ public class LambdaDemo1 { public static void main(String[] args) { // 无参无返回值方法 NoReturnNoParam noReturnNoParam = () ->{ System.out.println("无参无返回值方法"); }; noReturnNoParam.method(); // 一个参数无返回值方法 NoReturnOneParam noReturnOneParam = (int a) ->{ System.out.println("一个参数无返回值"); }; noReturnOneParam.method(10); // 多个参数无返回值方法 NoReturnMultiParam noReturnMultiParam = (int a , int b) ->{ System.out.println("多个参数无返回值"); }; noReturnMultiParam.method(10,20); // 无参有返回值方法 ReturnNoParam returnNoParam = ()->{ return 100; }; System.out.println(returnNoParam.method()); // 一个参数有返回值方法 ReturnOneParam returnOneParam = (int a) ->{ return a; }; System.out.println(returnOneParam.method(200)); // 多个参数有返回值方法 ReturnMultiParam returnMultiParam = (int a,int b)->{ return a+b; }; System.out.println(returnMultiParam.method(100, 200)); } }
1.6 Lambda 语法简化测试
我们可以通过观察以下代码来完成代码的进一步简化,写出更加优雅的代码。
/* @Author xiangge @Date 2023/8/23 @Description Lambda表达式简化版测试 */ public class LambdaDemo2 { public static void main(String[] args) { // 无参无返回值方法 NoReturnNoParam noReturnNoParam = () -> System.out.println("无参无返回值方法"); noReturnNoParam.method(); // 一个参数无返回值方法 NoReturnOneParam noReturnOneParam = a -> System.out.println("一个参数无返回值"); noReturnOneParam.method(10); // 多个参数无返回值方法 NoReturnMultiParam noReturnMultiParam = ( a , b) -> System.out.println("多个参数无返回值"); noReturnMultiParam.method(10,20); // 无参有返回值方法 ReturnNoParam returnNoParam = ()-> 100; System.out.println(returnNoParam.method()); // 一个参数有返回值方法 ReturnOneParam returnOneParam = a -> a; System.out.println(returnOneParam.method(200)); // 多个参数有返回值方法 ReturnMultiParam returnMultiParam = (a,b)-> a+b; System.out.println(returnMultiParam.method(100, 200)); } }
1.7 Lambda 表达式常用示例
1.7.1 lambda 表达式引用方法
有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以利用 lambda表达式的接口快速指向一个已经被实现的方法。
语法:
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
代码如下所示:
/* @Author xiangge @Date 2023/8/23 @Description Lambda表达式方法的引用 类中静态方法的引用: 类名::静态方法名; 类中非静态方法的引用: 对象名::非静态方法名; */ public class LambdaDemo3 { public static void main(String[] args) { // 一个参数有返回值方法 ReturnOneParam returnOneParam = a -> a; System.out.println(returnOneParam.method(200)); // 引用LambdaDemo3中静态方法staticMethod的方法体 ReturnOneParam returnOneParam1 = LambdaDemo3::staticMethod; int method = returnOneParam1.method(10); System.out.println("method = " + method); // 引用LambdaDemo3中非静态方法method的方法体 LambdaDemo3 lambdaDemo3 = new LambdaDemo3(); ReturnOneParam returnOneParam2 = lambdaDemo3::method; int method1 = returnOneParam2.method(10); System.out.println("method1 = " + method1); } /* 定义静态方法 要求: 方法的参数列表和返回值类型必须和接口中被重写方法一致 */ public static int staticMethod(int a){ return a * 2; } /* 定义非静态方法 要求: 方法的参数列表和返回值类型必须和接口中被重写方法一致 */ public int method(int a){ return a + 2; } }
运行结果如图:
1.7.2 构造方法的引用
一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
/* @Author xiangge @Date 2023/8/23 @Description Lambda表达式构造方法的引用 说明: 使用接口作为对象生成器 */ public class LambdaDemo4 { public static void main(String[] args) { // 重写NoParamConstructorInter接口中的getStu() , 将 new Student()作为方法体 NoParamConstructorInter inter1 = () -> new Student(); Student stu = inter1.getStu(); System.out.println("stu = " + stu); // Lambda表达式构造方法的引用 NoParamConstructorInter inter2 = Student::new; // 调用getStu()没有传入任何参数,所以引用的是:Student类中的无参构造方法 Student stu1 = inter2.getStu(); System.out.println("stu1 = " + stu1); MulitParamConstructorInter inter3 = Student::new; // 调用getStu()传入三个参数,所以引用的是:Student类中的满参构造方法 Student stu2 = inter3.getStu("jack", 18, "上海"); System.out.println("stu2 = " + stu2); } } interface NoParamConstructorInter{ Student getStu(); } interface MulitParamConstructorInter{ Student getStu(String name , int age , String address); } class Student{ private String name; private int age; private String address; public Student() { } public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
1.7.3 lambda 表达式创建线程
我们以往都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。
/* @Author xiangge @Date 2023/8/23 @Description 使用Lambda创建线程 */ public class LambdaDemo5 { public static void main(String[] args) { // 1. 使用匿名内部类创建线程 Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("线程任务开始执行."); } },"线程A"); t1.start(); //2. 使用Lambda简化写法: 标准写法 Thread t2 = new Thread(()->{ System.out.println("线程任务开始执行."); }); t2.start(); //3. 使用Lambda简化写法: 简化写法写法 Thread t3 = new Thread(()-> System.out.println("线程任务开始执行.")); t3.start(); } }
1.7.4 遍历集合
我们可以调用集合的 public void forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。
代码如下:
/* @Author xiangge @Date 2023/8/23 @Description 使用Lambda表达式遍历集合 */ public class LambdaDemo6 { public static void main(String[] args) { List<Integer> list = Arrays.asList(11,12,13,14,15); // 使用Lambda表达式 list.forEach(item -> System.out.println(item)); System.out.println("------------------------------"); // 使用方法的引用:引用 System.out 中的println(Object x) list.forEach(System.out::println); } }
1.7.5 删除集合中的某个元素
我们通过public boolean removeIf(Predicate<? super E> filter)方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
代码如下:
/* @Author xiangge @Date 2023/8/23 @Description 使用Lambda表达式删除集合中某一个元素 */ public class LambdaDemo7 { public static void main(String[] args) { List<Student> list = new ArrayList<>(); list.add(new Student("zhangsan",18,"上海")); list.add(new Student("lisi",16,"北京")); list.add(new Student("wangwu",22,"南京")); list.add(new Student("zhaoliu",30,"武汉")); // 删除集合中年龄为16的学生 list.removeIf(item-> item.getAge() == 16); // 遍历集合 list.forEach(System.out::println); } }
1.7.6 集合内元素的排序
在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
代码如下:
/* @Author xiangge @Date 2023/8/23 @Description 使用Lambda表达式给集合中元素排序 */ public class LambdaDemo8 { public static void main(String[] args) { List<Student> list = new ArrayList<>(); list.add(new Student("zhangsan",18,"上海")); list.add(new Student("lisi",16,"北京")); list.add(new Student("wangwu",22,"南京")); list.add(new Student("zhaoliu",30,"武汉")); // 使用Lambda表达式给集合中元素排序 list.sort((item1,item2) -> item1.getAge() - item2.getAge()); // 遍历集合 list.forEach(System.out::println); } }
2. Stream处理集合
2.1 知识点概述
2.2 Stream概述
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
Stream可以由数组或集合创建,对流的操作分为两种:
-
中间操作,每次返回一个新的流,可以有多个
-
终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
-
stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
-
stream不会改变数据源,通常情况下会产生一个新的集合或一个值
-
stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行
2.3 Stream的创建
Stream可以通过集合数组创建。
1、通过 java.util.Collection.stream() 方法用集合创建流
2、使用java.util.Arrays.stream(T[] array)方法用数组创建流
3、使用Stream的静态方法:of()、iterate()、generate()
代码如下:
package com.huayu.stream; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; /* @Author xiangge @Date 2023/8/23 @Description Stream流创建的三种方式 Stream可以通过集合数组创建。 1、通过 java.util.Collection.stream() 方法用集合创建流 2、使用java.util.Arrays.stream(T[] array)方法用数组创建流 3、使用Stream的静态方法:of()、iterate()、generate() */ public class StreamDemo1 { public static void main(String[] args) { // 1. 通过单列集合创建Stream流 List<Integer> list = Arrays.asList(11,22,33,44,55,66,77); // 通过集合创建一个顺序流 Stream<Integer> stream1 = list.stream(); // 将流转换为List集合 List<Integer> collect1 = stream1.collect(Collectors.toList()); System.out.println("collect1 = " + collect1); System.out.println("-------------------------"); // 通过集合创建一个并行流 Stream<Integer> stream2 = list.parallelStream(); List<Integer> collect2 = stream2.collect(Collectors.toList()); collect2.forEach(System.out::println); System.out.println("-------------------------"); // 2. 使用数组创建流 IntStream stream3 = Arrays.stream(new int[]{11, 22, 33, 44, 55, 66, 77}); stream3.forEach(System.out::println); System.out.println("-------------------------"); // 3. 使用Stream的静态方法:of()、iterate()、generate()创建Stream Stream<Integer> stream4 = Stream.of(11, 22, 33, 44, 55, 66, 77); stream4.forEach(System.out::println); System.out.println("-------------------------"); Stream<Integer> stream5 = Stream.iterate(0, item -> item + 10).limit(3); stream5.forEach(System.out::println); System.out.println("-------------------------"); Stream<Integer> stream6 = Stream.generate(() -> 10).limit(5); stream6.forEach(System.out::println); System.out.println("-------------------------"); } }
注意:
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。
2.4 Stream的使用
新建实体类Person,后续用于Stream操作。
2.4.1 Person
public class Person { private String name; // 姓名 private int salary; // 薪资 private int age; // 年龄 private String sex; //性别 private String area; // 地区 public Person(){} public Person(String name, int salary,String sex,String area) { this.name = name; this.salary = salary; this.sex = sex; this.area = area; } // 构造方法 public Person(String name, int salary, int age,String sex,String area) { this.name = name; this.salary = salary; this.age = age; this.sex = sex; this.area = area; } //get and set 自己写 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getArea() { return area; } public void setArea(String area) { this.area = area; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", salary=" + salary + ", age=" + age + ", sex='" + sex + '\'' + ", area='" + area + '\'' + '}'; } }
2.4.2 遍历/匹配(foreach/find/match)
Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。
/* @Author xiangge @Date 2023/8/23 @Description 遍历/匹配(foreach/find/match) */ public class StreamDemo2 { public static void main(String[] args) { // 1. 定义集合 List<Integer> list = Arrays.asList(3, 6, 4, 2, 1, 5, 9, 7, 8); // 2. 顺序打印 list.stream().forEach(System.out::println); System.out.println("-------------------------------"); // 3. 并行打印 list.parallelStream().forEach(System.out::println); System.out.println("-------------------------------"); // 4. 发现集合中大于6的元素中的第一个 Optional<Integer> first = list.stream().filter(item -> item > 6).findFirst(); System.out.println(first.get()); // 5. 发现集合中大于6的元素中的任意一个 Optional<Integer> any = list.parallelStream().filter(item -> item > 6).findAny(); System.out.println(any.get()); // 6. 判断集合中是否包含大于8的元素 boolean b = list.stream().anyMatch(item -> item > 8); System.out.println("b = " + b); } }
2.4.3 筛选(filter)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
/* @Author xiangge @Date 2023/8/23 @Description 遍历/匹配(foreach/find/match) */ public class StreamDemo3 { public static void main(String[] args) { // 1. 定义集合 List<Integer> list = Arrays.asList(3, 6, 4, 2, 1, 5, 9, 7, 8); // 2. 筛选集合list中大于5的元素并返回新的集合 List<Integer> collect = list.stream().filter(item -> item > 5).collect(Collectors.toList()); collect.forEach(System.out::println); System.out.println("-------------------------------------"); // 3. 定义集合 List<Person> list2 = new ArrayList<>(); list2.add(new Person("zhangsan",6000,"男","北京")); list2.add(new Person("lisi",5500,"男","上海")); list2.add(new Person("wangwu",10000,"女","武汉")); list2.add(new Person("zhaoliu",4800,"女","长沙")); list2.add(new Person("zhangsan",7200,"男","深圳")); // 4. 筛选出list2中薪资大于7000的元素并返回新的集合 List<Person> collect1 = list2.stream().filter(item -> item.getSalary() > 7000).collect(Collectors.toList()); collect1.forEach(System.out::println); } }
2.4.4 聚合(max/min/count)
Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。
/* @Author xiangge @Date 2023/8/23 @Description 聚合(max/min/count) */ public class StreamDemo4 { public static void main(String[] args) { // 1. 定义集合 List<Integer> list = Arrays.asList(3, 6, 4, 2, 1, 5, 9, 7, 8); // 2. 获取list中的最大值 自然排序 Optional<Integer> max = list.stream().max((item1, item2) -> item1 - item2); System.out.println(max.get()); // 3. 获取list中的最小值 自然排序 Optional<Integer> min = list.stream().min((item1, item2) -> item1 - item2); System.out.println(min.get()); // 4. 获取list中的最小值 自定义排序规则 Optional<Integer> min1 = list.stream().min(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); System.out.println(min1.get()); // 定义集合 List<String> list2 = Arrays.asList("java","H5","python","php","helloworld"); //5. 获取list2中长度最长的字符串 Optional<String> max1 = list2.stream().max((item1, item2) -> item1.length() - item2.length()); System.out.println(max1.get()); //6. 获取list2中长度最短的字符串 Optional<String> min2 = list2.stream().min(String::compareTo); System.out.println(min2.get()); //7. 统计list2中元素的个数 long count = list2.stream().count(); System.out.println("count = " + count); // 8. 定义集合 List<Person> list3 = new ArrayList<>(); list3.add(new Person("zhangsan",6000,"男","北京")); list3.add(new Person("lisi",5500,"男","上海")); list3.add(new Person("wangwu",10000,"女","武汉")); list3.add(new Person("zhaoliu",4800,"女","长沙")); list3.add(new Person("tianqi",7200,"男","深圳")); // 9. 获取list3中薪资最高的元素 Optional<Person> max2 = list3.stream().max((item1, item2) -> item1.getSalary() - item2.getSalary()); System.out.println(max2.get()); // 10. 获取list3中 姓名最长的元素 Optional<Person> max3 = list3.stream().max((item1, item2) -> item1.getName().length() - item2.getName().length()); System.out.println(max3.get()); } }