关于lambda表达式,labmda expressions in java 8 这里有比较简单的解释:
在java 里面,如果一个接口只有一个方法,那么这个接口就叫做function interface
; 当然,我们可以按照传统方式去定义一个新的class来实现这个接口,但是,在有了lambda以后,我们也可以写一个lambda来完成。labmda表达式其实就是function interface
的一种特殊的实现方式, 我们可以简单理解为,一个function interface
的匿名实现类。所以,我们如果需要使用labmda表达式,就一定需要预先定义对应的function interface
,然后,在需要的时候,定义对应的 labmda表达式,作为对这个interface的实现;
labmda表达式其实就是一个function interface
的实现类,但是这个实现类是没有名字的,可以认为是一个function interface
的匿名实现类;
我们遇到的最常见的function interface
,就是Runnable
接口,所以,为了使用Runnable
,我们既可以按照传统方式去定义一个实现了Runnable
接口的类:
public class MyRunnable implements Runnable{
@Override
public void run()
{
}
}
同时,也可以使用lambda表达式来实现:
Runnable myRunnable = () -> {
}
有了lambda表达式,我们在书写程序的时候变得更加方便快捷,带来了巨大的自由。
比如,我们在一个方法里面正在开心地写着一段逻辑,忽然发现我们对于某一个简单接口的实现类还没有写,这时候,如果不使用labmda表达式,我们需要去new一个class,让这个class实现该接口;如果这个接口有多个不同的实现,那么我们就需要新建对应的多个class,此时,我们可能会因为这个class应该叫什么名字,以及这个class应该放在哪个package下面而焦虑;
但是,有了lambda表达式以后,我们可以随手就把这个接口给实现了,不用想大多数情况下,我们可以在一个方法里面去实现我们需要的labmda表达式:
public void addQueryInfoStateChangeListener(StateChangeListener stateChangeListener)
{
AtomicBoolean done = new AtomicBoolean();
StateChangeListener> fireOnceStateChangeListener = finalQueryInfo -> {
if (finalQueryInfo.isPresent() && done.compareAndSet(false, true)) {
stateChangeListener.stateChanged(finalQueryInfo.get());
}
};
finalQueryInfo.addStateChangeListener(fireOnceStateChangeListener);
fireOnceStateChangeListener.stateChanged(finalQueryInfo.get());
}
这里的StateChangeListener
就是一个function interface
,只有一个方法定义:
public interface StateChangeListener
{
void stateChanged(T newState);
}
这里很随意地就在addQueryInforStateChangeListener()
中实现了接口StateChangeListener
, 而且是按需实现,这个实现逻辑由于写在方法addQueryInforStateChangeListener()
中,是因为这段实现只在方法addQueryInforStateChangeListener()
中会发生,对于不关心这段实现的其它代码来说,这段实现完全隐藏;
我们也必须看到,lambda表达式和接口实现类虽然功能上相同,但是并不是说二者完全可以相互取代,因为他们的试用场景是不同的:一般情况下,如果这个接口的实现需要被很多业务共享,那么我们当然使用类定义的方式来实现接口,但是,如果这段接口的实现逻辑只是在当前场景下使用,那么我们完全没必要定义一个class暴露给所有人,只需要在当前位置立刻实现该接口就行,这个实现只在当前作用域(当前类、当前方法)中可见,其他人甚至不知道我们在这里还实现了这个接口;
lambda表达式带来了coding上的巨大遍历,但是,也带来了代码和异常堆栈的可读性的降低,这甚至让很多人在coding的时候尽量不适用lambda表达式。
在没有正则表达式之前,我们看到一个完整的异常堆栈以后,可以有一下几个特点:
比如,以下是一个没有lambda表达式的系统堆栈:
java.io.IOException: Wait for ZKClient creation timed out
at org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore$ZKAction.runWithCheck(ZKRMStateStore.java:1119)
at org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore$ZKAction.runWithRetries(ZKRMStateStore.java:1155)
at org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore.doStoreMultiWithRetries(ZKRMStateStore.java:947)
可以看到,堆栈信息非常明确,一行堆栈一个方法。如果我们对系统比较熟悉,我们甚至可以不看代码,直接从堆栈信息(类和方法的米那个字)上直观看到异常发生的时候的调用逻辑;
但是,如果堆栈信息中出现了lambda表达式,情况将不再那么简单,我们的堆栈信息变成了这样:
com.facebook.presto.event.query.QueryMonitor.queryCompletedEvent(QueryMonitor.java:282)
com.facebook.presto.execution.SqlQueryManager.lambda$createQueryInternal$4(SqlQueryManager.java:502)
com.facebook.presto.execution.QueryStateMachine.lambda$addQueryInfoStateChangeListener$10(QueryStateMachine.java:865)
com.facebook.presto.execution.StateMachine.lambda$fireStateChanged$0(StateMachine.java:230)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:748)
在代码理解和通过堆栈对代码进行追踪上,lambda的出现让代码阅读和错误追踪变得困难很多,这主要是因为:
下一结,关于lambda表达式的debug方式中,我们将具体来讲;
堆栈的每一行最后的行号,是这个lambda表达式中的某一个语句所在的那一行,但是这一行只是对应了这个lambda表达式的定义, 但是,调用堆栈并不是从这一行上方顺序执行下来的:
例如,这一行堆栈信息:
com.facebook.presto.execution.QueryStateMachine.lambda$addQueryInfoStateChangeListener$10(QueryStateMachine.java:865)
它对应的代码位置是:
public void addQueryInfoStateChangeListener(StateChangeListener stateChangeListener)
{
AtomicBoolean done = new AtomicBoolean();
#863 StateChangeListener> fireOnceStateChangeListener = finalQueryInfo -> {
#864 if (finalQueryInfo.isPresent() && done.compareAndSet(false, true)) {
#865 stateChangeListener.stateChanged(finalQueryInfo.get());
#863 }
};
finalQueryInfo.addStateChangeListener(fireOnceStateChangeListener);
fireOnceStateChangeListener.stateChanged(finalQueryInfo.get());
}
我们看到调用位置是第865
行,虽然这一行代码位于方法addQueryInfoStateChangeListener
中,但是方法addQueryInfoStateChangeListener
只是用来对这个labmda进行定义,这个定义的执行很可能发生在系统启动的时候(可能系统一年前就启动过了),所以,此时的堆栈,跟addQueryInfoStateChangeListener
没有任何关系了,865
行代码发生时候的上一行代码应该是864
,因为864
也是这个labmda表达式,是顺序执行的,但是863
和862
,也许是在一年以前系统刚启动的时候调用的;
lambda表达式形成的堆栈信息中的类和方法,很可能与这个堆栈发生的运行时位置完全无关
比如上文堆栈中:
com.facebook.presto.execution.SqlQueryManager.lambda$createQueryInterna$l4(SqlQueryManager.java:502)
最后一个数组 $14
是这个labmda表达式的运行时的实例,也就是这个lambda表达式对应的function iinterface
的实现类的一个instance,由于是匿名实现,所以没有class 的名字。
$14
前面的方法SqlQueryManager
这个类以及 createQueryInterna
方法其实都与当前的调用逻辑没有任何关系,只代表了这个labmda表达式的定义位置。为了看清这一行堆栈到底发生什么,我们只能从createQueryInterna
找到这个lambda表达式的定义:
#404 private void createQueryInternal(QueryId queryId, SessionContext sessionContext, String query)
#405 {
#406 // 代码略
#407 QueryInfo queryInfo = queryExecution.getQueryInfo();
#408 queryMonitor.queryCreatedEvent(queryInfo);
#409 // 定义了lambda表达式
#500 queryExecution.addFinalQueryInfoListener(finalQueryInfo -> {
#501 try {
#501 QueryInfo info = queryExecution.getQueryInfo();
#501 stats.queryFinished(info);
#502 queryMonitor.queryCompletedEvent(info);
#503 }
#504 finally {
#505 // execution MUST be added to the expiration queue or there will be a leak
#506 expirationQueue.add(queryExecution);
}
});
}
由于labmda是接口的匿名实现,所以我们已经无法通过堆栈直接得出方法之间的调用关系,这时的调用与被调用关系必须结合源代码反推得出
比如这两行堆栈信息:
com.facebook.presto.execution.QueryStateMachine.lambda$addQueryInfoStateChangeListener$10(QueryStateMachine.java:865)
com.facebook.presto.execution.StateMachine.lambda$fireStateChanged$0(StateMachine.java:222)
从堆栈中,我们看到貌似调用关系是StateMachine.fireStateChanged()
-> QueryStateMachine.addQueryInfoStateChangeListener
,但是实际上调用关系跟这两个方法没有任何关系,这两个方法只是lambda表达式的定义位置。
我们看看
com.facebook.presto.execution.StateMachine.lambda$fireStateChanged$0(StateMachine.java:222)
这一行堆栈对应的代码:
private void fireStateChanged(T newState, FutureStateChange futureStateChange, List> stateChangeListeners)
{
checkState(!Thread.holdsLock(lock), "Can not fire state change event while holding the lock");
requireNonNull(newState, "newState is null");
#220 for (StateChangeListener stateChangeListener : stateChangeListeners) {
#221 try {
#222 stateChangeListener.stateChanged(newState);
#223 }
catch (Throwable e) {
log.error(e, "Error notifying state change listener for %s", name);
}
}
}
显然,堆栈信息中的222
行代码stateChangeListener.stateChanged(newState);
是真正的堆栈调用位置,然后,我们需要查找stateChangeListener.stateChanged(newState);
实际的代码位置,这时候就变得非常庞杂了,因为StateChangeListener这个接口通过lambda进行的实现可能出现在任何地方;
对于堆栈信息,我们往上追追溯,看到当前异常发生的时候的这个lambda表达式的定义的位置是com.facebook.presto.execution.QueryStateMachine.lambda$addQueryInfoStateChangeListener$10(QueryStateMachine.java:865)
,我们对照代码找到QueryStateMachine.java:865
:
public void addQueryInfoStateChangeListener(StateChangeListener stateChangeListener)
{
AtomicBoolean done = new AtomicBoolean();
StateChangeListener> fireOnceStateChangeListener = finalQueryInfo -> {
#864 if (finalQueryInfo.isPresent() && done.compareAndSet(false, true)) {
#865 stateChangeListener.stateChanged(finalQueryInfo.get());
#863 }
};
finalQueryInfo.addStateChangeListener(fireOnceStateChangeListener);
fireOnceStateChangeListener.stateChanged(finalQueryInfo.get());
}
可以看到,QueryStateMachine.java:865
的确定义了一个StateChangeListener
。
lambda表达式的出现给我们的编程带来了极大遍历,这种使用时再实现的自由编程思想现在已经变得越来越广泛,其封装性和细节隐藏也更强大;但是同时也降低了代码和堆栈可读性。在阅读含有lambda表达式的堆栈的时候,我们放弃阅读非lambda表达式堆栈的直观思维,将lambda表达式的定义和调用彻底区别开,才能准确理解代码;