Nashorn ist eine der wenigen Javascript Runtimes, die 100% ECMAScript 5.1 compliant sind.
Für Java 9 ist eine Unterstützung von ECMAScript 6/2015 geplant, die teilweise schon umgesetzt ist.
Die Entwickler von Nashorn haben aber sehr deutlich gemacht, dass Javascript vom Design und der Semantik eine der schlimmsten Sprachen darstellt.
"Und wer JavaScript performant auf der JVM zu laufen bekommt, hat die potentiellen Probleme der meisten anderen dynamischen Programmiersprachen auch gleich mit gelöst" [Lagergren JLS2014]
Die Ausführung von Javascript auf der JVM erlaubt interessante neue Optionen, die Nutzung des breiten Ökosystems und der Klassenbibliotheken.
Aber auch die bewährten Test-, Integrations and Deployment-Plattformen stehen zur Verfügung, so dass große Javascript-Projekte in verlässlichen Umgebungen laufen können.
Desweiteren kann Profiling von Ausführung und Speichernutzung sowohl über Debugger, als auch über Java-Flight-Recorder wichtige Laufzeitinformationen bereitstellen.
Für den Schnellstart mit Nashorn bedient man sich der mitgelieferten Shell "jjs", die im "bin" Verzeichnis des JDK/JRE zu finden ist:
// JavaScript mit Kurzform für Funktionen
[1,0].map(function(x) x*x).join("");
// Konvertierung von Feldern
var a = Java.to([1,"2","false"],"int[]");
for(x in a) print(x) // 1,2,0
Java.from(a) // 1,2,0
// Überladung von Funktionen
java.lang.System.out["println(double)"](12); // 12.0
// Java Klasse deklarieren, erzeugen, aufrufen
var File = Java.type("java.io.File");
new File("/etc/passwd").exists(); // true
java.util.Array.asList(-1,1).stream()
.reduce(0, function(a,x) a+x); // 0
// Funktionen als SAM-Parameter weitergeben
new Thread(function() { print("new thread")});
// Erweiterung von Klassen
var r = new java.lang.Runnable
({ run: function() { print('hello') }});
// Kurzform
var r = new java.lang.Runnable
{ run: function() print('hello') };
++[[]][+[]]+[+[]]; // 10
Die Einbindung in Java Programme erfolgt mittels der javax.script.ScriptEngine
des JSR-223, von der man sich eine "nashorn" Runtime zurückgeben lässt.
Da Nashorn nicht thread-safe ist, sollten Instanzen der ScriptEngine über ThreadLocal
oder Pools verwaltet werden.
Da bei der Entwicklung von Nashorn viel Wert auf die gute Integration von Java Typen und Mechanismen gelegt wurde, gibt es bei der Integration nur ein wenig zu beachten.
Die meisten Java-Typen und deren Methoden, können ohne Verlust von Typinformationen direkt in Javascript genutzt werden.
In Javascript können Klassen mittels Java.extend
abgeleitet werden, neben den Typen (1 Klasse, n Interfaces) wird dann ein Javascript Objekt übergeben, dessen Properties die Funktionen und Eigenschaften enthalten, die für die Erweiterung genutzt werden können.
Dabei müssen alle Methoden-Overloads mit demselben Namen durch diesselbe Javascriptfunktion gehandhabt werden, welche dann aufgrund der Parameteranzahl und -typen das Dispatching selbst vornehmen muss.
Alle JavaScript Objekte (also auch Felder) sind Subklassen von ScriptObjectMirror
und implementieren das Map
interface.
Felder in JavaScript können aufgrund ihrer sehr eigenwilligen Semantik - sie können verschiedene Typen enthalten und auch spärlich besetzt sein - nicht direkt auf Java-Felder abgebildet werden, sondern nur mit Java.to(array[,"int[]"])
.
Eine Abbildung von Feldern auf das java.util.List
Interface ist leider auch nicht möglich da dessen boolean Collection.remove(Object)
mit der Object Map.remove(Object)
des Map-Interfaces von ScriptObjectMirror
kollidiert.
Java-Klassen sollten mittels ihres Paketes über var HashMap = Java.type("java.lang.HashMap")
referenziert werden.
Es gibt auch die Möglichkeit, globale Konstanten zu nutzen, die eine Paketstruktur simulieren, wie z.b. new java.io.File()
, aber ihre eigenen Fehlerquellen mitbringen.
Java-Features wie Streams können mit Funktionen kombiniert und Java-Collections/Felder mit foreach genutzt werden.
Ein cooles Gimmick ist, dass Javascript Funktionen an allen Stellen genutzt werden können, die ein Single-Abstract-Method (SAM) Interface bzw. Klasse entgegennehmen.
Variablen können in den ScriptEngine-Kontext gelegt werden und Fragmente oder vollständige Skriptdateien ausgeführt werden, deren Kreationen dann ebenfalls im Kontext vorliegen.
Für die Ausführung von in Javascript definierten Funktionen kann die ScriptEngine
nach Invocable
gecasted werden, um dann diese mit invokeFunction("name",args)
auszuführen.
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
engine.eval("function encode(x) { return encodeURIComponent(x); }");
Invocable js = (Invocable)engine;
System.out.println(js.invokeFunction("encode", "süß"));
}
}
// s%C3%BC%C3%9F