Lambda表达式
- Lambda表达式
-
- 1、 Lambda表达式的简介
-
- 1.1 Lambda表达式的概念
- 1.2 Lambda表达式的使用场景
- 1.3 Lambda 表达式对接口的要求
- 1.4 函数式接口
-
- 1.4.1 基础概念
- 1.4.2 @FunctionalInterface
- 1.4.3 常见函数式接口
- 2、 Lambda表达式的语法
-
- 2.1、 Lambda表达式的基础语法
- 2.2、Lambda表达式的语法进阶
-
- 2.2.1、 参数部分的精简
- 2.2.2、方法体部分的精简
- 3、函数引用
-
- 3.1、 静态方法的引用
- 3.2、非静态方法的引用
- 3.3、构造方法的引用
- 3.4、对象方法的特殊引用
- 4、Lambda表达式需要注意的问题
Lambda表达式
1、 Lambda表达式的简介
1.1 Lambda表达式的概念
lambda表达式,是Java8的一个新特性, 也是Java8中最值得学习的新特性之一。
lambda表达式,从本质来讲,是一个匿名函数。可以使用这个匿名函数,实现接口中的方法。对接口进行非常简洁的实现,从而简化代码。
1.2 Lambda表达式的使用场景
通常来讲,使用lambda表达式,是为了简化接口实现的。
关于接口实现,可以有很多方式来实现。例如:设计接口的实现类、使用匿名内部类。但是lambda表达式,比这两种方式都简单。
public ckass program{
public static void main(String[] args){
//无参、无返回值的函数式接口
interfaceImpl();
}
private static void interfaceImpl(){
// 1. 使用显示的实现类对象
SingleReturnSingleParameter parameter1 = new Impl();
// 2. 使用匿名内部类实现
SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter(){
@Overrride
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;
}
}
}
1.3 Lambda 表达式对接口的要求
虽然说,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 {
}
//这个接口,虽然里面定义了两个方法,但是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();
}
//是
1.4.2 @FunctionalInterface
是一个注解,用在接口之前,判断这个接口是否是一个函数式接口。如果是函数式接口,没有任何问题。如果不是函数式接口,则会报错。功能类似于 @Override
@FunctionalInterface
interface FUnctionalInterfaceTest{
void test();
}
1.4.3 常见函数式接口
函数式接口 | 参数类型 | 返回类型 | 说明 |
---|---|---|---|
Consumer 消费型接口 | T | void | void accept(T t),对类型为T的对象应用操作 |
Supplier 供给型接口 | 无 | T | T get{}; 返回类型为 T 的对象 |
Function<T, R> 函数式接口 | T | R | R apply(T t); 对类型为T 的对象应用操作,并返回类型为R类型的对象 |
Predicate 断言型接口 | T | boolean | boolean test(T t); 确定类型为T 的对象是否满足条件,并返回Boolean类型 |
2、 Lambda表达式的语法
2.1、 Lambda表达式的基础语法
lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。
实际上,我们在写lambda表达式的时候,也不需要关系返回值类型。
我们在写lambda表达式的时候,只需要关注两部分内容即可: 参数列表 和 方法体
lambda 表达式的基础语法:
(参数) -> {
方法体
};
参数部分: 方法的参数列表,要求和是实现的接口给中的方法参数部分一致,包括参数的数量和类型。
方法体部分: 方法的实现部分,若果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
->: 分隔参数部分和方法体部分
public class Test{
public static void main(String[] args){
// 1. 实现无参,无返回值的函数式接口
NoneReturnNoneParameter lambda1 = () => {
System.out.println("这是一个无参,无返回值的方法");
}
lambda1.test();
// 2. 实现一个参数,无返回值的函数式接口
NoneReturnSingleParameter lambda2 = (int a) => {
System.out.println("这是一个无参,无返回值的方法, 参数a" + a);
}
lambda2.test(10);
// 3. 实现多个参数,无返回值的函数式接口
NoneReturnMutipleParameter lambda3 = (int a, int b) => {
System.out.println("这是一个无参,无返回值的方法, 参数a=" + a + ", b=" + b);
}
lambda3.test(100, 200);
// 4. 实现无参,有返回值的函数式接口
SingleReturnNoneParameter lambda4 = () -> {
System.out.println("这是一个无参,有返回值的方法, 返回值是:" + 10);
return 10;
}
int ret1 = lambda4.test();
System.out.println(ret1);
// 5. 实现一个参数,有返回值的函数值接口
SingleReturnSingleParameter lambda4 = (int a) -> {
System.out.println("这是一个参数,有返回值的方法, 返回值是:" + a);
return a;
}
int ret2 = lambda4.test(100);
System.out.println(ret2);
// 6. 实现多个参数,有返回值的函数式接口
SingleReturnSingleParameter lambda4 = (int a, int b) -> {
System.out.println("这是一个参数,有返回值的方法");
return a+b;
}
int ret3 = lambda4.test(100, 200);
System.out.println(ret3);
}
}
2.2、Lambda表达式的语法进阶
2.2.1、 参数部分的精简
-
参数的类型
-
由于在接口中的方法中,已经定义了每一个参数的类型是什么,而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类型需要和接口中的方法保持一致。因此,此时lambda表达式中的参数的类型可以省略不写。
-
注意事项:
- 如果需要省略参数的类型,要保证:要省略,每一个参数的类型都必须省略不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。
// 多个参数、无返回值的方法实现 NoneReturnMutipleParameter lambda1 = (a, b) -> { System.out.println("多个参数、无返回值方法的实现:参数a是 " + a + ", 参数b是 " + b); }
-
-
参数的小括号
-
如果方法的参数列表中的参数数量 有且只有一个,此时,参数列表的小括号是可以省略不写的
-
注意事项:
- 只有当参数的数量是一个的时候,多了、少了都不能省略
- 省略掉小括号的同时,必须要省略参数的类型
//有参、无返回值的方法实现 NoneReturnSingleParameter lambda2 = a -> { System.out.println("一个参数、无返回值方法的实现:参数是 " + a); }
-
2.2.2、方法体部分的精简
-
方法体大括号的精简
- 当一个方法体中的逻辑,有且只有一句的情况下, 大括号可以省略
//有参、无返回值的方法实现 NoneReturnSingleparameter lambda2= a -> System.out.println("一个参数、无返回值方法的实现:参数是 " + a);
-
return 的精简
- 如果一个方法中唯一的一条语句是一个返回语句,此时在省略掉大括号的同时,也必须省略掉return
SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;
3、函数引用
lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在1ambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,一般情况会单独的写一个方法。在lambda表达式中直接引用这个方法即可。
或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。
函数引用: 引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。
3.1、 静态方法的引用
-
语法:
- 类::静态方法
-
注意事项:
- 在引用的方法后面,不要添加小括号
- 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致
-
实例:
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; } } }
3.2、非静态方法的引用
-
语法:
- 对象::非静态方法
-
注意事项:
- 在引用的方法后面,不要添加小括号
- 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致
-
实例
public class Syntax2 { public static void main(String[] args){ // 非静态方法的引用, 需要使用对象来完成 SIngleReturnMutipleParameter lambda2 = new Calculator()::calculate; System.out.println(lambda1.test(10, 30)); } private static class Calculator{ public int calculate(int a, int b){ return a > b ? a-b : b-a; } } }
3.3、构造方法的引用
-
使用场景
- 如果某一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
-
语法:
- 类名::new
-
注意事项:
- 可以通过接口中的方法的参数,区分引用不同的构造方法
-
实例
public class Syntax3{ private static class Person{ String name; int age; public Person(){ System.out.println("Person类的无参构造方法执行了"); } public Person(String name){ System.out.println("Person类的有参构造方法执行了"); this.name = name; } public Person(String name, int age){ System.out.println("Person类的有两个参数构造方法执行了"); this.name = name; this.age = age; } } }
public class Lambda2{ @FunctionalInterface private interface GetPersonWithNoneParameter { Person get(); } @FunctionalInterface private interface GetPersonWithSingleParameter { Person get(String name); } @FunctionalInterface private interface GetPersonwithMutipleParamleter { Person get(String name,int age); } public static void main(String[] args) { // 1.使用lambda表达式,实现GetPersonWithNoneParameter接口 GetPersonWithNoneParameter getPerson = Person::new; // 2.使用lambda表达式,实现GetPersonwithSingleParameter接口 GetPersonwithSingleParameter getPerson2 = Person::new; // 3. 使用lambda表达式,实现GetPersonWithMutipleParameter接口 GetPersonwithMutipleParameter getPerson3 = Person::new; Person person = getPerson.get(); } }
3.4、对象方法的特殊引用
如果在使用 lambda 表达式,实现某些接口的时候,lambda表达式中包含了某一个对象,此时方法体重,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。其他的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。
public class Lambda3{
pirvate String name;
public void setName(String name){
this.name = name;
}
public String getName(){
retrun name;
}
}
public class Syntax {
public static void main(String[] args){
//如果对于这个方法的实现逻辑,是为了获取到对象的名字
GetField field = person -> person.getName();
//对于对象方法的特殊引用
GetField field = Person::getName;
//如果对于这个方法的实现逻辑,是为了给对象的某些属性进行赋值
SetField lambda = (p,n) -> p.setName(n);
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 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() {
}
}
4、Lambda表达式需要注意的问题
这里类似于局部内部类、匿名内部类,依然存在闭包的问题。
如果在lambda表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为 final。是一个常量,不能修改值。