APP下载

看完这篇文章 别说自己不会用Lambda表示式了

消息来源:baojiabao.com 作者: 发布时间:2024-05-20

报价宝综合消息看完这篇文章 别说自己不会用Lambda表示式了

标题虽然显得有点标题党的味道?但是看了文章内容之后就知道是干货了!这篇文章用来入门或者复习Java的Lambda表示式都是非常不错的。我在文章补充了少部分知识点,比如Java 开发使用手册对Arrays.asList()方法使用的介绍、IDEA在Lambda表示式这块的智慧提示......

1. 引言

在 Java 8 以前,若我们想要把某些功能传递给某些方法,总要去写匿名类。以前注册事件监听器的写法与下面的示例程式码就很像:

manager.addScheduleListener(new ScheduleListener() {

@Override

public void onSchedule(ScheduleEvent e) {

// Event listener implementation goes here...

}

});

这里我们添加了一些自定义程式码到 Schedule 监听器中,需要先定义匿名内部类,然后传递一些功能到 onSchedule 方法中。

正是 Java 在作为引数传递普通方法或功能的限制,Java 8 增加了一个全新语言级别的功能,称为 Lambda 表示式

2. 为什么 Java 需要 Lambda 表示式

Java 是面向物件语言,除了原始资料型别之处,Java 中的所有内容都是一个物件。而在函式式语言中,我们只需要给函式分配变数,并将这个函式作为引数传递给其它函式就可实现特定的功能。JavaScript 就是功能程式语言的典范(闭包)。

Lambda 表示式的加入,使得 Java 拥有了函数语言程式设计的能力。在其它语言中,Lambda 表示式的型别是一个函式;但在 Java 中,Lambda 表示式被表示为物件,因此它们必须系结到被称为功能界面的特定物件型别。

3. Lambda 表示式简介

Lambda 表示式是一个匿名函式(对于 Java 而言并不很准确,但这里我们不纠结这个问题)。简单来说,这是一种没有宣告的方法,即没有访问修饰符,返回值宣告和名称。

在仅使用一次方法的地方特别有用,方法定义很短。它为我们节省了,如包含类宣告和编写单独方法的工作。

Java 中的 Lambda 表示式通常使用语法是 (argument) -> (body),比如:

(arg1, arg2...) -> { body }

(type1 arg1, type2 arg2...) -> { body }

以下是 Lambda 表示式的一些示例:

(int a, int b) -> { return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

3.1 Lambda 表示式的结构

Lambda 表示式的结构:

Lambda 表示式可以具有零个,一个或多个引数。可以显式宣告引数的型别,也可以由编译器自动从上下文推断引数的型别。例如 (int a) 与刚才相同 (a)。引数用小括号括起来,用逗号分隔。例如 (a, b) 或 (int a, int b) 或 (String a, int b, float c)。空括号用于表示一组空的引数。例如 () -> 42。当有且仅有一个引数时,如果不显式指明型别,则不必使用小括号。例如 a ->return a*a。Lambda 表示式的正文可以包含零条,一条或多条语句。如果 Lambda 表示式的正文只有一条语句,则大括号可不用写,且表示式的返回值型别要与匿名函式的返回型别相同。如果 Lambda 表示式的正文有一条以上的语句必须包含在大括号(程式码块)中,且表示式的返回值型别要与匿名函式的返回型别相同。

4. 方法引用

4.1 从 Lambda 表示式到双冒号操作符

使用 Lambda 表示式,我们已经看到程式码可以变得非常简洁。

例如,要建立一个比较器,以下语法就足够了

Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

然后,使用型别推断:

Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

但是,我们可以使上面的程式码更具表现力和可读性吗?我们来看一下:

Comparator c = Comparator.comparing(Person::getAge);

使用 :: 运算子作为 Lambda 呼叫特定方法的缩写,并且拥有更好的可读性。

4.2 使用方式

双冒号(::)操作符是 Java 中的方法引用。当们使用一个方法的引用时,目标引用放在 :: 之前,目标引用提供的方法名称放在 :: 之后,即 目标引用::方法。比如:

Person::getAge;

在 Person 类中定义的方法 getAge 的方法引用。

然后我们可以使用 Function 物件进行操作:

// 获取 getAge 方法的 Function 物件

Function getAge = Person::getAge;

// 传引数呼叫 getAge 方法

Integer age = getAge.apply(p);

我们引用 getAge,然后将其应用于正确的引数。

目标引用的引数型别是 Function,T 表示传入型别,R 表示返回型别。比如,表示式 person -> person.getAge();,传入引数是 person,返回值是 person.getAge(),那么方法引用 Person::getAge 就对应着 Function 型别。

5. 什么是功能界面(Functional interface)

在 Java 中,功能界面(Functional interface)指只有一个抽象方法的界面。

java.lang.Runnable 是一个功能界面,在 Runnable 中只有一个方法的宣告 void run()。我们使用匿名内部类例项化功能界面的物件,而使用 Lambda 表示式,可以简化写法。

每个 Lambda 表示式都可以隐式地分配给功能界面。例如,我们可以从 Lambda 表示式建立 Runnable 界面的引用,如下所示:

Runnable r = () -> System.out.println("hello world");

当我们不指定功能界面时,这种型别的转换会被编译器自动处理。例如:

new Thread(

() -> System.out.println("hello world")

).start();

在上面的程式码中,编译器会自动推断,Lambda 表示式可以从 Thread 类的建构函式签名(public Thread(Runnable r) { })转换为 Runnable 界面。

@FunctionalInterface 是在 Java 8 中新增的一个新注解,用于指示界面型别,宣告界面为 Java 语言规范定义的功能界面。Java 8 还声明了 Lambda 表示式可以使用的功能界面的数量。当您注释的界面不是有效的功能界面时, @FunctionalInterface 会产生编译器级错误。

以下是自定义功能界面的示例:

package com.wuxianjiezh.demo.lambda;

@FunctionalInterface

public interface WorkerInterface {

public void doSomeWork();

}

正如其定义所述,功能界面只能有一个抽象方法。如果我们尝试在其中新增一个抽象方法,则会丢掷编译时错误。例如:

package com.wuxianjiezh.demo.lambda;

@FunctionalInterface

public interface WorkerInterface {

public void doWork();

public void doMoreWork();

}

错误:

Error:(3, 1) java: 意外的 @FunctionalInterface 注释

com.wuxianjiezh.demo.lambda.WorkerInterface 不是函式界面

在 界面 com.wuxianjiezh.demo.lambda.WorkerInterface 中找到多个非覆盖抽象方法

一旦定义了功能界面,我们就可以利用 Lambda 表示式呼叫。例如:

package com.wuxianjiezh.demo.lambda;

@FunctionalInterface

public interface WorkerInterface {

public void doWork();

}

class WorkTest {

public static void main(String[] args) {

// 通过匿名内部类呼叫

WorkerInterface work = new WorkerInterface() {

@Override

public void doWork() {

System.out.println("通过匿名内部类呼叫");

}

};

work.doWork();

// 通过 Lambda 表示式呼叫

// Lambda 表示式实际上是一个物件。

// 我们可以将 Lambda 表示式赋值给一个变数,就可像其它物件一样呼叫。

work = ()-> System.out.println("通过 Lambda 表示式呼叫");

work.doWork();

}

}

执行结果:

通过匿名内部类呼叫

通过 Lambda 表示式呼叫

6. Lambda 表示式的例子

6.1 执行绪初始化

执行绪可以初始化如下:

// Old way

new Thread(new Runnable() {

@Override

public void run() {

System.out.println("Hello world");

}

}).start();

// New way

new Thread(

() -> System.out.println("Hello world")

).start();

我们在使用IDEA的时候,如果写出Old way的程式码,IDEA会提示我们将其转换为Lambda表示式的形式,为IDEA点赞!

IDEA自动检测并提示转换为Lambda表示式形式

我们将游标移动到灰色程式码区域(new Runnable这里),使用快捷键alt+Enter就可以实现自动转换了。

自动转换为Lambda表示式

6.2 事件处理

事件处理可以用 Java 8 使用 Lambda 表示式来完成。以下程式码显示了将 ActionListener 新增到 UI 元件的新旧方式:

// Old way

button.addActionListener(new ActionListener() {

@Override

public void actionPerformed(ActionEvent e) {

System.out.println("Hello world");

}

});

// New way

button.addActionListener( (e) -> {

System.out.println("Hello world");

});

6.3 遍例输出(方法引用)

输出给定阵列的所有元素的简单程式码。请注意,还有一种使用 Lambda 表示式的方式。

// old way

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

for (Integer n : list) {

System.out.println(n);

}

// 使用 -> 的 Lambda 表示式

list.forEach(n -> System.out.println(n));

// 使用 :: 的 Lambda 表示式

list.forEach(System.out::println);

这里顺便补充一下Arrays.asList()方法。Arrays.asList()将阵列转换为集合后,底层其实还是阵列,《阿里巴巴》Java 开发使用手册对于这个方法有如下描述:

阿里巴巴Java开发手-Arrays.asList()方法

如何正确的将阵列转换为ArrayList?可以像下面这样(参见:stackoverflow- https://dwz.cn/vcBkTiTW)

List list = new ArrayList(Arrays.asList("a", "b", "c"))

6.4 逻辑操作

输出通过逻辑判断的资料。

package com.wuxianjiezh.demo.lambda;

import java.util.Arrays;

import java.util.List;

import java.util.function.Predicate;

public class Main {

public static void main(String[] args) {

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

System.out.print("输出所有数字:");

evaluate(list, (n) -> true);

System.out.print("不输出:");

evaluate(list, (n) -> false);

System.out.print("输出偶数:");

evaluate(list, (n) -> n % 2 == 0);

System.out.print("输出奇数:");

evaluate(list, (n) -> n % 2 == 1);

System.out.print("输出大于 5 的数字:");

evaluate(list, (n) -> n > 5);

}

public static void evaluate(List list, Predicate predicate) {

for (Integer n : list) {

if (predicate.test(n)) {

System.out.print(n + " ");

}

}

System.out.println();

}

}

执行结果:

输出所有数字:1 2 3 4 5 6 7

不输出:

输出偶数:2 4 6

输出奇数:1 3 5 7

输出大于 5 的数字:6 7

6.4 Stream API 示例

java.util.stream.Stream界面 和 Lambda 表示式一样,都是 Java 8 新引入的。所有 Stream 的操作必须以 Lambda 表示式为引数。Stream 界面中带有大量有用的方法,比如 map() 的作用就是将 input Stream 的每个元素,对映成output Stream 的另外一个元素。

下面的例子,我们将 Lambda 表示式 x -> x*x 传递给 map() 方法,将其应用于流的所有元素。之后,我们使用 forEach打印列表的所有元素。

// old way

List list = Arrays.asList(1,2,3,4,5,6,7);

for(Integer n : list) {

int x = n * n;

System.out.println(x);

}

// new way

List list = Arrays.asList(1,2,3,4,5,6,7);

list.stream().map((x) -> x*x).forEach(System.out::println);

下面的示例中,我们给定一个列表,然后求列表中每个元素的平方和。这个例子中,我们使用了 reduce() 方法,这个方法的主要作用是把 Stream 元素组合起来。

// old way

List list = Arrays.asList(1,2,3,4,5,6,7);

int sum = 0;

for(Integer n : list) {

int x = n * n;

sum = sum + x;

}

System.out.println(sum);

// new way

List list = Arrays.asList(1,2,3,4,5,6,7);

int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();

System.out.println(sum);

7. Lambda 表示式和匿名类之间的区别

this 关键字。对于匿名类 this 关键字解析为匿名类,而对于 Lambda 表示式,this 关键字解析为包含写入 Lambda 的类。编译方式。Java 编译器编译 Lambda 表示式时,会将其转换为类的私有方法,再进行动态系结。
2019-09-11 19:53:00

相关文章