-
Notifications
You must be signed in to change notification settings - Fork 0
Java Type Objects
Nashorn represents Java types with a special class of objects, we call these "Java type objects", or just "type objects". They are used as constructors with the new
operator, they expose static fields, properties, and methods, and they can be used with the ECMAScript instanceof
operator as well.
A typical way to obtain a type object from a type name is using the Java.type
method:
var arrayListType = Java.type("java.util.ArrayList")
var intType = Java.type("int")
var stringArrayType = Java.type("java.lang.String[]")
var int2DArrayType = Java.type("int[][]")
Note that the name of the type is always a string for a fully qualified name.
You can use any of these types to create new instances, e.g.:
var anArrayList = new (Java.type("java.util.ArrayList"))
or
var ArrayList = Java.type("java.util.ArrayList")
var anArrayList = new ArrayList
var anArrayListWithSize = new ArrayList(16)
When array types are used as constructors, they take one parameter that is the array size:
var intArrType = Java.type("int[]")
var intArr5 = new intArrType(5)
is equivalent to new int[5]
in Java.
In the special case of inner classes, you can either use the JVM fully qualified name, meaning using $
sign in the class name, or you can use the dot:
var ftype = Java.type("java.awt.geom.Arc2D$Float")
and
var ftype = Java.type("java.awt.geom.Arc2D.Float")
both work. Note however that using the dollar sign is faster, as Java.type
first tries to resolve the class name as it is originally specified, and the internal JVM names for inner classes use the dollar sign. If you use the dot, Java.type
will internally get a ClassNotFoundException
and subsequently retry by changing the last dot to dollar sign. It'll keep replacing dots with dollar signs until it either successfully loads the class or runs out of all dots in the name. This way it can correctly resolve and load even multiply nested inner classes with the dot notation. Again, this will be slower than using the dollar signs in the name.
Finally, you can also access the type object for an inner class as a property of the type object for the outer class:
var arctype = Java.type("java.awt.geom.Arc2D")
var ftype = arctype.Float
You can access both static and non-static inner classes. If you want to create an instance of a non-static inner class, remember to pass an instance of its outer class as the first argument to the constructor.
Type objects expose all static fields and methods on the Java class they represent as properties. Examples:
var File = Java.type("java.io.File")
var pathSep = File.pathSeparator
var tmpFile1 = File.createTempFile("abcdefg", ".tmp")
var tmpFile2 = File.createTempFile("abcdefg", ".tmp", new File("/tmp"))
You can even assign static methods to variables and invoke them on their own, so the above example can be rewritten as:
var File = Java.type("java.io.File")
var createTempFile = File.createTempFile
var tmpFile1 = createTempFile("abcdefg", ".tmp")
var tmpFile2 = createTempFile("abcdefg", ".tmp", new File("/tmp"))
If you need to access the actual java.lang.Class
object for the type, you can use the class
property on the object representing the type, just as you would do with a type name in Java:
var File = Java.type("java.io.File")
var someFile = new File("blah")
print(File.class === someFile.getClass()) // prints true
Nashorn also exposes a synthetic property named static
on any java.lang.Class
object to retrieve its type-representing object:
var File = Java.type("java.io.File")
print(File.class.static === File) // prints true
Note that this functionality doesn't have a Java equivalent as in Java, types are not objects themselves.
Type objects work with the ECMAScript instanceof
operator:
var File = Java.type("java.io.File")
var aFile = new File("foo")
print(aFile instanceof File) // prints true
It is a good time to clarify the difference between type objects and java.lang.Class
objects.
In Nashorn, type objects are distinct from Class
objects. Class
objects are treated as ordinary Java objects. You can call getClass()
(it also works as a class
property) on any object to get its Class
object, and call any methods on it such as getName()
, or getSuperclass()
etc. However, you can not use them as constructors, and you can not retrieve static members of the class through them. This distinction exists in Java too: you don't write new File.class("/tmp")
or File.class.pathSeparator
, or aFile instanceof File.class
but rather use only the type name ("File") in these places that gets resolved by the compiler. The difference is that in ECMAScript, there's no compiler involved, and everything is an object, so we introduce a separate class of objects to represent types for purposes of construction, subclassing, and static access.
For those curious about details, type objects are not actually implemented in Nashorn. They are instead provided by the JDK built-in jdk.dynalink
module that Nashorn heavily relies on; they are instances of jdk.dynalink.beans.StaticClass
.
Similar to how you can create anonymous inner classes in Java, you can use the type objects of abstract classes and interfaces to create anonymous subclasses. If the type is abstract, you can instantiate an anonymous subclass of it using an argument list that is applicable to any of its public or protected constructors and adding a ECMAScript object with functions properties that provide ECMAScript implementations of the abstract methods. E.g.:
var TimerTask = Java.type("java.util.TimerTask")
var task = new TimerTask({ run: function() { print("Hello World!") } })
While not pictured here, it is important to note that when method names are overloaded, a ECMAScript function for a name will provide implementation for all overloads of the name.
Nashorn supports a syntactic extension where a "new" expression followed by an argument is identical to invoking the constructor and passing the argument to it, so you can write the above example also as:
var task = new TimerTask {
run: function() {
print("Hello World!")
}
}
which is very similar to Java anonymous inner class definition. On the other hand, if the type is an abstract type with a single abstract method (commonly referred to as a "SAM type" or "lambda") or all abstract methods it has share the same overloaded name, then instead of an object, you can just pass a function, so the above example can become even further simplified to:
var task = new TimerTask(function() { print("Hello World!") })
Note that in every one of these cases if you are trying to instantiate an abstract class that has constructors that take some arguments, you can invoke those simply by specifying the arguments after the initial implementation object or function.
The use of functions can be taken even further; if you are invoking a Java method that takes a SAM type, you can simply pass in a function object, and Nashorn will know what you meant:
var timer = new (Java.type("java.util.Timer"))
timer.schedule(function() { print("Hello World!") }, 0)
Here, Timer.schedule()
expects a TimerTask
as its argument, so Nashorn creates an instance of a TimerTask subclass and uses the passed function to implement its only abstract method, run()
. In this usage though, you can't use non-default constructors; the type must be either an interface, or must have a protected or public no-arg constructor.
In addition to the above functionality of creating anonymous subclasses of abstract Java classes and interfaces, you can also subclass non-abstract Java classes and implement an arbitrary number of Java interfaces in a single class using Nashorn; for that you will need to use the Java.extend
method. Example:
Example:
var ArrayList = Java.type("java.util.ArrayList")
var ArrayListExtender = Java.extend(ArrayList)
var printSizeInvokedArrayList = new ArrayListExtender() {
size: function() { print("size invoked!"); }
}
var printAddInvokedArrayList = new ArrayListExtender() {
add: function(x, y) {
if(typeof(y) === "undefined") {
print("add(e) invoked!");
} else {
print("add(i, e) invoked!");
}
}
Note that if the Java method is overloaded (as in the above example List.add()
), then your ECMAScript adapter must be prepared to deal with all overloads.
It is also possible to specify an ordinary ECMAScript object as the last argument to extend. In that case, it is treated as a class-level override. extend will return an extender class where all instances will have the methods implemented by functions on that object, just as if that object were passed as the last argument to their constructor. Example:
var Runnable = Java.type("java.lang.Runnable")
var R1 = Java.extend(Runnable, {
run: function() {
print("R1.run() invoked!")
}
})
var r1 = new R1
var t = new java.lang.Thread(r1)
t.start()
t.join()
As you can see, you don't have to pass any object when you create a new instance of R1
as its run()
function was defined already when extending the class. If you also want to add instance-level overrides on these objects, you will have to repeatedly use extend()
to subclass the class-level adapter. For such adapters, the order of precedence is instance-level method, class-level method, superclass method, or UnsupportedOperationException
if the superclass method is abstract. If we continue our previous example:
var R2 = Java.extend(R1);
var r2 = new R2(function() { print("r2.run() invoked!") })
r2.run()
We'll see it'll print "r2.run() invoked!", thus overriding on instance-level the class-level behavior. Note that you must use Java.extend
to explicitly create an instance-override adapter class from a class-override adapter class, as the class-override adapter class is no longer abstract!
To invoke super methods from adapters, call them on the adapter instance prefixing them with super$
, or use the special super-adapter created by the Java._super
method:
var cw = new FilterWriterAdapter(sw) {
write: function(s, off, len) {
s = capitalize(s, off, len)
cw_super.write(s, 0, s.length())
}
}
var cw_super = Java._super(cw)
You can pass multiple types to Java.extend
. Of those, at most one can represent a class, and the rest must be interfaces. The created adapter class will extend the specified class (or java.lang.Object
if no classes are specified) and implement all specified interfaces.
Every specified list of Java types with Java.extend
will have one extender subclass in Nashorn -- repeated invocations of extend
with the same list of types will yield the same extender type. It's a generic adapter that delegates to whatever ECMAScript functions its implementation object has on a per-instance basis. If you are using a security manager then this is somewhat refined: you will end up with one extender subclass for scripts in the same protection domain. Nashorn must create one extender subclass for every extended type for every protection domain from which an extender subclass is requested.
Java.extend
doesn't deduplicate adapter classes for different order of parameters. If you specify
var Runnable = Java.type("java.lang.Runnable")
var Comparable = Java.type("java.lang.Comparable")
var t1 = Java.extend(Runnable, Comparable)
var t2 = Java.extend(Runnable, Comparable)
var t3 = Java.extend(Comparable, Runnable)
then t1
and t2
will be the same type object, but t3
will be distinct from t1
even though they functionally equivalent.