课程重点:
1 函数式接口
函数式接口的概念
函数式接口的判断
@FunctionalInterface
2 lambda表达式的语法
基础的语法
语法的精简(参数、方法体)
3 函数引用
静态、非静态、构造方法的引用
对象方法的特殊引用
lambda表达式, 是Java8的一个新特性, 也是Java8中最值得学习的新特性之一。
lambda表达式, 从本质来讲, 是一个匿名函数。 可以使用使用这个匿名函数, 实现接口中的方法。 对接口进行非常简洁的实现, 从而简化代码。
通常来讲, 使用lambda表达式, 是为了简化接口实现的。
关于接口实现, 可以有很多种方式来实现。 例如: 设计接口的实现类、 使用匿名内部类。 但是lambda表达式, 比这两种方式都简单。
/**
* @Description
*/
public class Program {
public static void main(String[] args) {
// 无参、无返回值的函数式接口
interfaceImpl();
}
private static void interfaceImpl() {
// 1. 使用显式的实现类对象
SingleReturnSingleParameter parameter1 = new Impl();
// 2. 使用匿名内部类实现
SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter() {
@Override
public int test(int a) {
return a * a;
}
};
// 3. 使用lambda表达式实现
SingleReturnSingleParameter parameter3 = a -> a * a;
System.out.println(parameter1.test(10));
System.out.println(parameter2.test(10));
System.out.println(parameter3.test(10));
}
private static class Impl implements SingleReturnSingleParameter {
@Override
public int test(int a) {
return a * a;
}
}
}
虽然说, lambda表达式可以在一定程度上简化接口的实现。 但是, 并不是所有的接口都可以使用lambda表达式来简洁实现的。
lambda表达式毕竟只是一个匿名方法。 当实现的接口中的方法过多或者多少的时候, lambda表达式都是不适用的。
lambda表达式,只能实现函数式接口。
22.1.4. 函数式接口
22.1.4.1. 基础概念
如果说, 一个接口中, 要求实现类必须实现的抽象方法, 有且只有一个! 这样的接口, 就是函数式接口。
// 这个接口中, 有且只有一个方法, 是实现类必须实现的, 因此是一个函数式接口
interface Test1 {
void test();
}
// 这个接口中, 实现类必须要实现的方法, 有两个! 因此不是一个函数式接口
interface Test2 {
void test1();
void test2();
}
// 这个接口中, 实现类必须要实现的方法, 有零个! 因此不是一个函数式接口
interface Test3 {
}
// 这个接口中, 虽然没有定义任何的方法, 但是可以从父接口中继承到一个抽象方法的。 是一个函数式接口
interface Test4 extends Test1 {
}
// 这个接口, 虽然里面定义了两个方法, 但是defualt方法子类不是必须实现的。
// 因此, 实现类实现这个接口的时候, 必须实现的方法只有一个! 是一个函数式接口。
interface Test5 {
void test5();
default void test() {}
}
// 这个接口中的 toString 方法, 是Object类中定义的方法。
// 此时, 实现类在实现接口的时候, toString可以不重写的! 因为可以从父类Object中继承到!
// 此时, 实现类在实现接口的时候, 有且只有一个方法是必须要重写的。 是一个函数式接口!
interface Test6 {
void test6();
String toString();
}
思考题: 下面的两个接口是不是函数式接口?
interface Test7 {
String toString();
}
interface Test8 {
void test();
default void test1() {}
static void test2() {}
String toString();
}
是一个注解, 用在接口之前, 判断这个接口是否是一个函数式接口。 如果是函数式接口, 没有任何问题。 如果不是函数式接口, 则会报错。 功能类似于 @Override。
@FunctionalInterface
interface FunctionalInterfaceTest {
void test();
}
22.1.4.3. 系统内置的若干函数式接口
22.2. Lambda表达式的语法
lambda表达式, 其实本质来讲, 就是一个匿名函数。 因此在写lambda表达式的时候, 不需要关心方法名是什么。
实际上, 我们在写lambda表达式的时候, 也不需要关心返回值类型。
我们在写lambda表达式的时候, 只需要关注两部分内容即可: 参数列表 和 方法体
lambda表达式的基础语法:
(参数) ->{
方法体
};
参数部分 : 方法的参数列表, 要求和实现的接口中的方法参数部分一致, 包括参数的数量和类型。
方法体部分 : 方法的实现部分, 如果接口中定义的方法有返回值, 则在实现的时候, 注意返回值的返回。
-> : 分隔参数部分和方法体部分。
/**
* @Description
*/
public class Syntax {
public static void main(String[] args) {
// 1. 无参、无返回值的方法实现
NoneReturnNoneParameter lambda1 = () -> {
System.out.println("无参、无返回值方法的实现");
};
lambda1.test();
// 2. 有参、无返回值的方法实现
NoneReturnSingleParameter lambda2 = (int a) -> {
System.out.println("一个参数、无返回值方法的实现: 参数是 " + a);
};
lambda2.test(10);
// 3. 多个参数、无返回值方法的实现
NoneReturnMutipleParameter lambda3 = (int a, int b) -> {
System.out.println("多个参数、无返回值方法的实现: 参数a是 " + a + ", 参数b是 " + b);
};
lambda3.test(10, 20);
// 4. 无参、有返回值的方法的实现
SingleReturnNoneParameter lambda4 = () -> {
System.out.println("无参、有返回值方法的实现");
return 666;
};
System.out.println(lambda4.test());
// 5. 一个参数、有返回值的方法实现
SingleReturnSingleParameter lambda5 = (int a) -> {
System.out.println("一个参数、有返回值的方法实现: 参数是 " + a);
return a * a;
};
System.out.println(lambda5.test(9));
// 6. 多个参数、有返回值的方法实现
SingleReturnMutipleParameter lambda6 = (int a, int b) -> {
System.out.println("多个参数、有返回值的方法实现: 参数a是 " + a + ", 参数b是 " + b);
return a * b;
};
System.out.println(lambda6.test(10, 20));
}
}
在上述代码中, 的确可以使用lambda表达式实现接口, 但是依然不够简洁, 有简化的空间。
// 多个参数、无返回值的方法实现
NoneReturnMutipleParameter lambda1 = (a, b) -> {
System.out.println("多个参数、无返回值方法的实现: 参数a是 " + a + ", 参数b是 " + b);
};
// 有参、无返回值的方法实现
NoneReturnSingleParameter lambda2 = a -> {
System.out.println("一个参数、无返回值方法的实现: 参数是 " + a);
};
22.2.2.2. 方法体部分的精简
// 有参、无返回值的方法实现
NoneReturnSingleParameter lambda2 = a -> System.out.println("一个参数、无返回值方法的实现: 参数是 " + a);
SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;
22.3. 函数引用
lambda表达式是为了简化接口的实现的。 在lambda表达式中, 不应该出现比较复杂的逻辑。 如果在lambda表达式中出现了过于复杂的逻辑, 会对程序的可读性造成非常大的影响。 如果在lambda表达式中需要处理的逻辑比较复杂, 一般情况会单独的写一个方法。 在lambda表达式中直接引用这个方法即可。
或者, 在有些情况下, 我们需要在lambda表达式中实现的逻辑, 在另外一个地方已经写好了。 此时我们就不需要再单独写一遍, 只需要直接引用这个已经存在的方法即可。
函数引用: 引用一个已经存在的方法, 使其替代lambda表达式完成接口的实现。
22.3.1. 静态方法的引用
/**
* @Description
*/
public class Syntax1 {
// 静态方法的引用
public static void main(String[] args) {
// 实现一个多个参数的、一个返回值的接口
// 对一个静态方法的引用
// 类::静态方法
SingleReturnMutipleParameter lambda1 = Calculator::calculate;
System.out.println(lambda1.test(10, 20));
}
private static class Calculator {
public static int calculate(int a, int b) {
// 稍微复杂的逻辑:计算a和b的差值的绝对值
if (a > b) {
return a - b;
}
return b - a;
}
}
}
22.3.2. 非静态方法的引用
/**
* @Description
*/
public class Syntax2 {
public static void main(String[] args) {
// 对非静态方法的引用,需要使用对象来完成
SingleReturnMutipleParameter lambda = new Calculator()::calculate;
System.out.println(lambda.test(10, 30));
}
private static class Calculator {
public int calculate(int a, int b) {
return a > b ? a - b : b - a;
}
}
}
如果某一个函数式接口中定义的方法, 仅仅是为了得到一个类的对象。 此时我们就可以使用构造方法的引用, 简化这个方法的实现。
类名::new
可以通过接口中的方法的参数, 区分引用不同的构造方法。
/**
* @Description
*/
public class Syntax3 {
private static class Person {
String name;
int age;
public Person() {
System.out.println("一个Person对象被实例化了");
}
public Person(String name, int age) {
System.out.println("一个Person对象被有参的实例化了");
this.name = name;
this.age = age;
}
}
@FunctionalInterface
private interface GetPerson {
// 仅仅是希望获取到一个Person对象作为返回值
Person test();
}
private interface GetPersonWithParameter {
Person test(String name, int age);
}
public static void main(String[] args) {
// lambda表达式实现接口
GetPerson lambda = Person::new; // 引用到Person类中的无参构造方法,获取到一个Person对象
Person person = lambda.test();
GetPersonWithParameter lambda2 = Person::new; // 引用到Person类中的有参构造方法,获取到一个Person对象
lambda2.test("xiaoming", 1);
}
}
如果在使用lambda表达式,实现某些接口的时候。 lambda表达式中包含了某一个对象, 此时方法体中, 直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。 其他的参数, 可以作为调用方法的参数。 此时, 可以对这种实现进行简化。
/**
* @Description
*/
public class Syntax {
public static void main(String[] args) {
// 如果对于这个方法的实现逻辑,是为了获取到对象的名字
GetField field = person -> person.getName();
// 对于对象方法的特殊引用
GetField field = Person::getName;
// 如果对于这个方法的实现逻辑,是为了给对象的某些属性进行赋值
SetField lambda = (person, name) -> person.setName(name);
SetField lambda = Person::setName;
// 如果对于这个方法的实现逻辑,正好是参数对象的某一个方法
ShowTest lambda2 = person -> person.show();
ShowTest lambda2 = Person::show;
}
}
interface ShowTest {
void test(Person person);
}
interface SetField {
void set(Person person, String name);
}
interface GetField {
String get(Person person);
}
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void show() {
}
}
22.4. Lambda表达式需要注意的问题
这里类似于局部内部类、匿名内部类,依然存在闭包的问题。
如果在lambda表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为 final。 是一个常量, 不能修改值。
22.5. Lambda表达式的实例
22.5.1. 线程的实例化
Thread thread = new Thread(() -> {
// 线程中的处理
});
22.5.2. 集合的常见方法
ArrayList list = new ArrayList<>();
Collections.addAll(list, "千锋", "大数据", "好程序员", "严选", "高薪");
// 按照条件进行删除
list.removeIf(ele -> ele.endsWith(".m"));
// 批量替换
list.replaceAll(ele -> ele.concat("!"));
// 自定义排序
list.sort((e1, e2) -> e2.compareTo(e1));
// 遍历
list.forEach(System.out::println);
22.5.3. 集合的流式编程
ArrayList list = new ArrayList<>();
Collections.addAll(list, "千锋", "大数据", "好程序员", "严选", "高薪");
list.parallelStream().filter(ele -> ele.length() > 2).forEach(System.out::println);