lambda
表达式是 Java 8 的一个新特性,可以取代大部分的匿名内部类,简化了匿名委托的使用,让你让代码更加简洁,优雅。
比较官方的定义是这样的:
lambda 表达式是一个可传递的代码块(或者匿名函数),可以在以后执行一次或多次。
这个匿名函数没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。lambda
表达式也可称为闭包
。
在 Java 中传递一个代码段并不容易,你不能直接传递代码段。Java 是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。接下来就看看 Java 是怎么来处理代码块的。
ava 中有一个 Comparator
接口用来排序。这是 Java 8 以前的代码形式:
public class LengthComparator implements Comparator
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
}
String[] strArr = new String[]{"abcde", "qwer"};
Arrays.sort(strArr, new LengthComparator());
我们需要定义一个实现了 Comparator
接口的类,并实现里面的 compare()
方法,然后把这个类当做参数传给 sort 方法。
而我们使用 lambda
表达式就可以这样来写:
Arrays.sort(strArr, (String a, String b) -> a.length() - b.length());
其中的 (String a, String b) -> a.length() - b.length()
就是一个 lambda
表达式。
lambda 表达式就是一个代码块,以及必须传入代码的变量规范
lambda
表达式的一些例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
再看一个例子加深理解:
// 用匿名内部类的方式来创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
});
// 使用Lambda来创建线程
new Thread(() -> System.out.println("hello world"));
注意:
如果一个 lambda 表达式只在某些分支返回一个值,而另外一些分支不返回值,这是不合法的。
例如,(int x) -> { if (x>= 0) return 1; } 就不合法
Java 中有很多封装代码块的接口,比如上面的 Comparator
或 ActionListener
,lambda 表达式与这些接口是兼容的。
但并不是所有的接口都可以使用 lambda 表达式来实现。lambda 规定接口中只能有一个需要被实现的方法(只包含一个抽象方法),不是规定接口中只能有一个方法。 这种接口就称为函数式接口。
Java 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
上面的 Comparator
和 ActionListener
,包括 Runnable
就是只有一个需要被实现的方法的接口。即函数式接口。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface Runnable
is used…
*/
public abstract void run();
}
我们来观察下 Runnable
接口,接口上面有一个注解 @FunctionalInterface
。
通过观察 @FunctionalInterface
这个注解的源码,可以知道这个注解有以下特点:
我们再来看一下 Comparator
接口的源码:
@FunctionalInterface
public interface Comparator
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
}
这里只贴出来了部分代码,可以看到排除掉接口的中的静态方法、默认方法和覆盖的 Object
中的方法之后,就剩下一个抽象方法 int compare(T o1, T o2);
符合 lambda
函数式接口的规范。
JDK 中提供一些其他的函数接口如下:
Java awt 包中有一个 Timer 类,作用是经过一段时间就执行一次。 用 lambda 表达式来处理:
Timer timer = new Timer(1000, event -> System.out.println("this time is " + new Date()));
这里面的 lambda 表达式可以这样表示:
Timer timer = new Timer(1000, System.out::println);
表达式 System.out::println
就是一个方法引用(method reference),它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。
方法引用需要用 ::
运算符分隔方法名与对象或类名。主要有3种情况:
1. object::instanceMethod 2. Class::instanceMethod 3. Class::staticMethod
具体解释这里不再叙述,有兴趣的可以看看《Java 核心技术卷1》。
注意:
只有当 lambda 表达式的体只调用一个方法而不做其他操作时,才能把 lambda 表达式重写为方法引用
构造器引用与方法引用很类似,只不过方法名 new。例如,Person::new 是 Person 构造器的一个引用。
假如有一个字符串列表。可以把它转换为一个 Person 对象数组,为此要在各个字符串上调用构造器:
ArrayList
Stream
List
其中,map 方法会为各个列表元素调用 Person(String) 构造器。这里的 stream
和 map
会在下一篇博客中学习,这篇暂不讨论。
看下面这个例子:
public static void repeatMessage(String text, int delay){
ActionListener listener = event ->
{
System.out.printLn(text);
};
new Timer(delay, listener).start();
}
// 调用
repeatMessage("Hello", 1000);
可以看到, lambda
表达式可以捕获外围作用域中变量的值。在 Java 中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在 lambda
表达式中,只能引用值不会改变的变量。这是为了保证并发执行过程的安全。
lambda 表达式中捕获的变量必须实际上是事实最终变量。就是这个变量初始化之后就不会再为它赋新值。
参考资料:
1.《Java 核心技术卷1-2》
手机扫一扫
移动阅读更方便
你可能感兴趣的文章