这是来自Tai Weiss的一篇博文,引发了不少讨论,大意如下:Java 8最大的特色是Lambda表达式,Lambda曾经是函数语言代表Scala和Clojure的显著特征,如今Java也加入了。
Java 8第二个最大特色是Nashorn,这是一个新的JVM Javascript引擎,能够将Java和JS引擎V8及其应用Node.js混合编程。
但是这些新特色也有其阴性面。
Java平台是由两个主要组件组成,JRE和JDK,这两个组件应该是解耦的,这样能让人们编写自己的JVM语言,Scala正是这样的代表。
JVM可以执行任何语言编写的代码,只要它们能编译成字节码,字节码自身是充分OO的,被设计成接近于Java语言,这意味着Java被编译成的字节码非常容易被重新组装。
但是如果不是Java语言,差距将越来越大,Scala源码和被编译成的字节码之间巨大差距是一个证明,编译器加入了大量合成类 方法和变量,以便让JVM按照语言自身特定语法和流程控制执行。
当你使用真正动态语言Javascript时,这种差距变得巨大。
如果这只是一个理论问题而不影响实际工作,那也就罢了,可惜不是,当我们加入新的元素进入Java,你的代码和运行情况之间有了距离,这意味着你编写代码和你调试代码是两回事。
我们首先看看Java 6/7中的一个传统方法案例:
// simple check against empty strings public static int check(String s) { if (s.equals("")) { throw new IllegalArgumentException(); } return s.length(); } //map names to lengths List lengths = new ArrayList(); for (String name : Arrays.asList(args)) { lengths.add(check(name)); }
|
如果一个空的字符串传入,这段代码将抛出错误,堆栈跟踪如下:
at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.main(LmbdaMain.java:34)
|
这里我们看到出错堆栈和我们代码之间是1:1的对应,这使得我们调试变得直接。那么现在看看Scala和Java 8:
在Scala中只要使用Lambda表达式map来实现这段代码,如下:
val lengths = names.map(name => check(name.length))
|
如果一个错误抛出,调用栈将变得很长,如下:
at Main$.check(Main.scala:6) at Main$$anonfun$1.apply(Main.scala:12) at Main$$anonfun$1.apply(Main.scala:12) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244) at scala.collection.immutable.List.foreach(List.scala:318) at scala.collection.TraversableLike$class.map(TraversableLike.scala:244) at scala.collection.AbstractTraversable.map(Traversable.scala:105) at Main$delayedInit$body.apply(Main.scala:12) at scala.Function0$class.apply$mcV$sp(Function0.scala:40) at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) at scala.App$$anonfun$main$1.apply(App.scala:71) at scala.App$$anonfun$main$1.apply(App.scala:71) at scala.collection.immutable.List.foreach(List.scala:318) at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32) at scala.App$class.main(App.scala:71) at Main$.main(Main.scala:1) at Main.main(Main.scala)
|
记住这个例子还是很简单。现实世界中嵌套lambda和复杂的结构,你会看更长的合成的调用堆栈,从中你还需要了解程序发生了什么事。再看看Java 8:
Stream lengths = names.stream().map(name -> check(name)); at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.lambda$0(LmbdaMain.java:37) at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) at java.util.stream.LongPipeline.sum(LongPipeline.java:396) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at LmbdaMain.main(LmbdaMain.java:39)
|
这非常类似Scala,出错栈信息太长,我们为代码的精简付出力代价,更精确的代码意味着更复杂的调试。Javac必须支持Lambda函数,但是JVM还必须无视它们,这样才能保证JVM操作在一个低级别层次,而无需引入任何新的规范元素。
当你明白这个设计决定的秘密,意味着Java程序员需要付出区分调用堆栈信息的成本。这种问题在java8 + javascript上表现得更加突出。(好长的堆栈信息可见原文,点按标题进入)
本站Java8 Lambda相关文章:点按这里
[该贴被banq于2014-04-03 14:39修改过]