With Java 8, Oracle has included Nashorn, a new JavaScript implementation that runs on the JVM. Nashorn is designed to replace the original JavaScript-on-the-JVM project—which was called Rhino (Nashorn is the German word for “rhino”).
Nashorn is a completely rewritten implementation and strives for easy interoperability with Java, high performance, and precise conformance to the JavaScript ECMA specifications. Nashorn was the first implementation of JavaScript to hit a perfect 100% on spec compliance and is already at least 20 times faster than Rhino on most workloads.
In this chapter, we will assume some basic understanding of JavaScript. If you aren’t already familiar with basic JavaScript concepts, then Head First JavaScript by Michael Morrison (O’Reilly) is a good place to start.
If you recall the differences between Java and JavaScript outlined in “Java Compared to JavaScript”, you know that we can see that the two languages are very different. It may, therefore, seem surprising that JavaScript should be able to run on top of the same virtual machine as Java.
In fact, there are a very large number of non-Java languages that run on the JVM—and some of them are a lot more unlike Java than JavaScript is. This is made possible by the fact that the Java language and JVM are only very loosely coupled, and only really interact via the definition of the class file format. This can be accomplished in two different ways:
The source language has an interpreter that has been implemented in Java.
The interpreter runs on the JVM and executes programs written in the source language.
The source language ships with a compiler that produces class files from units of source language code.
The resulting compiled class files are then directly executed on the JVM, usually with some additional language-specific runtime support.
Nashorn takes the second approach—but with the added refinement that the compiler is inside the runtime, so that JavaScript source code is never compiled before program execution begins. This means that JavaScript that was not specifically written for Nashorn can still be easily deployed on the platform.
This is interesting, from a technical perspective, but many developers are curious as to what role Nashorn is intended to play in the mature and well-established Java ecosystem. Let’s look at that role next.
Nashorn serves several purposes within the Java and JVM ecosystem. Firstly, it provides a viable environment for JavaScript developers to discover the power of the JVM. Second, it enables companies to continue to leverage their existing investment in Java technologies while additionally adopting JavaScript as a development language. Last, it provides a great engineering showcase for the advanced virtual machine technology present in the HotSpot Java Virtual Machine.
With the continued growth and adoption of JavaScript, broadening out from its traditional home in the browser to more general-purpose computing and the server side, Nashorn represents a great bridge between the existing rock-solid Java ecosystem and a promising wave of new technologies.
For now, let’s move on to discuss the mechanics of how Nashorn works, and how to get started with the platform. There are several different ways in which JavaScript code can be executed on Nashorn, and in the next section we’ll look at two of the most commonly used.
In this section, we’ll be introducing the Nashorn environment, and discuss two different ways of executing JavaScript (both of which are present in the bin subdirectory of $JAVA_HOME):
jjs
A more full-featured shell—suitable for both running scripts and use as an interactive, read-eval-print-loop (REPL) environment for exploring Nashorn and its features.
Let’s start by looking at the basic runner, which is suitable for the majority of simple JavaScript applications.
To run a JavaScript file called my_script.js with Nashorn, just use the jrunscript
command:
jrunscript my_script.js
jrunscript
can also be used with different script engines than Nashorn (see “Nashorn and javax.script” for more details on script engines) and it provides a -l
switch to specify them if needed:
jrunscript –l nashorn my_script.js
jrunscript
can even run scripts in languages other than JavaScript, provided a suitable script engine is available.The basic runner is perfectly suitable for simple use cases but it has limitations and so for serious use we need a more capable execution environment. This is provided by jjs
, the Nashorn shell.
The Nashorn shell command is jjs
. This can be used either interactively, or non-interactively, as a drop-in replacement for jrunscript
.
The simplest JavaScript example is, of course, the classic “Hello World,” so let’s look at how we would achieve this in the interactive shell:
$
jjs
jjs
>
(
"Hello World!"
);
Hello
World
!
jjs
>
Nashorn interoperability with Java can be easily handled from the shell. We’ll discuss this in full detail in “Calling Java from Nashorn”, but to give a first example, we can directly access Java classes and methods from JavaScript by using the fully qualified class name. As a concrete example, let’s access Java’s builtin regular expression support:
jjs
>
var
pattern
=
java
.
util
.
regex
.
Pattern
.
compile
(
"\d+"
);
jjs
>
var
myNums
=
pattern
.
split
(
"a1b2c3d4e5f6"
);
jjs
>
(
myNums
);
[
Ljava
.
lang
.
String
;
@
10
b48321
jjs
>
(
myNums
[
0
]);
a
myNums
, we got the result [Ljava.lang.String;@10b48321
—this is a tell-tale sign that despite being represented in a JavaScript variable, myNums
is really a Java array of strings.We’ll have a great deal more to say about interoperation between Nashorn and Java later on, but first let’s discuss some of the additional features of jjs
. The general form of the jjs
command is:
jjs
[<
options
>]
<
files
>
[--
<
arguments
>]
There are a number of options that can be passed to jjs
—some of the most common are:
-cp
or -classpath
indicates where additional Java classes can be found (to be used via the Java.type
mechanism, as we’ll see later).
-doe
or -dump-on-error
will produce a full error dump if Nashorn is forced to exit.
-J
is used to pass options to the JVM. For example, if we want to increase the maximum memory available to the JVM:
$
jjs
-
J
-
Xmx4g
jjs
>
java
.
lang
.
Runtime
.
getRuntime
().
maxMemory
()
3817799680
-strict
causes all script and functions to be run in JavaScript strict mode. This is a feature of JavaScript that was introduced with ECMAScript version 5, and is intended to reduce bugs and errors. Strict mode is recommended for all new development in JavaScript, and if you’re not familiar with it you should read up on it.
-D
allows the developer to pass key-value pairs to Nashorn as system properties, in the usual way for the JVM. For example:
$
jjs
–
DmyKey
=
myValue
jjs
>
java
.
lang
.
System
.
getProperty
(
"myKey"
);
myValue
-v
or -version
is the standard Nashorn version string.
-fv
or -fullversion
prints the full Nashorn version string.
-fx
is used to execute a script as a JavaFX GUI application. This allows a JavaFX programmer to write a lot less boilerplate by making use of Nashorn.15
-h
is the standard help switch.
-scripting
can be used to enable Nashorn-specific scripting extensions. This is the subject of the next subsection.
The jjs
shell can be a good way to test out some basic JavaScript, or to work interactively with an unfamiliar JavaScript package (e.g., when learning it). However, it is slightly hampered by lacking multiline input and other more advanced features that are often expected when developing with languages that make heavy use of a REPL.
Instead, jjs
is very suitable for noninteractive use, such as bringing up a daemon process written in JavaScript. For use cases like this, we invoke jjs
like this:
$
jjs
-
scripting
my_script
.
js
This enables us to make use of the enhanced features of jjs
. These include some useful extensions, many of which make using Nashorn slightly more familiar to the script programmer.
In traditional Unix scripting, the #
character is used to indicate a comment that runs until the end of the line. JavaScript, of course, uses C/C++ style comments that include //
to indicate a comment that runs to the end of the line. Nashorn conforms to this as well, but in scripting mode also accepts the Unix scripting style, so that this code is perfectly legal:
#
!/
usr
/
bin
/
jjs
#
A
perfectly
legal
comment
in
scripting
mode
(
"After the comment"
);
This feature is usually referred to as “backticks” by seasoned Unix programmers. So, just as we could write this bit of bash to download content from Google by using the Unix curl
command:
echo
"Google says: "
`
curl
http:
//www.google.co.uk`
we can also use the `
backtick quotes to enclose a Unix shell command that we want to run from within a Nashorn script. Like this:
(
"Google says: "
+
`
curl
http:
//www.google.co.uk`);
String interpolation is a special bit of syntax that allows the programmer to directly include the contents of a variable without using string concatenation. In Nashorn scripting, we can use the syntax ${<variable name>}
to interpolate variables within strings. For example, the previous example of downloading a web page can be rewritten using interpolation like this:
var
url
=
"www.google.co.uk"
;
var
pageContents
=
`
curl
http:
//${url}`;
(
"Google says: ${pageContents}"
);
Nashorn also provides several special global variables and functions that are specifically helpful for scripting and are not normally available in JavaScript. For example, the arguments to a script can be accessed via the variable $ARG
. The arguments must be passed using the --
convention, like this:
jjs
test1
.
jjs
--
aa
bbb
cccc
Then the arguments can be accessed as shown in this example:
(
$ARG
);
for
(
var
i
=
0
;
i
<
$ARG
.
length
;
i
++)
{
(
"${i}: "
+
$ARG
[
i
]);
}
$ARG
is a JavaScript array (as we can see from how it behaves when it’s passed to print()
) and needs to be treated as one. This syntax may be a little confusing for programmers coming from other languages, where the $
symbol often indicates a scalar variable.The next special global variable that we’ll meet is $ENV
, that provides an interface to the current environment variables. For example, to print out the current user’s home directory:
(
"HOME = "
+
$ENV
.
HOME
);
#
Prints
/
home
/
ben
for
me
Nashorn also provides access to a special global function called $EXEC()
. This works like the backticks we met just now, as this example shows:
var
execOutput
=
$EXEC
(
"echo Print this on stdout"
);
(
execOutput
);
You may have noticed that when we use the backtick or $EXEC()
then the output of the executed command is not printed—but instead ends up as the return value of the function. This is to prevent the printed output of executed commands from corrupting the output of the main script.
Nashorn provides two other special variables that can help the programmer to work with the output of commands that are executed from within a script: $OUT
and $ERR
. These are used to capture the output and any error messages from a command that is executed from within a script. For example:
$EXEC
(
"echo Print this on stdout"
);
// Code that doesn't change stdout
var
saveOut
=
$OUT
;
(
"- - - - - - -"
);
(
saveOut
);
The contents of $OUT
and $ERR
persist until they are overwritten by subsequent code in the main script that can also affect the values held there (such as another command execution).
JavaScript, like Java, does not support strings where the opening quote is on one line and the closing quote on another (known as multiline strings). However, Nashorn in scripting mode supports this as an extension. This feature is also known as an inline document or a heredoc and is a common feature of scripting languages.
To use a heredoc, use the syntax <<END_TOKEN
to indicate that the heredoc starts on the next line. Then, everything until the end token (which can be any string, but is usually all capitals—strings like END
, END_DOC
, END_STR
, EOF
, and EOSTR
are all quite common) is part of the multiline string. After the end token, the script resumes as normal. Let’s look at an example:
var
hw
=
"Hello World!"
;
var
output
=
<<
EOSTR
;
This
is
a
multiline
string
It
can
interpolate
too
-
$
{
hw
}
EOSTR
(
output
);
Nashorn also provides some helper functions to make it easier for developers to accomplish common tasks that shell scripts often want to perform:
print()
/ echo()
We’ve been using print()
throughout many of our examples, and these functions behave exactly as expected. They print the string they’ve been passed, followed by a newline character.
quit()
/ exit()
These two functions are completely equivalent—they both cause the script to exit. They can take an integer parameter that will be used as the return code of the script’s process. If no argument is supplied, they will default to using 0, as is customary for Unix processes.
readLine()
Reads a single line of input from standard input (usually the keyboard). By default, it will print the line out on standard output, but if the return value of readLine()
is assigned to a variable, the entered data will end up there instead, as in this example:
(
"Please enter your name: "
);
var
name
=
readLine
();
(
"Please enter your age: "
);
var
age
=
readLine
();
(<<
EOREC
);
Student
Record
-+-+-+-+-+-+-+-
Name:
$
{
name
}
Age:
$
{
age
}
EOREC
readFully()
Instead of reading from standard input, readFully()
loads the entire contents of a file. As with readLine()
, the contents are either printed to standard output, or assigned to a variable:
var
contents
=
readFully
(
"input.txt"
);
load()
This function is used to load and evaluate (via JavaScript’s eval
) a script. The script can be located by a local path, or a URL. Alternatively, it may be defined as a string using JavaScript’s script object notation.
When using load()
to evaluate other scripts, unexpected errors may occur. JavaScript supports a form of exception handling using try-catch blocks, so you should use it when loading code.
Here’s a quick example of how to load the D3 graphics visualization library from Nashorn:
try
{
load
(
"http://d3js.org/d3.v3.min.js"
);
}
catch
(
e
)
{
(
"Something went wrong, probably that we're not a web browser"
);
}
loadWithNewGlobal()
When we use load()
, it evaluates the script based on the current JavaScript context. Sometimes we want to put the script into its own, clean context. In these cases, use loadWithNewGlobal()
instead, as this starts off the script with a fresh, global context.
All the features in this section help to make jjs
a good alternative language that can easily be used to write shell scripts as bash, Perl, or other scripting languages. One final feature helps to round out this support—the availability of the “shebang” syntax for starting up scripts written in Nashorn.
#!
followed by a path to an executable, then a Unix operating system will assume the path points at an interpreter that is able to handle this type of script. If the script is executed, the OS will execute the interpreter and pass it the script file to be handled.In the case of Nashorn, it is good practice to symlink (possibly needing sudo
access) so that there is a link from /usr/bin/jjs (or /usr/local/bin/jjs) to the actual location of the jjs
binary (usually $JAVA_HOME/bin/jjs). The Nashorn shell scripts can then be written like this:
#
!/
usr
/
bin
/
jjs
#
...
rest
of
script
For more advanced use cases (e.g., long-running daemons) Nashorn can even provide compatibility with Node.js. This is achieved by the Avatar.js portion of Project Avatar, that is discussed in “Project Avatar”.
The tools we’ve seen in this section easily enable JavaScript code to be run directly from the command line, but in many cases we will want to go the other way. That is, we will want to call out to Nashorn and execute JavaScript code from within a Java program. The API that enables us to do this is contained in the Java package javax.script
, so let’s move on to examine that package next, and discuss how Java interacts with engines for interpreting scripting languages.
Nashorn is not the first scripting language to ship with the Java platform. The story starts with the inclusion of javax.script
in Java 6, which provided a general interface for engines for scripting languages to interoperate with Java.
This general interface included concepts fundamental to scripting languages, such as execution and compilation of scripting code (whether a full script or just a single scripting statement in an already existing context). In addition, a notion of binding between scripting entities and Java was introduced, as well as script engine discovery. Finally, javax.script
provides optional support for invocation (distinct from execution, as it allows intermediate code to be exported from a scripting language’s runtime and used by the JVM runtime).
The example language provided was Rhino, but many other scripting languages were created to take advantage of the support provided. With Java 8, Rhino has been removed, and Nashorn is now the default scripting language supplied with the Java platform.
Let’s look at a very simple example of how to use Nashorn to run JavaScript from Java:
import
javax.script.*
;
ScriptEngineManager
m
=
new
ScriptEngineManager
();
ScriptEngine
e
=
m
.
getEngineByName
(
"nashorn"
);
try
{
e
.
eval
(
"print('Hello World!'),"
);
}
catch
(
final
ScriptException
se
)
{
// ...
}
The key concept here is ScriptEngine
, which is obtained from a ScriptEngineManager
. This provides an empty scripting environment, to which we can add code via the eval()
method.
The Nashorn engine provides a single global JavaScript object, so all calls to eval()
will execute on the same environment. This means that we can make a series of eval()
calls and build up JavaScript state in the script engine. For example:
e
.
eval
(
"i = 27;"
);
e
.
put
(
"j"
,
15
);
e
.
eval
(
"var z = i + j;"
);
System
.
out
.
println
(((
Number
)
e
.
get
(
"z"
)).
intValue
());
// prints 42
Note that one of the problems with interacting with a scripting engine directly from Java is that we don’t normally have any information about what the types of values are.
Nashorn has a fairly close binding to much of the Java type system, so we need to be somewhat careful, however. When dealing with the JavaScript equivalents of primitive types, these will typically be converted to the appropriate (boxed) types when they are made visible to Java. For example, if we add the following line to our previous example:
System
.
out
.
println
(
e
.
get
(
"z"
).
getClass
());
we can easily see that the value returned by e.get("z")
is of type java.lang.Integer
. If we change the code very slightly, like this:
e
.
eval
(
"i = 27.1;"
);
e
.
put
(
"j"
,
15
);
e
.
eval
(
"var z = i + j;"
);
System
.
out
.
println
(
e
.
get
(
"z"
).
getClass
());
then this is sufficient to alter the type of the return value of e.get("z")
to type java.lang.Double
, which marks out the distinction between the two type systems. In other implementations of JavaScript, these would both be treated as the numeric type (as JavaScript does not define integer types). Nashorn, however, is more aware of the actual type of the data.
In our examples, we have made use of the get()
and put()
methods on the Script
. These allow us to directly get and set objects within the global scope of the script being executed by a Nashorn engine, without having to write or eval
JavaScript code directly.
Let’s round out this section with a brief description of some key clases and interfaces in the javax.script
API. This is a fairly small API (six interfaces, five classes, and one exception) that has not changed since its introduction in Java 6.
ScriptEngineManager
The entry point into the scripting support. It maintains a list of available scripting implementations in this process. This is achieved via Java’s service provider mechanism, which is a very general way of managing extensions to the platform that may have wildly different implementations. By default, the only scripting extension available is Nashorn, although other scripting environments (such as Groovy or JRuby) can also be made available.
ScriptEngine
This class represents the script engine responsible for maintaining the environment in which our scripts will be interpreted.
Bindings
This interface extends Map
and provides a mapping between strings (the names of variables or other symbols) and scripting objects. Nashorn uses this to implement the ScriptObjectMirror
mechanism for interoperability.
In practice, most applications will deal with the relatively opaque interface offered by methods on ScriptEngine
such as eval()
, get()
, and put()
, but it’s useful to understand the basics of how this interface plugs in to the overall scripting API.
Nashorn is a sophisticated programming environment, which has been engineered to be a robust platform for deploying applications, and to have great interoperability with Java. Let’s look at some more advanced use cases for JavaScript to Java integration, and examine how this is achieved by looking inside Nashorn at some implementation details.
As each JavaScript object is compiled into an instance of a Java class, it’s perhaps not surprising that Nashorn has seamless integration with Java—despite the major difference in type systems and language features. However, there are still mechanisms that need to be in place to get the most out of this integration.
We’ve already seen that we can directly access Java classes and methods from Nashorn, for example:
$
jjs
-
Dkey
=
value
jjs
>
(
java
.
lang
.
System
.
getProperty
(
"key"
));
value
Let’s take a closer look at the syntax and see how to achieve this support in Nashorn.
From a Java perspective, the expression java.lang.System.getProperty("key")
reads as fully qualified access to the static method getProperty()
on java.lang.System
. However, as JavaScript syntax, this reads like a chain of property accesses, starting from the symbol java
—so let’s investigate how this symbol behaves in the jjs
shell:
jjs
>
(
java
);
[
JavaPackage
java
]
jjs
>
(
java
.
lang
.
System
);
[
JavaClass
java
.
lang
.
System
]
So java
is a special Nashorn object that gives access to the Java system packages, which are given the JavaScript type JavaPackage
, and Java classes are represented by the JavaScript type JavaClass
. Any top-level package can be directly used as a package navigation object, and subpackages can be assigned to a JavaScript object. This allows syntax that gives concise access to Java classes:
jjs
>
var
juc
=
java
.
util
.
concurrent
;
jjs
>
var
chm
=
new
juc
.
ConcurrentHashMap
;
In addition to navigation by package objects, there is another object, called Java
, which has a number of useful methods on it. One of the most important is the Java.type()
method. This allows the user to query the Java type system, and get access to Java classes. For example:
jjs
>
var
clz
=
Java
.
type
(
"java.lang.System"
);
jjs
>
(
clz
);
[
JavaClass
java
.
lang
.
System
]
If the class is not present on the classpath (e.g., specified using the -cp
option to jjs
), then a ClassNotFoundException
is thrown (jjs
will wrap this in a Java RuntimeException
):
jjs
>
var
klz
=
Java
.
type
(
"Java.lang.Zystem"
);
java
.
lang
.
RuntimeException
:
java
.
lang
.
ClassNotFoundException
:
Java
.
lang
.
Zystem
The JavaScript JavaClass
objects can be used like Java class objects in most cases (they are a slightly different type—but just think of them as the Nashorn-level mirror of a class object). For example, we can use a JavaClass
to create a new Java object directly from Nashorn:
jjs
>
var
clz
=
Java
.
type
(
"java.lang.Object"
);
jjs
>
var
obj
=
new
clz
;
jjs
>
(
obj
);
java
.
lang
.
Object
@
73
d4cc9e
jjs
>
(
obj
.
hashCode
());
1943325854
// Note that this syntax does not work
jjs
>
var
obj
=
clz
.
new
;
jjs
>
(
obj
);
undefined
However, you should be slightly careful. The jjs
environment automatically prints out the results of expressions—which can lead to some unexpected behavior:
jjs
>
var
clz
=
Java
.
type
(
"java.lang.System"
);
jjs
>
clz
.
out
.
println
(
"Baz!"
);
Baz
!
null
The point here is that java.lang.System.out.println()
has a return type of void
(i.e., it does not return a value). However, jjs
expects expressions to have a value and, in the absence of a variable assignment, it will print it out. So the nonexistent return value of println()
is mapped to the JavaScript value null
, and printed out.
null
and missing values in JavaScript is subtle, and in particular that null != undefined
.The interoperability between JavaScript and Java goes to a very deep level. We can even use JavaScript functions as anonymous implementations of Java interfaces (or as lambda expressions). For example, let’s use a JavaScript function as an instance of the Callable
interface (which represents a block of code to be called later). This has only a single method, call()
, which takes no parameters and returns void
. In Nashorn, we can use a JavaScript function as a lambda expression instead:
jjs
>
var
clz
=
Java
.
type
(
"java.util.concurrent.Callable"
);
jjs
>
(
clz
);
[
JavaClass
java
.
util
.
concurrent
.
Callable
]
jjs
>
var
obj
=
new
clz
(
function
()
{
(
"Foo"
);
}
);
jjs
>
obj
.
call
();
Foo
The basic fact that is being demonstrated is that, in Nashorn, there is no distinction between a JavaScript function and a Java lambda expression. Just as we saw in Java, the function is being automatically converted to an object of the appropriate type. Let’s look at how we might use a Java ExecutorService
to execute some Nashorn JavaScript on a Java thread pool:
jjs
>
var
juc
=
java
.
util
.
concurrent
;
jjs
>
var
exc
=
juc
.
Executors
.
newSingleThreadExecutor
();
jjs
>
var
clbl
=
new
juc
.
Callable
(
function
(){
java
.
lang
.
Thread
.
sleep
(
10000
);
return
1
;
});
jjs
>
var
fut
=
exc
.
submit
(
clbl
);
jjs
>
fut
.
isDone
();
false
jjs
>
fut
.
isDone
();
true
The reduction in boilerplate compared to the equivalent Java code (even with Java 8 lambdas) is quite staggering. However, there are some limitations caused by the manner in which lambdas have been implemented. For example:
jjs
>
var
fut
=
exc
.
submit
(
function
(){
java
.
lang
.
Thread
.
sleep
(
10000
);
return
1
;});
java
.
lang
.
RuntimeException
:
java
.
lang
.
NoSuchMethodException
:
Can
'
t
unambiguously
select
between
fixed
arity
signatures
[(
java
.
lang
.
Runnable
),
(
java
.
util
.
concurrent
.
Callable
)]
of
the
method
java
.
util
.
concurrent
.
Executors
.
FinalizableDelegatedExecutorService
↵
.
submit
for
argument
types
[
jdk
.
nashorn
.
internal
.
objects
.
ScriptFunctionImpl
]
The problem here is that the thread pool has an overloaded submit()
method. One version will accept a Callable
and the other will accept a Runnable
. Unfortunately, the JavaScript function is eligible (as a lambda expression) for conversion to both types. This is where the error message about not being able to “unambiguously select” comes from. The runtime could choose either, and can’t choose between them.
As we’ve discussed, Nashorn is a completely conformant implementation of ECMAScript 5.1 (as JavaScript is known to the standards body). In addition, however, Nashorn also implements a number of JavaScript language syntax extensions, to make life easier for the developer. These extensions should be familiar to developers used to working with JavaScript, and quite a few of them duplicate extensions present in the Mozilla dialect of JavaScript. Let’s take a look at a few of the most common, and useful, extensions.
Nashorn also supports another small syntax enhancement, designed to make one-line functions that comprise a single expression easier to read. If a function (named or anonymous) comprises just a single expression, then the braces and return statements can be omitted. In the example that follows, cube()
and cube2()
are completely equivalent functions, but cube()
is not normally legal JavaScript syntax:
function
cube
(
x
)
x
*
x
*
x
;
function
cube2
(
x
)
{
return
x
*
x
*
x
;
}
(
cube
(
3
));
(
cube2
(
3
));
JavaScript supports try
, catch
, and throw
in a similar way to Java.
However, standard JavaScript only allows a single catch
clause following a try block. There is no support for different catch
clauses handling different types of exception. Fortunately, there is already an existing Mozilla syntax extension to offer this feature, and Nashorn implements it as well, as shown in this example:
function
fnThatMightThrow
()
{
if
(
Math
.
random
()
<
0.5
)
{
throw
new
TypeError
();
}
else
{
throw
new
Error
();
}
}
try
{
fnThatMightThrow
();
}
catch
(
e
if
e
instanceof
TypeError
)
{
(
"Caught TypeError"
);
}
catch
(
e
)
{
(
"Caught some other error"
);
}
Nashorn implements a few other nonstandard syntax extensions (and when we met scripting mode for jjs
we saw some other useful syntax innovations), but these are likely to be the most familiar and widely used.
As we have previously discussed, Nashorn works by compiling JavaScript programs directly to JVM bytecode, and then runs them just like any other class. It is this functionality that enables, for example, the straightforward representation of JavaScript functions as lambda expressions and their easy interoperability.
Let’s take a closer look at an earlier example, and see how we’re able to use a function as an anonymous implementation of a Java interface:
jjs
>
var
clz
=
Java
.
type
(
"java.util.concurrent.Callable"
);
jjs
>
var
obj
=
new
clz
(
function
()
{
(
"Foo"
);
}
);
jjs
>
(
obj
);
jdk
.
nashorn
.
javaadapters
.
java
.
util
.
concurrent
.
Callable
@
290
dbf45
This means that the actual type of the JavaScript object implementing Callable
is jdk.nashorn.javaadapters.java.util.concurrent.Callable
. This class is not shipped with Nashorn, of course. Instead, Nashorn spins up dynamic bytecode to implement whatever interface is required and just maintains the original name as part of the package structure for readability.
One final note is that Nashorn’s insistence on 100% compliance with the spec does sometimes restrict the capabilities of the implementation. For example, consider printing out an object, like this:
jjs
>
var
obj
=
{
foo:
"bar"
,
cat:
2
};
jjs
>
(
obj
);
[
object
Object
]
The ECMAScript specification requires the output to be [object Object]
—conformant implementations are not allowed to give more useful detail (such as a complete list of the properties and values contained in obj
).
In this chapter, we’ve met Nashorn, the JavaScript implementation on top of the JVM that ships with Oracle’s Java 8. We’ve seen how to use it to execute scripts and even replace bash and Perl scripts with enhanced JavaScript scripts that can leverage the full power of Java and the JVM. We’ve met the JavaScript engine API and seen how the bridge between Java and scripting languages is implemented.
We’ve seen the tight integration between JavaScript and Java that Nashorn provides, and some of the small language syntax extensions that Nashorn provides to make programming a little bit easier. Finally, we’ve had a brief peek under the hood at how Nashorn implements all of this functionality. To conclude, let’s take a quick look into the future and meet Project Avatar, which could be the future of Java/JavaScript web applications.
One of the most successful movements in the JavaScript community in recent years has been Node.js. This is a simple server-side JavaScript implementation developed by Ryan Dahl and now curated by Joyent. Node.js provides a programming model that is heavily asynchronous—designed around callbacks, nonblocking I/O, and a simple, single-threaded event loop model.
While it is not suitable for developing complex enterprise applications (due to limitations of the callback model in larger codebases), Node.js (often referred to simply as Node) has nonetheless become an interesting option for developing prototypes, simple “glue” servers, and single-purpose HTTP and TCP server applications of low to moderate complexity.
The Node ecosystem has also prospered by promoting reusable units of code, known as Node packages. Similar to the Maven archives (and to earlier systems, such as the Perl CPAN), Node packages allow the easy creation and redistribution of code, although they suffer from the relative immaturity of JavaScript, which is missing many modularity and deployment features.
The original implementation of Node is composed of several basic components—a JavaScript execution engine (the V8 engine developed by Google for their Chrome browser), a thin abstraction layer, and a standard library (of mostly JavaScript code).
In September 2013, Oracle announced Project Avatar. This is an effort by Oracle to produce a future-state architecture for web applications and to marry JavaScript (and Node) to the mature ecosystem that already exists for Java web apps.
As part of Project Avatar, Oracle open sourced their implementation of the Node API, which runs on top of Nashorn and the JVM. This implementation, known as Avatar.js, is a faithful implementation of most of the Node API. It is currently (April 2014) capable of running a large number of Node modules—essentially anything that does not depend on native code.
The future is, of course, unknown, but Avatar points the way towards a possible world where the JVM is the foundation of a new generation of web applications that combine JavaScript with Java and hopefully provide the best of both worlds.
15 JavaFX is a standard Java technology used for making GUIs—but it is outside the scope of this book.