Java中的Lambda表达式

Posted 小二玩编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的Lambda表达式相关的知识,希望对你有一定的参考价值。

Lambda 表达式是 Java 1.8 中的新特性之一,被称之为闭包,它的目的就是为了减少代码量让接口变得更简洁,也可以说是编译器推断并帮我们转换包装为常规的代码,所以我们可以使用更少的代码来实现同样的功能;lambda表达式是一个匿名方法,可以使用这个匿名方法实现接口中的函数,lambda表达式就和方法一样,它为开发者提供了一个参数列表和一个使用这些参数的主体;这么有意思的新特性,那下面就让我们来探索它的语法吧。

1、Lambda 表达式适用的接口

lambda 表达式在一定程度上简化接口的实现,但并非所有的接口都可以使用 lambda 表达式,并没有我们想像的那么简单;lambda 表达式对接口的使用是有要求的,lambda 表达式是一个匿名的函数,当接口中必须要实现的方法超过一个的时候,lambda 表达式就不使用了,lambda 表达式只适用于函数式接口。

2、函数式接口

一个接口中,存在抽象的方法,要实现的抽象方法是必须的且只有一个,那么我们就称它为函数式接口。有一个注解是可以检查接口是不是函数式接口,那便是 @FunctionalInterface,它和注解 @Override 检查的效果是一个的,都是检查编译语法是否有错,只不过@Override是用于方法,而 @FunctionalInterface 用于接口。

下面用 @FunctionalInterface 举个例子,如果报红线则证明不是函数式接口,否则是函数式接口

@FunctionalInterface
interface MyInterface1 {
 void test();
 }

我们来写几个接口例子,看看哪些是函数式接口

 /**
 * 这个接口,有且只有一个方法,是接口必须实现的,所以它是函数式接口
 */
 interface MyInterface1 {
 void test();
 }
 
 /**
 * 这个接口,有2个方法,都是接口必须实现的,所以它不是函数式接口
 */
 interface MyInterface2 {
 void test();
 void test2();
 }
 
 /**
 * 这个接口,没有一个方法是接口必须实现的,所以它不是函数式接口
 */
 interface MyInterface3 {
 }
 
 /**
 * 这个接口,虽然自己本身没有定义一个抽象方法,
 * 但可以从父类那里继承一个抽象方法,
 * 所以它是函数式接口
 */
 interface MyInterface4 extends MyInterface1 {
 }
 
 /**
 * 这个接口,虽然有2个方法,但是 default 标签的方法在子类中不是必须实现的,
 * 实现的方法只有 test(),所以它是函数式接口
 */
 interface MyInterface5 {
 void test();
 default void test2(){
 };
 }
 
 /**
 * 这个接口,虽然有2个方法,但是该接口继承于 Object,
 * 在父类 Object 那里已经实现了 toString() 方法,
 * 因此 toString() 不是必须要实现的,
 * 实现的方法只有 test(),它是函数式接口
 */
 interface MyInterface6 {
 void test();
 String toString();
 }

3、Lambda 表达式的基本语法

lambda 表达式是匿名函数,lambda 不像普通的方法那样有一个明确的名称,写得少而想得多;lambda 函数不像方法那样属于某个特定的类。但和方法一样,Lambda 有参数列表、函数主体,还可能有返回类型或者可以抛出的异常列表;lambda 表达式可以作为参数传递给方法或存储在变量中,无需像匿名类那样写很多模板代码;lambda 表达式的基本语法 (参数列表) -> {方法体},其中参数列表和方法体里的返回值不是必须的。

下面举几个小例子加深对 lambda 表达式基本语法的理解

ps: 在 androidStudio 工具写的代码

(1)声明几个函数式接口

 /**
 * 无参数无返回值
 */
 interface MyInterface7 {
 void test();
 }
 
 /**
 * 无参数有返回值
 */
 interface MyInterface8 {
 int test();
 }
 /**
 * 有参数无返回值
 */
 interface MyInterface9 {
 void test(int x,int y);
 }
 
 /**
 * 有参数有返回值
 */
 interface MyInterface10 {
 int test(int x,int y);
 }

(2)用 lambda 表达式对接口进行实现

 /**
 * 用 lambda 表达式实现 无参数无返回值的接口
 */
 MyInterface7 myInterface7 = () -> {
 System.out.println("用 lambda 表达式实现 无参数无返回值的接口");
 };
 
 /**
 * 用 lambda 表达式实现 无参数有返回值的接口
 * lambda 表达式的返回值类型和接口方法中的返回值类型一样
 */
 MyInterface8 myInterface8 = () -> {
 System.out.println("用 lambda 表达式实现 无参数有返回值的接口");
 return 0;
 };
 
 /**
 * 用 lambda 表达式实现 有参数无返回值的接口
 * 这里的 lambda 表达式函数中参数的个数和参数类型要与接口中方法的参数个数和参数类型一一对应
 */
 MyInterface9 myInterface9 = (int x,int y) -> {
 int sum = x + y;
 System.out.println("用 lambda 表达式实现 有参数无返回值的接口,sum的值为" + sum);
 };
 
 /**
 * 用 lambda 表达式实现 有参数有返回值的接口
 * 这里的 lambda 表达式函数中参数的个数和参数类型要与接口中方法的参数个数和参数类型一一对应
 * lambda 表达式的返回值类型和接口方法中的返回值类型一样
 */
 MyInterface10 myInterface10 = (int x,int y) -> {
 return x - y;
 };

(3)对接口中的方法进行调用,假设 x = 1,y = 2

myInterface7.test();
int result = myInterface8.test();
myInterface9.test(1,2);
int result2 = myInterface10.test(1,2);

4、对可以简化的 lambda 表达式进行简化

在上面列举的实现接口的 lambda 表达式例子中,虽然达到了和匿名内部类一样的作用,但它的实现代码还是不够简洁,在一定程度上某些特殊的 lambda 表达式还是可以进行简化的。

4、1 参数的类型简化

在接口中的方法,如果该方法存在参数,那么参数的类型是已知的;在 lambda 表达式实现接口的过程中,参数的个数和类型和接口方法中参数的个数、类型都是一一对应的,所以在 lambda 表达式中所有参数的类型可以不写。下面就拿上面列举的 MyInterface9 的实现举个例子,如下所示:

 /**
 * 这里省略了全部的参数类型,
 * 注意:参数类型要么一个都不省略,要么全省略,否则语法报错
 */
 MyInterface9 myInterface9 = (x,y) -> {
 int sum = x + y;
 System.out.println("用 lambda 表达式实现 有参数无返回值的接口,sum的值为" + sum);
 };

4、2 参数的小括号简化

如果接口方法中存在参数且只有一个,那么在 lambda 表达式中可以省略小括号,注意不能省略掉参数;下面举个例子:

 interface MyInterface11 {
 void test(int x);
 }
 
 /**
 * 没有简化 lambda 表达式小括号
 */
 MyInterface11 myInterface11_1 = (x) -> {
 System.out.println("传入的参数为:" + x);
 };
 
 /**
 * 简化 lambda 表达式小括号之后,参数 x 一定要写上
 */
 MyInterface11 myInterface11_2 = x -> {
 System.out.println("传入的参数为:" + x);
 };

4、3 lambda 表达式方法体大括号的简化

当 lambda 表达式的方法体只有一条语句时,可以省略大括号;拿上面 MyInterface11 的实现举个例子,如下所示:

 /**
 *lambda 表达式的方法体只有一条语句,可以省略大括号
 */
 MyInterface11 myInterface11_3 = x ->
 System.out.println("传入的参数为:" + x);

4、4 lambda 表达式方法体 return 返回值语句的简化

当 lambda 表达式的方法体只有一条 return 返回值语句时,可以省略 return 关键字,同时一定要省略大括号;拿上面 MyInterface10 的实现举个例子,如下所示:

MyInterface10 myInterface10 = (int x, int y) ->
 x - y;

5、lambda 表达式对方法的引用

如果在 lambda 表达式中出现了很复杂的逻辑,会对程序的可读性造成很大的影响,这里就不推荐在 lambda 表达式中直接处理逻辑了,一般我们会单独的写一个方法,直接在 lambda 表达式中引用这个方法就可以了;引用一个存在的方法,让它完成 lambda 表达式实现接口的功能;这里需要注意几点:

(1)、如果该接口的方法有返回值,那么 lambda 表达式引用的方法必须有返回值,并且与接口方法中的返回值类型一样。

(2)、如果该接口的方法没有参数,那么 lambda 表达式引用的方法也必须没有参数。

(3)、如果该接口的方法有参数,那么 lambda 表达式引用的方法必须有参数,且参数的个数和参数的类型要和接口中参数的个数以及类型一一对应。

5、1 非静态方法的引用

它的引用规则有2种写法:

(1)、对象 :: 非静态方法

(2)、this :: 非静态方法

下面通过代码举个例子

(1)定义几个接口

 /**
 * 无参数无返回值的接口
 */
 interface MyInterface12 {
 void test();
 }
 
 /**
 * 无参数有返回值的接口
 */
 interface MyInterface13 {
 int test();
 }
 /**
 * 有2个参数无返回值的接口
 */
 interface MyInterface14 {
 void test(int x,int y);
 }
 
 /**
 * 有2个参数有返回值的接口
 */
 interface MyInterface15 {
 int test(int x,int y);
 }

(2)定义 lambda 表达式要引用的方法的对象的所属类

 static class MyClass {
 public void test() {
 System.out.println("执行MyClass类对象的test()方法");
 }
 public int test2() {
 System.out.println("执行MyClass类对象的test2()方法");
 return 1;
 }
 public void test3(int x,int y) {
 System.out.println("执行MyClass类对象的test3()方法");
 }
 public int test4(int x,int y) {
 System.out.println("执行MyClass类对象的test()方法");
 return 1;
 }
 } 

(3)用 lambda 表达式对方法进行引用

 MyClass myClass = new MyClass();
 
 /**
 * lambda 表达式对方法的引用实现了无参数无返回值的接口
 * 注意方法后面不能出现大括号
 */
 MyInterface12 myInterface12 = myClass ::test;
 
 /**
 * lambda 表达式对方法的引用实现了无参数有返回值的接口
 * 注意方法后面不能出现大括号
 */
 MyInterface13 myInterface13 = myClass ::test2;
 
 /**
 * lambda 表达式对方法的引用实现了2个参数无返回值的接口
 * 注意方法后面不能出现大括号
 */
 MyInterface14 myInterface14 = myClass ::test3;
 
 /**
 * lambda 表达式对方法的引用实现了2个参数有返回值的接口
 * 注意方法后面不能出现大括号
 */
 MyInterface15 myInterface15 = myClass ::test4;

(4)调用接口方法,假设 x = 1,y = 2,则

 myInterface12.test();
 int result = myInterface13.test();
 myInterface14.test(1,2);
 int result2 = myInterface15.test(1,2);

5、2 对静态方法的引用

lambda 表达式对静态方法的引用和 lambda 表达式对非静态方法的引用是一样的,只不过不同的是静态方法含有 stitac 关键字以及用类名调用方法,例子就不再写了,语法如下所示:

类名 :: 静态方法

5、3 对构造方法的引用

lambda 表达式对构造方法引用时会将某一个类型的对象返回给接口;如果一个函数式接口定义的方法只是为了得到某一个类型的对象,我们可使用对构造方法的引用,进而简化这个方法的实现,它的语法如下所示:

类名 :: new

下面通过代码举个例子

(1)定义一个类

 class MyClass2 {
 private String name;
 private int age;
 public MyClass2(){
 System.out.println("执行了MyClass2类对象的无参数构造方法");
 }
 public MyClass2(String name,int age){
 System.out.println("执行了MyClass2类对象的有2个参数的构造方法," +
 "一个参数为:" + name + ",一个参数为:" + age);
 }
 
 public void fun(int x,int y) {
 System.out.println("调用了MyClass2类对象的fun方法,参数x = " + x + ",参数y = " + y);
 }
 
 public void fun2() {
 System.out.println("调用了MyClass2类对象的无参数fun方法");
 }
 }

(2)定义几个接口

 /**
 * 没有参数没有返回值
 */
 interface MyInterface16 {
 void test();
 }
 
 /**
 * 没有参数有 MyClass2 类型对象返回值
 */
 interface MyInterface17 {
 MyClass2 test();
 }
 
 /**
 * 有2个参数以及有 MyClass2 类型对象返回值
 */
 interface MyInterface18 {
 MyClass2 test(String name,int age);
 }

(3)用 lambda 表达式对构造方法引用进而实现接口

 /**
 *  引用了 MyClass2() 构造方法,new 后面不能有大括号
 */
 MyInterface16 myInterface16 = MyClass2 ::new;
 
 /**
 *  引用了 MyClass2() 构造方法,new 后面不能有大括号
 */
 MyInterface17 myInterface17 = MyClass2 ::new;
 
 /**
 *  引用了 MyClass2(String name,int age) 构造方法,new 后面不能有大括号
 */
 MyInterface18 myInterface18 = MyClass2 ::new;

(4)对接口中的方法进行调用

 /**
 * 这里接口中的方法没有返回值
 */
 myInterface16.test();
 MyClass2 myClass2_1 = myInterface17.test();
 MyClass2 myClass2_2 = myInterface18.test("小二",21);

6、对方法中参数含有对象的引用

如果一个函数式接口中的方法含有一个对象,那在使用 lambda 表达式的时候参数也是包含了这个对象的,直接使用这个对象调用它的某一个方法,就可以完成逻辑的处理,其他参数可作为调用方法的参数,注意:第一个参数必须是对象;对于它的用法,下面举个例子

(1)对方法中参数含有对象的引用有2种写法,用上面例子中 MyClass2 类对象作为参数,先定义一个接口

 interface MyInterface19 {
 void test(MyClass2 mc2,int x,int y);
 }

(2)用 lambda 表达式对实现接口,有2种写法

 /**
 * 在 lambda 表达式实现的过程中,
 * 接口方法种除了第一个参数,所有的参数个数以及类型要和对象方法中参数的个数以及类型一一对应
 */
 MyInterface19 myInterface19_1 = (mc2, x, y) -> mc2.fun(x,y);
 
 /**
 * 在 lambda 表达式实现的过程中,
 * 接口方法种除了第一个参数,所有的参数个数以及类型要和对象方法中参数的个数以及类型一一对应
 */
 MyInterface19 myInterface19_2 = MyClass2 ::fun;

(3)对接口进行调用,假设x = 1,y = 2,则

 myInterface19_1.test(myClass2,1,2);
 myInterface19_2.test(myClass2,1,2);

7、局部变量使用在 lambda 表达式时,默认为副本

局部声明的变量,在使用 lambda 表达式时,不可以再更改,它被 final 修饰;就拿上面的 MyInterface7 举个例子:

 private void test7() {
 int num = 2;
 MyInterface7 myInterface7 = () -> {
 int j = num * 3;
 };
 
 /**
 * error,不可以再更改了
 */
 num = 5;
 }

本篇文章写到这里就结束了,由于技术水平有限,文章中难免会有错误,欢迎大家批评指正,另外附上 demo 地址:https://github.com/Yubao1/Lam...

以上是关于Java中的Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

Java8中的Lambda表达式

Java 8中的lambda表达式

Java8和Scala中的Lambda表达式

JAVA由一个将JSONArray转成Map的需求引发的lambda语法的学习

Java中的Lambad表达式

JAVA——进阶语法——使用lambda表达式(λ)进行函数式编程,对匿名内部内代码简化思路及案例在线程中的应用