1、Lamdba表达式
1.1、Lambda表达式的概念
Lambda表达式,从本质上来讲,是一个匿名函数。可以使用这个匿名函数,实现接口中的方法,从而简化代码。
1.2、Lambda表达式的使用场景
通常来讲,使用Lambda表达式,是为了简化接口实现的。
关于接口实现,可以有很多种方式来实现。例如:设计借口的实现类、使用匿名内部类。但是lambda表达式,比这两种方式简单。
package com.helen.lambda.main;
import com.helen.lambda.funcationInterface.SingleReturnSingleParameter;
/**
* @Description
* @Author BF <xu.rongchang@foxmail.com>
* @Version 1.0
* @Date 2021/6/3
*/
public class Program {
public static void main(String[] args) {
interfaceImpl();
}
public static void interfaceImpl(){
// 1、使用显示的实现类对象
SingleReturnSingleParameterImpl parameter1 = new SingleReturnSingleParameterImpl();
// 2、使用匿名内部类的方式
SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter() {
@Override
public int test(int a) {
return a * a;
}
};
// 3、使用lambda表达式的方式实现
SingleReturnSingleParameter parameter3 = a -> a * a;
System.out.printf("显示的实现类对象: %d
",parameter1.test(10));
System.out.printf("匿名内部类的方式: %d
", parameter2.test(10));
System.out.printf("lambda表达式的方式: %d
", parameter3.test(10));
}
}
class SingleReturnSingleParameterImpl implements SingleReturnSingleParameter{
@Override
public int test(int a) {
return a * a;
}
}
1.3、Lamdba表达式对接口的要求
虽然说,lambda表达式可以在一定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简洁实现的。
lambda表达式毕竟只是一个匿名方法。当实现的接口中的方法过多或者过少的时候(一个都没有),lambda表达式都是不适用的。
lambda表达式,只能实现函数式接口。
1.4、函数式接口
1.4.1、基础概念
如果说,一个接口中,要求实现类必须实现的抽象方法,有且只有一个! 这样的接口,就是函数式接口。
// 这个接口中,有且只有一个方法,是实现类必须实现的,因此是一个函数式接口。
interface Test1 {
void test();
}
// 这个接口中,实现类必须实现的方法,有两个! 因此不是一个函数式接口。
interface Test2 {
void test1();
void test2();
}
// 这个接口中,实现类必须实现的方法,有零个!因此不是一个函数式接口。
interface Test3 {
}
// 这个接口中,虽然没有定义任何方法,但是可以从父类中继承一个抽象方法,因此是一个函数式接口。
interface Test4 extends Test1 {
}
// 这个接口,虽然里面定义了两个方法,但是 default 修饰的方法子类不是必须实现的。
// 因此子类必须实现的方法也只有一个,所以此接口是一个函数式接口。
interface Test5 {
void test5();
default void test2(){};
}
// 这个接口中的 toString 方法,是Object中定义的方法
// 此时实现类在实现接口的时候,toString可以不重写!因为可以从父类Object中继承到!
// 此时,实现类在实现接口的时候,有且只有一个接口需要实现,因此是一个函数式接口。
interface Test6 {
void test6();
String toString();
}
1.4.2、@FunctionalInterface
是一个注解,用在接口上,判断这个接口是否是一个函数式接口。如果是函数式接口,没任何问题。如果不是一个函数式接口,则会报错。功能类似于 @Override。
@FunctionalInterface
interface Test1 {
void test();
}
2、Lambda表达式的语法
2.1、Lambda表达式的基础语法
Lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。
实际上,我们在写lambda表达式的时候,也不需要关系返回值的类型。
我们在写lambda表达式的时候,只需要关注两部分内容即可:参数列表 和 方法体
lambda表达式的基础语法:
(参数) -> {
方法体
};
参数部分:方法的参数列表,要求和实现的接口中的方法参数一致,包括参数的数量和类型。
方法体部分:方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
-> : 分隔参数部分和方法体部分。
public class BasicSyntax {
public static void main(String[] args) {
// 1、实现无参、无返回值函数式接口
NoneReturnNoneParameter lambda1 = () -> {
System.out.println("lambda 实现 无参、无返回值函数式接口");
};
lambda1.test();
// 2、实现一个参数、无返回值的函数式接口
NoneReturnSingleParameter lambda2 = (int a) -> {
System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a);
};
lambda2.test(100);
// 3、实现多个参数、无返回值的函数式接口
NoneReturnMultipleParameter lambda3 = (int a ,int b) -> {
System.out.println("lambda 实现 多个参数、无返回值的函数式接口 参数a的值为:"+a+" 参数b的值为:"+b);
};
lambda3.test(100,200);
// 4、实现有返回值、无参数的函数式接口
SingleReturnNoneParameter lambda4 = () -> {
System.out.println("lambda 实现 有返回值、无参数的函数式接口 返回值是:"+10);
return 10;
};
int ret1 = lambda4.test();
System.out.println("ret1 = " + ret1);
// 5、实现有返回值、一个参数的函数式接口
SingleReturnSingleParameter lambda5 = (int a) -> {
System.out.println("lambda 实现 有返回值、一个参数的函数式接口 返回值是:"+a);
return a;
};
int ret2 = lambda5.test(1998);
System.out.println("ret2 = " + ret2);
// 6、实现有返回值、多个参数的函数式接口
SingleReturnMultipleParameter lambda6 = (int a ,int b) -> {
System.out.println("lambda 实现 有返回值、多个参数的函数式接口");
return a+b;
};
int ret3 = lambda6.test(100,200);
System.out.println("ret3 = " + ret3);
}
}
2.2、Lambda表达式的语法进阶
在上述代码中,的确可以使用lambda表达式实现接口,但是依然不够简洁,有简化的空间。
2.2.1、参数部分的精简
-
参数的类型
-
由于在接口的方法中,已经定义了每一个参数的类型是什么。而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类型需要和接口中的方法保持一致。因此,此时lambda表达式中的参数类型可以省略不写。
-
注意事项:
-
如果需要省略参数的类型,要保证:要省略,每一个参数的类型必须保证不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。
// 3、实现多个参数、无返回值的函数式接口 NoneReturnMultipleParameter lambda3 = ( a , b) -> { System.out.println("lambda 实现 多个参数、无返回值的函数式接口 参数a的值为:"+a+" 参数b的值为:"+b); }; lambda3.test(100,200);
-
-
-
参数的小括号
-
如果方法的参数列表中参数数量 有且只有一个 ,此时,参数列表的小括号是可以省略不写的。
-
注意事项:
-
只有当参数的数量是一个的时候,才可以省略不写。
-
省略小括号的同时,必须要省略参数的类型。
// 2、实现一个参数、无返回值的函数式接口 NoneReturnSingleParameter lambda2 = a -> { System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a); }; lambda2.test(100);
-
-
2.2.2、方法体部分
-
方法体的大括号
-
如果方法体有且只有一条语句时,方法体的大括号可以省略不写。
// 2、实现一个参数、无返回值的函数式接口 NoneReturnSingleParameter lambda2 = a -> System.out.println("lambda 实现 一个参数、无返回值的函数式接口。参数a的值为:"+a); lambda2.test(100);
-
-
返回关键字return
-
如果方法体有且只有一条语句,并且就是返回语句时。return 关键字可以省略不写。
//之前: // 6、实现有返回值、多个参数的函数式接口 SingleReturnMultipleParameter lambda6 = (int a ,int b) -> { return a * b; }; int ret3 = lambda6.test(100,200); System.out.println("ret3 = " + ret3); // 精简之后: // 6、实现有返回值、多个参数的函数式接口 SingleReturnMultipleParameter lambda6 = (int a ,int b) -> a * b; int ret3 = lambda6.test(100,200); System.out.println("ret3 = " + ret3);
-
3、函数引用
lambda表达式是为了简化接口实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,一般会单独的写一个方法。在lambda表达式中直接引用这个方法即可。
或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。
函数引用:引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。
3.1、静态方法的引用
-
语法:
- 类::静态方法
-
注意事项:
- 在引用的方法后面,不要添加小括号。
- 引用的这个方法,参数(数量、类型) 和 返回值,必须要跟接口定义的一致。
-
示例:
/** * @Description 函数引用 ---> 静态方法的引用 * @Author BF <xu.rongchang@foxmail.com> * @Version 1.0 * @Date 2021/6/3 */ public class Syntax1 { private interface Calculator{ int cacl(int a ,int b); } public static void main(String[] args) { Calculator calculator = Syntax1::cal; System.out.println(calculator.cacl(10,15)); } public static int cal(int a ,int b){ if (a>b){ return a - b; }else if (b>a){ return b - a; } return a + b ; } }
3.2、非静态方法的引用
-
语法:
- 对象::非静态方法
-
注意事项:
- 在引用的方法后面,不要添加小括号。
- 引用的这个方法,参数(数量、类型)和 返回值,必须要跟接口中定义的一致。
-
示例:
/** * @Description 函数引用 ---> 非静态方法的引用 * @Author BF <xu.rongchang@foxmail.com> * @Version 1.0 * @Date 2021/6/3 */ public class Syntax1 { private interface Calculator{ int cacl(int a ,int b); } public static void main(String[] args) { Calculator calculator = new Syntax1()::cal; System.out.println(calculator.cacl(10,15)); } public int cal(int a ,int b){ if (a>b){ return a - b; }else if (b>a){ return b - a; } return a + b ; } }
3.3、构造方法的引用
-
使用场景:
- 如果某一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
-
语法:
- 类名::new
-
注意事项:
- 可以通过接口中的方法的参数,区分引用不同的构造方法。
-
示例:
/** * @Description 函数引用 ---》 构造方法的引用 * @Author BF <xu.rongchang@foxmail.com> * @Version 1.0 * @Date 2021/6/3 */ public class Lambda1 { private static class Person{ public Person() { System.out.println("Person类的无参构造执行了....."); } public Person(String str) { System.out.println("Person类的一个参数的构造方法执行了....."); } public Person(String str,int age) { System.out.println("Person类的多个参数的构造方法执行了....."); } } @FunctionalInterface private interface GetPersonWithNoneParameter{ Person get(); } @FunctionalInterface private interface GetPersonWithSingleParameter{ Person get(String str); } @FunctionalInterface private interface GetPersonWithMultipleParameter{ Person get(String str,int age); } public static void main(String[] args) { // 1、通过 lambda 表达式,实现了 GetPersonWithNoneParameter 接口 GetPersonWithNoneParameter person1 = Person::new; // 2、通过 lambda 表达式,实现了 GetPersonWithSingleParameter 接口 GetPersonWithSingleParameter person2 = Person::new; // 3、通过 lambda 表达式,实现了 GetPersonWithMultipleParameter 接口 GetPersonWithMultipleParameter person3 = Person::new; person1.get(); person2.get("111"); person3.get("123",123); } }
3.4、对象方法的特殊引用
如果在使用lambda表达式,实现某些接口的时候。lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。其它的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。
/**
* @Description 函数引用 ---》 对象方法的特殊引用(非静态方法)
* @Author BF <xu.rongchang@foxmail.com>
* @Version 1.0
* @Date 2021/6/3
*/
public class Lambda2 {
private static class Person{
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void show(){
}
public int showTest(int a ,int b){
return a + b;
}
}
@FunctionalInterface
private interface GetName{
String getName01(Person person);
}
@FunctionalInterface
private interface SetName{
void setName02(Person person,String name);
}
@FunctionalInterface
private interface VoidName{
void voidName02(Person person);
}
@FunctionalInterface
private interface AddShow{
void addShow1(Person person,int a,int b);
}
public static void main(String[] args) {
GetName getName1 = Person -> Person.getName();
/**
* lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。
* 此时,可以对这种实现进行简化。
* */
GetName getName2 = Person::getName;
SetName setName3 = (Person,name) -> Person.setName(name);
/**
* lambda表达式中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。
* 其它的参数,可以作为调用方法的参数。
* 此时,可以对这种实现进行简化。
* */
SetName setName4 = Person::setName;
/**
* 对于这个方法的实现逻辑,刚好是调用参数对象的某一个方法
**/
VoidName voidName5 = Person -> Person.show();
VoidName voidName6 = Person::show;
/**
* 对于这个方法的实现逻辑,刚好是调用参数对象的某一个方法
* 带参数了和上面差不多
**/
AddShow addShow7 = (Person,a,b) -> Person.showTest(a, b);
AddShow addShow8 = Person::showTest;
}
}
4 、Lambda表达式需要注意的地方
/**
* @Description Lambda表达式需要注意的地方
* @Author BF <xu.rongchang@foxmail.com>
* @Version 1.0
* @Date 2021/6/3
*/
public class Lambda3 {
//static int x = 100;
public static void main(String[] args) {
// 1、定义一个局部变量
int x = 100;
// 2、使用Lambda表达式实现接口
AddTest addTest = () -> System.out.println("X的值为:"+x);
// 3、修改变量的值
int x = 50;
/**
* 发现 x 的值,无法被修改;
* 原因在于 局部变量x传递进 Lambda表达式后形成包围(闭包)了。而且 x 的修饰符将增加为 final
* 导致下一行代码无法进行修改。
* 解决:我们将局部变量升级为全局变量 ,即可解决为题!
* */
}
}
@FunctionalInterface
interface AddTest{
void test();
}