直观认识一下 Lambda 表达式:
简单的说就是一种紧凑的、传递行为的方式。也可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
通过代码展示一下:
// 使用匿名内部类的方式
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button Pressed");
}
});
// 使用 lambda 表达式
jButton.addActionListener(e -> System.out.println("Button Pressed"));
它们实现的是同样的功能,但是和传入一个实现某接口的对象不同,我们传入的是一段代码块——一个没有名字的函数(其实在 java 中 Lambda 表达式就是一个对象,但是在其它语言中它是一个函数)。Lambda 表达式中的 -> (箭头符号)将参数和 Lambda 表达式主体分开,主体就是要执行的动作。
为何需要 lambda 表达式?
在 Java 7之前,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法。但是在 JavaScript 中,方法参数是一个函数,返回值是另一个函数的情况是非常常见的;JavaScript 是一门非常典型的函数式语言。所以为了实现这个功能,出现了 Lambda 表达式这个概念。
引入函数式接口:
什么是函数式接口?
1. 如果一个接口只有一个抽象方法,那么该就接口就是一个函数式接口。
2. 如果我们在某个接口上声明了 FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口。
3. 如果某个接口只有一个抽象方法,但我们并没有给该接口声明 FunctionalInterface 注解,那么编译器依旧会将该接口看作是函数式接口。
通过 java 源码看看怎么定义的函数式接口:
/* ActionListener.java */
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
* @param e the event to be processed
*/
public void actionPerformed(ActionEvent e);
}
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我们可以看到 Consumer<T> 接口中不仅有一个抽象方法,还有一个 default 定义的实现方法,在 java8 之前一个接口中只能有抽象方法,不能有实现方法,但是 java8 之后为了兼容它之前版本的接口定义并且能在接口中有实现方法,最后就添加使用 default 关键字来定义的实现方法(也叫默认方法)来与普通实现方法进行区分。
另外我们还要注意,如果在一个接口中覆盖 java.lang.Object 中的方法,这个并不会在抽象方法的计数中被包含。举个例子:
// 这个是没问题的
@FunctionalInterface
public interface InterfaceTest {
void test();
}
// 由于 FunctionalInterface 注解的接口只能有一个抽象方法,所以这个会报错
@FunctionalInterface
public interface InterfaceTest {
void test();
void test2();
}
// 这个也是没问题的,因为 toString() 方法是继承自 java.lang.Object 的,并不包含在该接口抽象方法的计数内,所以这个接口仍然只是有一个抽象方法
@FunctionalInterface
public interface InterfaceTest {
void test();
@Override
String toString();
}
// 这个也是错误的,因为 InterfaceTest1 接口继承自 InterfaceTest 接口,但是 InterfaceTest 中已经有一个 test() 抽象方法了,再加上 test2() 方法就变成两个抽象方法了
@FunctionalInterface
public interface InterfaceTest1 extends InterfaceTest{
void test2();
}
函数式接口和 Lambda 表达式的关系:
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
通过官方文档可以知道,函数式接口的实例对象可以通过 Lambda 表达式、方法引用、构造函数引用来实现。
综上,我们可以了解到,其实 lambda 表达式在 java 中就是函数式接口的一个实现,所以 Lambda 表达式在 java 中代表的就是一个对象实例。eg:
jButton.addActionListener(e -> System.out.println("Button Pressed"));
就等价为:
ActionListener actionListener = e -> System.out.println("Button Pressed");
jButton.addActionListener(actionListener);
Lambda 表达式的几种表现形式以及如何书写:
首先,我们知道了,所谓 Lambda 表达式其实就是函数式接口的实现,接下来看一下不同情况下,它的表达形式以及含义:
① 无参 Lambda 表达式(执行体为一行代码时)---无返回值
Runable 函数式接口的实现(jdk 源码):
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
可以看到抽象方法 run() 是一个无参并且无返回值的,所以就可以这样写:
Runnable noArguments = () -> System.out.println("Hello World!");
Lambda 表达式左边参数 () 代表无参,箭头符号右边为主体---执行体。
② 无参 Lambda 表达式(执行体为多行代码时) --- 无返回值
Runnable noArguments = () -> {
System.out.println("Hello World!");
System.out.println("Hello World!");
};
Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号 { } 将代码块括起来。该代码块和普通方法遵循的规则一样,都可以使用 return 返回或抛出异常来退出。当然一行代码中也可以使用 { }。
③ 包含一个参数的 Lambda 表达式 --- 无返回值
ActionListener 函数式接口的实现:
ActionListener oneArgument = e -> System.out.println("button clicked");
由于 ActionListener 接口中的抽象方法参数只有一个,所以箭头符号的前面的 e 可以省略括号,并且名称也可以是任意名称。为了增加可读性,也可以给参数加上类型:
ActionListener oneArgument = (ActionEvent e) -> System.out.println("button clicked");
④ 包含两个及以上的参数且有返回值的 lambda 表达式
函数式接口 InterfaceTest:
@FunctionalInterface
public interface InterfaceTest {
int test(int a, int b);
}
Lambda 表达式实现以及使用:
InterfaceTest add = (x, y) -> x + y;
int result = add.test(1, 2);
System.out.println(result);
这里一定要知道,这个 Lambda 表达式就是 Interface 接口的实现,所以使用这个实现就可以调用相应的方法并得到结果。