ORDINA BLOGT

JavaScript voor JDK8

Er is steeds meer belangstelling voor andere talen dan Java, vooral JavaScript krijgt veel aandacht. Voor de Java Virtual Machine zijn dan ook allerlei talen ontwikkeld zoals JPyhton en JRuby.

  • 5 april 2014

JavaScript voor JDK8 

Momenteel zijn er al meer dan 400 talen. De performance laat helaas vaak te wensen over. Een belangrijke reden daarvoor is dat in de Java Virtual Machine voor Java SE 7 een cruciale instructie ontbreekt om goed om te gaan met dynamische typen. Oracle wil met Nashorn laten zien dat dit nu wel mogelijk is. JavaScript is daarvoor een uitstekende kandidaat, want ze verschilt op veel punten van Java. Door optimaal gebruik te maken van de nieuwe instructie #invokdedynamic is Nashorn efficiënter dan zijn voorlopers. De performance is nog niet heel erg goed, maar volgens Oracle wordt dit snel beter. Voldoende reden om Nashorn eens uit te proberen. Voor een eerste poging volstaat een texteditor en de commandline. De IDE-ondersteuning is mager en alleen NetBeans heeft (op dit moment van schrijven) een debugger. 

Opstarten vanaf de commandline

Nashorn kun je aanroepen met ‘jrunscript’ of ‘jjs’ en heeft ondersteuning voor shell scripting en javafx. In ‘scripting’ mode (jjs -scripting) kun je parameters ($ en {}) en globale variabelen ($ARG, $ENV) gebruiken. Met de commandline-optie ‘fc’ (jjs –fc) schakel je JavaFx in. 

Java klassen importeren

Klassen importeren kan op twee manieren: via het object ‘Packages’ en via het globale Java-object. 

//Voorbeeld van Packages
var System=Packages.java.lang.System; 
//Voorbeeld van Java object
var System=Java.type('java.lang.System');

Gebruik bij voorkeur het Java-object, omdat aan de packages-methode een paar nadelen kleven:

  • Elke ‘property access’ kost tijd, een package of klasse diep in de hiërarchie kan langzaam zijn.
  • Er is geen speciale syntax om Java Arrays te maken, daarvoor moet je java.lang.reflect.Array als workaround gebruiken.

Als er een ‘typo’ zit in een klassenaam, dan wordt het gezien als een JavaPackage in plaats van een JavaClass, de fout komt vaak pas tijdens runtime[a1]  boven tafel.  

//Voorbeeld van een typo in klassenaam
var System=Packages.java.lang.System; 
var Zyztem=Packages.java.lang.Zyztem; 
print(System);//print [JavaClass] 
print(Zyztem);//print [JavaPackage] 

Om te voorkomen dat er een lange rij met imports en allerlei globale variabelen boven aan in een script komen te staan, gebruik je JavaImporter.  

var imports=new JavaImporter(java.util,java.io);
with(imports) { 
    var map = new HashMap(); //verwijst naar java.util.HashMap 
} 

Nieuwe Java objecten maken

Nashorn kent een aantal korte notaties om nieuwe objecten te maken.

Aan de constructor van een abstracte klasse of interface kun je een Javascriptobject meegeven die de ‘overloaded’-methoden implementeert.
var TimerTask = Java.type("java.util.TimerTask"); 
var task = new TimerTask({ run: function() { print("Hello World!") } });

De implementatie kun je in brackets zetten.

 var task = new TimerTask { 
    run: function() {
        print("Hello World!")
    }
}

Een abstracte klasse met één methode heet een Single Abstract Method (SAM), aan een SAM hoeft alleen een functie aan de constructor worden meegegeven.

var task = new TimerTask(function() { print("Hello World!") }); 

Als het argument van methode een SAM is, geef dan een functie mee als argument.

var timer = new Java.type("java.util.Timer");
timer.schedule(function() { print("Hello World!") });

Andere scripts laden

Met de load-functie kun je andere scripts laden uit een bestand, url of een JavaScript-object met de juiste properties. Via pseudo url’s zoals: ‘nashorn:’ en ‘fx:’ laad je handige functionaliteit in. 

load(“foo.js”); 
load(“http://www.example.com/foo.js”);
load({ script: “print(‘hello world’)”,name:”script.js”});
load(“nashorn:parser.js”); // Nashorn parser support script
load(“nashorn:mozilla_compat.js”); // Mozilla compatibillity script voor Rhino compabiliteit.

De functie ‘LoadWithNewGlobal’ laadt een script in een nieuwe global scope. Dit kan handig zijn bij multi-threading.

var Executors = java.util.concurrent.Executors; 
var ArrayList = java.util.ArrayList;

//Verhoog een teller in een nieuwe global scope
function doSomething() {
    return loadWithNewGlobal({name:”dosomething”,script:” i=0; i+=1;”});
}

//Maak een threadpool
var executor = Executors.newCachedThreadPool();
var results = new ArrayList();

//Start een aantal threads
for(var i = 0; i < 50; i++) {
    results.add(executor["submit(java.util.concurrent.Callable)"](dosomething));
}
//Verzamel de resultaten
for each( var result in results) {
    print(result.get().intValue());
}

JSAdapter

JSAdapter maakt allerlei ‘proxy’-achtige oplossingen mogelijk. Het is een java.lang.reflect.Proxy-mechanisme voor scriptobjecten die aanroepen op een object onderschept met ‘magic methods’ zoals: __get__, __put__, __call__, __new__, __getIds__, __getValues__, __has__ en __delete__. 

function printArgs(arguments) {for each (var arg in arguments) print(arg);} 

var adapter = new JSAdapter() {
    __get__ : function(name) { print("__get__ "+name); },
    __has__ : function(name) { print("__has__ "+name); },
    __put__ : function(name) { print("__put__ ");printArgs(arguments); },
    __call__ : function(name) { print("__call__ ");printArgs(arguments); },
    __new__ : function() { print("__new__ "); printArgs(arguments); return this;},
    __delete__ : function() { print("__delete__ ");printArgs(arguments); },
    __getIds__ : function() { print("__getIds__ ");return ["id1","id2"]; },
    __getValues__ : function() { print("__getValues__ "); return ["value1","value2"]; }
}

//print __new__ FOO BAR
var x = new adapter("FOO","BAR");

//print __call__ foo
x.foo();

//print __call__ bar foo
x.bar("foo");

//print __put__ prop 100
x.prop="100";

//print __getIds__ id1 id2
for(el in x) {
    print(el);
}

//print __getValues__ value1 value2
for each(el in x) {
    print(el);
}

Arrays, Collections en Java Beans

Collections en arrays ziet Nashorn als ‘gewone’ JavaScript-arrays en je kunt erover itereren of elementen opvragen. Java Beans getters en setters [a1] ziet Nashorn als een property van een object.

Verander de waarde van een getter setter via een property.

var date = new (Java.type("java.util.Date")); 
//print 114
print(date.year);
date.year+=1900
//print 2014
print(date.year);
//print 2014
print(date[‘year’]);

Vierkante haken kun je gebruiken om elementen in een array op te vragen.

var arr = new (Java.type("double[]"))(6); 
arr[0]=1
print(arr);

Converteer een JavaScript-array naar een Java Array.  

var jsArr=["a", "b", "c"]; 
var javaArr=Java.to(jsArr,Java.type("java.lang.String[]"));
print("Java class " + javaArr.class + " length " + javaArr.length);

Gebruik 'for' of 'for each' om te itereren over een Java Array. 

for (var indx in javaArr) print(indx); 
for each (var el in javaArr) print(el);

Bij Map-objecten moet je keySet en values gebruiken om over keys of waarden te itereren.

var map = new (Java.type("java.util.HashMap")); 
map.put("naam","Nashorn");
map.put("taal","JavaScript");
for each (var el in map.keySet()) print(el);
for each (var el in map.values()) print(el);

Strings en Numbers

Bij Strings is er een uitzondering waar je rekening mee moet houden. Als je twee strings samenvoegt (concat), dan maakt Nashorn van de samengestelde string een jdk.nashorn.internal.runtime.ConsString-object. Om deze door te geven aan een Java-method moet je ze converteren naar een Java String met de functie String().

//Strings, Nashorn gebruikt bij 'concatenation' jdk.nashorn.internal.runtime.ConsString 
var first = "first";
var second = first + " second";
print(first.class);
print(second.class); //print ConsString

Gebruik altijd String() bij een aanroep van een Java-methode om er zeker van te zijn dat java.lang.String wordt meegegeven aan een methode. 

var secondAsString = String(second); 
print(secondAsString.class); //print String

Nashorn gebruikt java.lang.Double, java.lang.Long of java.lang.Integer, afhankelijk van de berekening. Met Number() kun je afdwingen dat er een double wordt gebruikt. 

var num = 100; 
print(num.class);
print((Number(num)).class);

In de documentatie van Nashorn staan nog veel andere voorbeelden die de moeite waard zijn, zie: http://download.java.net/jdk8u20/docs/technotes/guides/scripting/nashorn/toc.html

Tot zover een aantal ‘basics’ van Nashorn. Het is leuk om mee te experimenteren en er zijn volop mogelijkheden voor toepassingen, bijvoorbeeld in combinatie met JavaFx.