Java lambda表达式,用户希望在代码中使用 lambda 表达式,使用某种 lambda 表达式语法,并将结果赋给函数式接口类型的引用。函数式接口是一种包含单一抽象方法(single abstract method)的接口。
Java lambda表达式 问题描述
用户希望在代码中使用 lambda 表达式。
Java lambda表达式 解决方案
使用某种 lambda 表达式语法,并将结果赋给函数式接口类型的引用。
Java lambda表达式 具体实例
函数式接口是一种包含单一抽象方法(single abstract method)的接口。类通过为接口中的所有方法提供实现来实现任何接口,这可以通过顶级类(top-level class)、内部类甚至匿名内部类完成。
以 Runnable
接口为例,它从 Java 1.0 开始就已存在。该接口包含的单一抽象方法是 run
,它不传入任何参数并返回 void
。Thread
类构造函数传入 Runnable
作为参数,例 1-1 显示了 Runnable
接口的匿名内部类实现。
例 1-1
Runnable
接口的匿名内部类实现
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new Runnable() { ➊
@Override
public void run() {
System.out.println(
"inside runnable using an anonymous inner class");
}
}).start();
}
}
➊ 匿名内部类
匿名内部类语法以关键字 new
开头,后面跟着 Runnable
接口名以及英文小括号,表示定义一个实现该接口但没有显式名(explicit name)的类。大括号({}
)中的代码重写 run
方法,将字符串打印到控制台。
例 1-2 中的代码采用 lambda 表达式,对例 1-1 进行了改写。
例 1-2 在
Thread
构造函数中使用 lambda 表达式
new Thread(() -> System.out.println(
"inside Thread constructor using lambda")).start();
上述代码使用箭头将参数与函数体隔开(由于没有参数,这里只使用一对空括号)。可以看到,函数体只包含一行代码,所以不需要大括号。这种语法被称为 lambda 表达式。注意,任何表达式求值都会自动返回。在本例中,由于 println
方法返回的是 void
,所以该表达式同样会返回 void
,与 run
方法的返回类型相匹配。
lambda 表达式必须匹配接口中单一抽象方法签名的参数类型和返回类型,这被称为与方法签名兼容。因此,lambda 表达式属于接口方法的实现,可以将其赋给该接口类型的引用。
例 1-3 显示了赋给某个变量的 lambda 表达式。
例 1-3 将 lambda 表达式赋给变量
Runnable r = () -> System.out.println(
"lambda expression implementing the run method");
new Thread(r).start();
Java 库中不存在名为
Lambda
的类,lambda 表达式只能被赋给函数式接口引用。
“将 lambda 表达式赋给函数式接口”与“lambda 表达式属于函数式接口中单一抽象方法的实现”表示相同的含义。我们可以将 lambda 表达式视为实现接口的匿名内部类的主体。这就是 lambda 表达式必须与抽象方法兼容的原因,其参数类型和返回类型必须匹配该方法的签名。注意,所实现方法的名称并不重要,它不会作为 lambda 表达式语法的一部分出现在代码中。
因为 run
方法不传入参数,并且返回 void
,所以本例特别简单。函数式接口 java.io.FilenameFilter
从 Java 1.0 开始就是 Java 标准库的一部分,该接口的实例被用作 File.list
方法的参数,只有满足该方法的文件才会被返回。
根据 Javadoc 的描述,FilenameFilter
接口包含单一抽象方法 accept
,它的签名如下:
boolean accept(File dir, String name)
其中,File
参数用于指定文件所在的目录,String
用于指定文件名。
例 1-4 采用匿名内部类来实现 FilenameFilter
接口,只返回 Java 源文件。
例 1-4
FilenameFilter
的匿名内部类实现
File directory = new File("./src/main/java");
String[] names = directory.list(new FilenameFilter() { ➊
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
System.out.println(Arrays.asList(names));
➊ 匿名内部类
在例 1-4 中,如果文件名以 .java
结尾,accept
方法将返回 true
,否则返回 false
。
而例 1-5 采用 lambda 表达式实现 FilenameFilter
接口。
例 1-5
FilenameFilter
接口的 lambda 表达式实现
File directory = new File("./src/main/java");
String[] names = directory.list((dir, name) -> name.endsWith(".java")); ➊
System.out.println(Arrays.asList(names));
}
➊ lambda 表达式
可以看到,代码要简单得多。参数包含在小括号中,但并未声明类型。在编译时,编译器发现 list
方法传入一个 FilenameFilter
类型的参数,从而获知其单一抽象方法 accept
的签名,进而了解 accept
的参数为 File
和 String
,因此兼容的 lambda 表达式参数必须匹配这些类型。由于 accept
方法的返回类型是布尔值,所以箭头右侧的表达式也必须返回布尔值。
如例 1-6 所示,我们也可以在代码中指定数据类型。
例 1-6 具有显式数据类型的 lambda 表达式
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) -> ➊
name.endsWith(".java"));
➊ 显式数据类型
此外,如果 lambda 表达式的实现多于一行,则需要使用大括号和显式返回语句,如例 1-7 所示。
例 1-7 lambda 代码块
File directory = new File("./src/main/java");
String[] names = directory.list((File dir, String name) -> { ➊
return name.endsWith(".java");
});
System.out.println(Arrays.asList(names));
➊ 代码块语法
这就是 lambda 代码块(block lambda)。在本例中,虽然代码主体只有一行,但可以使用大括号将多个语句括起来。注意,不能省略 return
关键字。
lambda 表达式在任何情况下都不能脱离上下文存在,上下文指定了将表达式赋给哪个函数式接口。lambda 表达式既可以是方法的参数,也可以是方法的返回类型,还可以被赋给引用。无论哪种情况,赋值类型必须为函数式接口。