Search code examples
rhinostatic-variablesnashorn

Access static Java variables from js code in Nashorn engine


While trying to port old code running Rhino engine to Nashorn in Java 8, I got the trouble, static properties/methods cannot be accessed from running js script. If I use Rhino, it runs perfectly. I don't know what happens with the implementation of the new Nashorn engine.

import javax.script.*;

public class StaticVars {
    public static String myname = "John\n";
    public static void main(String[] args) {
        try{
            ScriptEngine engine;
            ScriptEngineManager manager = new ScriptEngineManager();
            engine=System.getProperty("java.version").startsWith("1.8")?            
                manager.getEngineByName("Nashorn") :    //j1.8_u51
                manager.getEngineByName("JavaScript");  //j1.7

            engine.put("staticvars", new StaticVars());
            engine.eval("print(staticvars.myname);");
            //print "John" if ran with java 7
            //print "undefined" if ran with java 8

        } catch(Exception e){e.printStackTrace();}
    }
}

Solution

  • In Nashorn, you can't access class static members through class instances. There are multiple ways to get at statics. You can obtain a type object that acts as both a constructor and as a static namespace, much like a type name acts in Java:

    var StaticVars = Java.type("StaticVars"); // use your full package name if you have it
    print(StaticVars.myname);
    

    Or, pass in a java.lang.Class object and use the .static pseudo-property to access the statics:

    engine.put("StaticVarsClass", StaticVars.class);
    

    followed by:

    var StaticVars = StaticVarsClass.static;
    print(StaticVars.myname);
    

    in the script. In general, .static is the inverse operation to .class:

    var BitSet = Java.type("java.util.BitSet");
    var bitSetClass = BitSet.class; // produces a java.lang.Class object, just like in Java
    print(BitSet === bitSetClass.static); // prints true
    var bitSet = new BitSet(); // type object creates a new bitset when used as a constructor
    var thisWontWork = new bitSetClass(); // java.lang.Class can't be used as a constructor.
    

    As you can see, we distinguish three concepts:

    • the runtime class objects, which are instances of java.lang.Class. They aren't special, and you only can use the Class API on them (.getSuperclass(), .getName(), etc.)
    • instances of classes themselves (normal objects that you can access instance members on)
    • type objects, which are both namespaces for static members of classes they represent, as well as constructors. The closest equivalent in Java to them is the name of the class as used in source code; in JavaScript they are actual objects.

    This actually produces least ambiguity, as everything is in its place, and maps most closely to Java platform idioms.