另载于 http://www.qingjingjie.com/blogs/11
熟悉Scala的人知道返回值是代码块的最后一句,一般不能提前返回。return关键字是用抛异常来实现的,这样就能提前脱离代码块了。
最近看Scala源代码,注意到它对return的高效实现,有趣。
Scala咋实现的?
抛的异常类是NonLocalReturnControl,继承自Throwable,有个字段用来放置要返回的值。编译器把return value大致翻译成throw new NonLocalReturnControl(key/*metadata*/, value)
就可以了。
通常Java的异常有三类: Exception, RuntimeException, Error。如果一个对象是Exception但不是RuntimeException,那就是checked exception。而Error通常是JVM抛出的,例如StackOverflowError, OutOfMemoryError, VerifyError,偶尔也有XML库抛Error。总之没有其他直接继承Throwable的类了。
所以在Scala里只要注意别无脑catch Throwable,就不会误拦return了。如果是个服务器程序,不想let it crash,只要catch Exception和Error就好了。
用抛异常来打断控制流的做法,在有的Web框架中也出现了,没啥。但在一门语言的实现中,必须保证高效。
于是我们要知道抛异常为什么慢!Scala咋解决的?
throw和catch都是很快很快的,毕竟只是几个地址操作,慢的是new Exception这一步,这里要让JVM取得当前的一大串stacktrace填充进去,开销约为new 200个Object的程度。
Scala的NonLocalReturnControl的窍门是——Override了fillInStackTrace()
方法。这个方法本来会调用native的fillInStackTrace(int)
让JVM去填stacktrace。覆盖成空实现,测一下,开销约为new 6个Object。效果拔群!
还可以提高!去掉synchronized关键字,开销又减半了。最后是new 3个Object的开销,可以接受!
我想到另一种实现方案是把value塞到一个ThreadLocal里,异常就不必包含value了,这样就只需全局new一次异常对象,每次都throw它。开销约为new 1个Object。Clojure就喜欢用ThreadLocal来实现一些特性。不过ThreadLocal悠着点用啊,玩不好是要出大事的(下一篇谈这个问题)。