lambda表达式
为什么要引入lambda表达式
为了给 Mouse 监听器添加自定义代码,我们定义了一个匿名内部类 MouseAdapter 并创建了它的对象,通过这种方式,我们将一些函数功能传给 addMouseListener 方法。
someObject.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//Event listener implementation goes here...
}
});
这个例子的特点是将一个代码块传递到某个对象(Listener),这个代码快会在将来的某个时间调用。
函数式语言提供了一种强大的功能——闭包,相比于传统的编程方法有很多优势,闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。Java 现在提供的最接近闭包的概念便是 Lambda 表达式,虽然闭包与 Lambda 表达式之间存在显著差别,但至少 Lambda 表达式是闭包很好的替代者。
lambda简介
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
lambda语法
1.参数、箭头以及一个表达式,如果需要多个表达式,使用{}
(String first,String second)->
{
if(first.length() < second.length() return -1;
else if(first.length() > second.length() return 1;
else return 0;
}
2.即使lambda没参数,仍然要提供空括号,就像无参数方法一样。
3.如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型。
Comparator<String> comp
= (first, second) //same as String first
-> first.length() - second.length();
为什么上面的可以推导出参数类型?因为因为Comparator是一个函数式接口,如下。
interface Comparator<T>{
int compare(T arg1, T arg2);
}
4.如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至可以省略小括号
ActionListener listener = event ->
System.out.println("Hello");
从上面例子也发现,无需制定lambda表达式的返回类型。
lambda表达式的应用
lambda转换为函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。例子
Arrays.sort(words,
(first, second) -> first.length() - second.length();
方法引用
可以使用方法引用作为函数式接口内的方法的方法体。
例如:
Timer t = new Timer(1000,System.out::println);
表达式System.out::println是一个方法引用,它等价于lambda表达式 x->System.out.println(x);
从例子可以发现,要用::分隔方法名和对象或类名。
主要有3种情况:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
在前两种情况中,方法引用等价于提供方法参数的lambda表达式。例子如下:
System.out::println//等价于 x -> System.out.println(x)
Math::pow//等价于(x, y) -> Math.pow(x, y)
对于第三种情况,即类名::实例方法这种格式,因为实例方法需要实例,所以第一个参数会成为方法的目标。例子:
String::compareToIgnoreCase//等价于(x, y) -> x.compareToIgnoreCase(y)
构造器引用
构造器引用和方法引用很类似,只不过方法名为new。
Person::new是Person构造器的一个引用。
具体调用哪一个构造器需要根据上下文推断。
可以用数组类型建立构造器引用。例如,int[]::new是一个构造器引用,它有一个参数:即数组的长度。这等价于x -> new int[x]
变量作用域
通常,我们希望能够在lambda表达式中访问外围方法或类中的变量。例如:
public static void repeatMessage(String text, int delay)
{
ActionListener listener = event ->
{
System.out.println(text);
Toolkit.getDefaultTolkit.beep();
};//这个分号不能省略
new Timer(delay, listener).start();
}
思考一个问题:lambda表达式代码可能会在repeatMessage调用很久以后才运行,而那个时候参数变量text已经不存在了。
所以lambda表达式的数据结构必须存储自由变量的值,我们称之为捕获。
注意点:
只能捕获值不会改变的变量
在lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的。
在lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。例如
public class Application()
{
public void init()
{
ActionListener listener = event ->
{
System.out.println(this.toString());
}
}
}
表达式this.toString()会调用Application对象的toString方法,而不是ActionListener实例的方法。
学会接收处理lambda表达式
使用lambda表达式的重点是延迟执行。
举例说明,自定义一个repeat方法,将这个动作和重复次数传递到到一个repeat方法:
repeat(10, () -> System.out.println("Hello World!"));
要接收这个lambda表达式,需要选择一个函数式接口这里选择使用Runnable接口
public static void repeat(int n, Runnabble action)
{
for(int i = 0; i < n; i++)
action.run();//此处执行lambda表达式的主体
}