文章目录
零、本讲学习目标
1、理解Lambda表达式的作用
2、掌握Lambda表达式的操作(遍历、过滤、提取)
Lambda表达式是JDK 8中一个重要的新特性,它使用一个清晰简洁的表达式来表达一个接口,同时Lambda表达式也简化了对集合以及数组数据的遍历、过滤和提取等操作。
一、Lambda表达式入门
1、匿名内部类的缺点
匿名内部类存在的一个问题是,如果匿名内部类的实现非常简单,例如只包含一个抽象方法的接口,那么匿名内部类的语法仍然显得比较冗余。
2、JDK1.8提供的解决方案
可以使用JDK 8中新增的Lambda表达式,这种表达式只针对有一个抽象方法的接口实现,以简洁的表达式形式实现接口功能来作为方法参数。
3、Lambda表达式语法格式
- 参数列表
- 表达式箭牌(->)
- 表达式主体
4、Lambda表达式案例演示
(1)创建动物接口Animal
package net.hw.lesson17.ex01;
/**
* 功能:动物接口
* 作者:华卫
* 日期:2020年05月13日
*/
public interface Animal {
void eat();
}
(2)创建学生类Student
package net.hw.lesson17.ex01;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void feed(Animal animal) {
System.out.println(name + "喂养一只动物。");
animal.eat();
}
}
(3)创建测试类TestStudent
package net.hw.lesson17.ex01;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("张晓红");
String animalName = "瑞瑞";
// 1. 采用匿名内部类对象作为参数传给对象的feed()方法
student.feed(new Animal() {
@Override
public void eat() {
System.out.println("狗崽[" + animalName + "]爱啃骨头。");
}
});
// 2. 采用Lambda表达式作为参数传给对象的feed()方法
student.feed(() -> System.out.println("狗崽[" + animalName + "]爱啃骨头。"));
}
}
运行程序,查看结果:
(4)对案例演示进行简要说明
首先定义了一个接口Animal,有个抽象方法eat(),然后定义了一个Student类,有个喂养方法feed(Animal animal),需要Animal接口对象作为参数。在测试类TestStudent里,分别采用匿名内部类和Lambda表达式的方式实现了接口方法所需的参数。两种方式的结果都是一样的,但是可以看出,采用Lambda表达式更加简洁和清晰。
二、函数式接口
1、函数式接口概述
接口中有且只有一个抽象方法时才能使用Lamdba表达式代替匿名内部类。这是因为Lamdba表达式是基于函数式接口实现的,所谓函数式接口是指有且仅有一个抽象方法的接口,Lambda表达式就是Java中函数式编程的体现,只有确保接口中有且仅有一个抽象方法,Lambda表达式才能顺利地推导出所实现的这个接口中的方法。
2、函数式接口定义
在JDK 8中,接口上标注有@FunctionalInterface
注解的即为函数式接口,在函数式接口内部有且只有一个抽象方法。
3、函数式接口说明
@FunctionalInterface
注解只是显式地标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式接口。
4、案例演示函数式接口
(1)创建无参无返回值函数式接口Animal
package net.hw.lesson17.ex02;
/**
* 功能:函数式接口Animal
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface Animal {
void eat();
}
函数式接口强制接口里有且只有一个抽象方法,如果没有抽象方法或多于一个抽象方法,编译都会报错。
(2)创建有参有返回值函数式接口Calculate
package net.hw.lesson17.ex02;
/**
* 功能:函数式接口Calculate
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface Calculate {
int sum(int a, int b);
}
(3)创建学生类Student,定义喂养与计算方法
- 定义name属性及其访问方法
- 定义feed(Animal animal)方法
- 定义calc(Calculate calculate)方法
package net.hw.lesson17.ex02;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 喂养动物
*
* @param animal
*/
public void feed(Animal animal) {
System.out.println(name + "喂养一只动物。");
animal.eat();
}
/**
* 计算
*
* @param x
* @param y
* @param calculate
*/
public void calc(int x, int y, Calculate calculate) {
System.out.println(name + "做一道加法题。");
System.out.println(x + " + " + y + " = " + calculate.sum(x, y));
}
}
(4)创建测试类TestStudent
package net.hw.lesson17.ex02;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudent {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 设置对象属性
student.setName("李小虎");
// 定义动物名
String animalName = "瑞瑞";
// 调用对象方法,测试函数式接口
student.feed(() -> System.out.println("狗崽[" + animalName + "]爱啃骨头。"));
student.calc(100, 150, (x, y)-> x + y);
}
}
运行程序,查看结果:
三、方法引用
(一)方法引用概述
1、说明
Lambda表达式的主体只有一条语句时,程序不仅可以省略包含主体的花括号,还可以通过英文双冒号“::
”的语法格式来引用普通方法
和构造方法
。
2、作用
可以进一步简化Lambda表达式的书写,其本质都是对Lambda表达式的主体部分已存在的方法进行直接引用,主要区别就是对普通方法
与构造方法
的引用而已。
3、JDK 8里Lambda表达式支持的引用类型
种类 | Lambda表达式示例 | 对应的引用示例 |
---|---|---|
类名引用普通方法 | (x, y, …)-> 对象名x.类普通方法名(y, …) | 类名::类普通方法名 |
类名引用静态方法 | (x, y, …) -> 类名.类静态方法名(x, y, …) | 类名::类静态方法名 |
对象名引用方法 | (x, y, …) -> 对象名.实例方法名(x, y, …) | 对象名::实例方法名 |
构造器引用 | (x, y, …) -> new 类名 (x, y, …) | 类名::new |
(二)类名引用静态方法
1、定义
类名引用静态方法也就是通过类名对静态方法的引用,该类可以是Java自带的特殊类,也可以是自定义的普通类。
// 1. 采用Lambda表达式的方式
student.printResult(10, n -> Math.factorial(n));
// 2. 采用方法引用的方式
student.printResult(10, Math::factorial);
2、案例演示
(1)创建函数式接口Calculate
package net.hw.lesson17.ex03;
/**
* 功能:函数式接口Calculate
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface Calculate {
int calc(int num);
}
(2)创建Math类,定义计算阶乘的静态方法
package net.hw.lesson17.ex03;
/**
* 功能:Math类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Math {
public static int factorial(int num) {
int jc = 1;
for (int i = 1; i <= num; i++) {
jc = jc * i;
}
return jc;
}
}
(3)创建学生类Student,定义输出阶乘结果的方法
package net.hw.lesson17.ex03;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 输出计算结果
*
* @param num
* @param calculate
*/
public void printResult(int num, Calculate calculate) {
System.out.println(name + "做一道计算题。");
System.out.println(num + "! = " + calculate.calc(num));
}
}
(4)创建测试类TestStudent
package net.hw.lesson17.ex03;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudent {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 设置对象属性
student.setName("李晓红");
// 调用对象方法
// 1. 采用Lambda表达式的方式
student.printResult(10, n -> Math.factorial(n));
// 2. 采用方法引用的方式
student.printResult(10, Math::factorial);
}
}
运行程序,查看结果:
(5)对案例演示进行简要说明
在TestStudent类中,分别使用了Lambda表达式和方法引用的方式作为printResult()方法的参数进行调用,运行结果表明,两种方法殊途同归。可以看出,采用类名引用静态方法的实现方式更为简洁。
(三)对象名引用方法
1、定义
对象名引用方法指的是通过实例化对象的名称来对其方法进行的引用。
// 创建数学对象
Math math = new Math();
// 1. 采用Lambda表达式的方式调用学生对象的方法
student.printResult(10, n -> math.factorial(n));
// 2. 采用方法引用的方式调用学生对象的方法
student.printResult(10, math::factorial);
2、案例演示
(1)创建函数式接口Calculate
package net.hw.lesson17.ex04;
/**
* 功能:函数式接口Calculate
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface Calculate {
int calc(int num);
}
(2)创建Math类,定义计算阶乘的非静态方法
package net.hw.lesson17.ex04;
/**
* 功能:Math类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Math {
public int factorial(int num) {
int jc = 1;
for (int i = 1; i <= num; i++) {
jc = jc * i;
}
return jc;
}
}
(3)创建学生类Student,定义输出阶乘结果的方法
package net.hw.lesson17.ex04;
import net.hw.lesson17.ex03.Calculate;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 输出计算结果
*
* @param num
* @param calculate
*/
public void printResult(int num, Calculate calculate) {
System.out.println(name + "做一道计算题。");
System.out.println(num + "! = " + calculate.calc(num));
}
}
(4)创建测试类TestStudent
package net.hw.lesson17.ex04;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudent {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 设置学生对象属性
student.setName("李晓红");
// 创建数学对象
Math math = new Math();
// 1. 采用Lambda表达式的方式调用学生对象的方法
student.printResult(10, n -> math.factorial(n));
// 2. 采用方法引用的方式调用学生对象的方法
student.printResult(10, math::factorial);
}
}
运行程序,查看结果:
(四)构造方法引用
1、定义
构造方法引用指的是对类自带的构造方法的引用。
// 1. 使用Lambda表达式方式
printName("李晓彤", name -> new Student(name));
// 2. 使用构造方法引用的方式
printName("李晓彤", Student::new);
2、案例演示
(1)创建学生类Student
package net.hw.lesson17.ex05;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)创建函数式接口StudentBuilder
package net.hw.lesson17.ex05;
/**
* 功能:函数式接口
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface StudentBuilder {
Student studentBuilder(String name);
}
(4)创建测试类TestStudentBuilder
package net.hw.lesson17.ex05;
/**
* 功能:测试StudentBuilder
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudentBuilder {
public static void printName(String name, StudentBuilder builder) {
System.out.print("创建一个学生对象:");
System.out.println(builder.studentBuilder(name).getName());
}
public static void main(String[] args) {
// 1. 使用Lambda表达式方式
printName("李晓彤", name -> new Student(name));
// 2. 使用构造方法引用的方式
printName("李晓彤", Student::new);
}
}
运行程序,查看结果:
(五)类名引用普通方法
1、定义
类名引用普通方法指的是通过一个普通类的类名来对其普通方法进行的引用。
2、案例演示
(1)创建Math类,定义计算阶乘的普通方法
package net.hw.lesson17.ex06;
/**
* 功能:Math类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Math {
public int factorial(int num) {
int jc = 1;
for (int i = 1; i <= num; i++) {
jc = jc * i;
}
return jc;
}
}
(2)创建函数式接口Calculate
package net.hw.lesson17.ex06;
/**
* 功能:函数式接口Calculate
* 作者:华卫
* 日期:2020年05月13日
*/
@FunctionalInterface
public interface Calculate {
int calc(Math math, int num);
}
注意:跟先前创建的函数式接口Calculate的calc()方法参数是不一样的,多了一个参数。
(3)创建学生类Student,定义输出阶乘结果的方法
package net.hw.lesson17.ex06;
/**
* 功能:学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 输出计算结果
*
* @param num
* @param calculate
*/
public void printResult(Math math, int num, Calculate calculate) {
System.out.println(name + "做一道计算题。");
System.out.println(num + "! = " + calculate.calc(math, num));
}
}
(4)创建测试类TestStudent
package net.hw.lesson17.ex06;
/**
* 功能:测试学生类
* 作者:华卫
* 日期:2020年05月13日
*/
public class TestStudent {
public static void main(String[] args) {
// 创建学生对象
Student student = new Student();
// 设置对象属性
student.setName("李晓红");
// 调用对象方法
// 1. 采用Lambda表达式的方式
student.printResult(new Math(), 10, (object, n) -> object.factorial(n));
// 2. 采用方法引用的方式
student.printResult(new Math(),10, Math::factorial);
}
}
运行程序,查看结果:
(5)对案例演示进行简要说明
在TestStudent类中,分别使用了Lambda表达式和方法引用的方式作为printResult()方法的参数进行调用,运行结果表明,两种方法殊途同归。可以看出,采用类名引用普通方法的实现方式更为简洁。
四、Lambda表达式的操作
1、利用Lambda表达式遍历数组
package net.hw.lesson17.ex07;
import java.util.Arrays;
/**
* 功能:采用Lambda表达式遍历数组
* 作者:华卫
* 日期:2020年05月13日
*/
public class Example1701 {
public static void main(String[] args) {
Integer[] arr = {45, 67, 78, 23, 90, 12, 59, 73, 84, 43};
// 方法一(采用Lambda表达式)
System.out.println("方法一:");
Arrays.asList(arr).forEach(item -> System.out.print(item + " "));
System.out.println();
// 方法二(采用方法引用的方式)
System.out.println("方法二:");
Arrays.asList(arr).forEach(System.out::println);
}
}
运行程序,查看结果:
2、使用Lambda表达式筛选数组数据
任务1、筛选出整型数组中大于50的元素
package net.hw.lesson17.ex07;
import java.util.Arrays;
/**
* 功能:使用Lambda筛选数组
* 作者:华卫
* 日期:2020年05月13日
*/
public class Example1702 {
public static void main(String[] args) {
Integer[] arr = {45, 67, 78, 23, 90, 12, 59, 73, 84, 43};
System.out.print("数组中大于50的元素:");
Arrays.asList(arr).stream().filter(x -> x > 50).forEach(x -> System.out.print(x + " "));
}
}
运行程序,查看结果:
课堂练习:利用Lambda表达式筛选字符串数组
- 字符串数组: {“hello java”, “nice python”, “I love java”, “I learn python”, “java is useful”};
- 筛选出包含“java”的元素
- 筛选出长度大于12的元素
3、利用Lambda表达式提取数组中满足条件的数据
package net.hw.lesson17.ex07;
import java.util.Arrays;
/**
* 功能:使用Lambda提取数据
* 作者:华卫
* 日期:2020年05月13日
*/
public class Example1703 {
public static void main(String[] args) {
Integer[] arr = {45, 67, 78, 23, 90, 12, 59, 73, 84, 43};
// 提取数组中大于50的元素构成一个新数组
Object[] arr2 = Arrays.asList(arr).stream().filter(x -> x > 50).toArray();
System.out.print("采用for循环输出结果:");
for (int i = 0 ; i < arr2.length; i++) {
System.out.print(arr2[i] + " ");
}
System.out.println();
System.out.print("采用增强for循环输出结果:");
for (Object x : arr2) {
System.out.print(x + " ");
}
System.out.println();
System.out.print("采用Lambda表达式输出结果:");
Arrays.asList(arr2).forEach(x -> System.out.print(x + " "));
}
}
运行程序,查看结果: