3. The second biggest feature
(depending of course on who
you ask) is Nashorn – the new
JVM JavaScript engine that’s
supposed to bring Java up to
par with other JS engines
such as V8 and its node.js
container.
5. The Java platform is built out of two main
components. The JRE, which JIT compiles and
executes bytecode, and the JDK which contains dev
tools and the javac source compiler.
I’ll explain.
www.takipi.com
6. The JVM was built to be
language agnostic in
the sense that it can
execute code written in
any language, and
bytecode compiled
from Java source will
pretty much resemble it
structurally.
www.takipi.com
7. But the farther away you
get from Java – the more
that distance grows.
www.takipi.com
8. When you look at
Scala which is a
functional
language, the
distance between
the source code and
the executed
bytecode is pretty
big.
www.takipi.com
9. When you look at fully dynamic
languages such as JavaScript,
that distance becomes huge.
www.takipi.com
10. And now with Java 8, this is
beginning to creep into Java
as well.
www.takipi.com
12. What you’re writing and
what you’re debugging will
be two different things.
See the Example below
www.takipi.com
13. This is the
traditional method
by which we would
iterate over a list of
strings to map their
lengths.
// 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));
}
Java 6 & 71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
www.takipi.com
14. This will throw an exception if an empty string
is passed. The stack trace will look like –
This is what most Java devs are
used to.
at LmbdaMain.check(LmbdaMain.java:19)
at LmbdaMain.main(LmbdaMain.java:34)
1
2
www.takipi.com
16. Scala
val lengths = names.map(name => check(name.length))1
The iteration is carried
out by the framework
(i.e. internal iteration).
Lambda expression to
map the string lengths
www.takipi.com
17. 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The call stack is an order of magnitude
longer, and much harder to understand.
Remember, this example is very simple. With
real-world nested Lambdas and complex
structures you’ll be looking at much longer
synthetic call stacks, from which you’ll need to
understand what happened.
This has long been an issue with Scala, and one of
the reasons we built the Scala
Stackifier.
www.takipi.com
18. Let’s look at the
corresponding Java 8 code,
and the resulting call stack.
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
And now in java 8
www.takipi.com
19. More concise code with more complex
debugging, and longer synthetic
call stacks.
The reason is that while javac has been extended to
support Lambda functions, the JVM still remains
oblivious to them. This has been a design decision
by the Java folks in order to to keep the JVM
operating at a lower-level, and without introducing
new elements into its specification.
www.takipi.com
20. JavaScript in Java 8
Java 8 introduces a brand new
JavaScript compiler. Now we can
finally integrate Java + JS in an
efficient and straightforward
manner. However, nowhere is
the dissonance between the
code we write and the code we
debug bigger than here.
Here’s the same function in Nashorn
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
String js = "var map = Array.prototype.map n";
js += "var a = map.call(names, function(name) { return Java.type("LmbdaMain").check(name) }) n";
js += "print(a)";
engine.eval(js);
1
2
3
4
5
6
7
www.takipi.com
21. In this case the bytecode code
is dynamically generated at
runtime using a nested tree of
Lambda expressions. There is
very little correlation between
our source code, and the
resulting bytecode executed by
the JVM. The call stack is now
two orders of magnitude longer.
www.takipi.com