This chapter describes how to access Java classes and interfaces from scripts.
The code snippets are written in JavaScript, but you can use any scripting language compliant with JSR 223. Examples can be used as script files, or can be run in an interactive shell one expression at a time. The syntax for accessing fields and methods of objects is the same in JavaScript as it is in Java.
This chapter contains the following sections:
To access primitive and reference Java types from JavaScript, call the Java.type()
function, which returns a type object that corresponds to the full name of the class passed in as a string. The following example shows you how to get various type objects:
var ArrayList = Java.type("java.util.ArrayList"); var intType = Java.type("int"); var StringArrayType = Java.type("java.lang.String[]"); var int2DArrayType = Java.type("int[][]");
The type object returned by the Java.type()
function can be used in JavaScript code similar to how a class name is used in Java. For example, you can can use it to instantiate new objects as follows:
var anArrayList = new Java.type("java.util.ArrayList");
Java type objects can be used to instantiate new Java objects. The following example shows you how to instantiate new objects using the default constructor and by passing arguments to another constructor:
var ArrayList = Java.type("java.util.ArrayList"); var defaultSizeArrayList = new ArrayList; var customSizeArrayList = new ArrayList(16);
You can use the type object returned by the Java.type()
function to access static fields and methods as follows:
var File = Java.type("java.io.File"); File.createTempFile("nashorn", ".tmp");
To access a static inner class, use the dollar sign ($) in the argument passed to the Java.type()
method. The following example shows how to return the type object of the Float
inner class in java.awt.geom.Arc2D
:
var Float = Java.type("java.awt.geom.Arc2D$Float");
If you already have the outer class type object, then you can access the inner class as a property of the outer class as follows:
var Arc2D = Java.type("java.awt.geom.Arc2D") var Float = Arc2D.Float
In case of a nonstatic inner class, you must pass an instance of the outer class as the first argument to the constructor.
Although a type object in JavaScript is used similar to the Java class, it is distinct from the java.lang.Class
object, which is returned by the getClass()
method. You can obtain one from the other using the class
and static
properties. The following example shows this distinction:
var ArrayList = Java.type("java.util.ArrayList"); var a = new ArrayList; // All of the following are true: print("Type acts as target of instanceof: " + (a instanceof ArrayList)); print("Class doesn't act as target of instanceof: " + !(a instanceof a.getClass())); print("Type is not the same as instance's getClass(): " + (a.getClass() !== ArrayList)); print("Type's `class` property is the same as instance's getClass(): " + (a.getClass() === ArrayList.class)); print("Type is the same as the `static` property of the instance's getClass(): " + (a.getClass().static === ArrayList));
Syntactically and semantically, this distinction between compile-time class expressions and runtime class objects makes JavaScript similar to Java code. However, there is no equivalent of the static
property for a Class
object in Java, because compile-time class expressions are never expressed as objects.
To access Java classes by their simple names, you can use the importPackage()
and importClass()
functions to import Java packages and classes. These functions are built into the compatibility script (mozilla_compat.js
). The following example shows you how to use the importPackage()
and importClass()
functions:
// Load compatibility script load("nashorn:mozilla_compat.js"); // Import the java.awt package importPackage(java.awt); // Import the java.awt.Frame class importClass(java.awt.Frame); // Create a new Frame object var frame = new java.awt.Frame("hello"); // Call the setVisible() method frame.setVisible(true); // Access a JavaBean property print(frame.title);
You can access Java packages using the Packages
global variable (for example, Packages.java.util.Vector
or Packages.javax.swing.JFrame
), but standard Java SE packages have shortcuts (java
for Packages.java
, javax
for Packages.javax
, and org
for Packages.org
).
The java.lang
package is not imported by default, because its classes would conflict with Object
, Boolean
, Math
, and other built-in JavaScript objects. Furthermore, importing any Java package or class can lead to conflicts with the global variable scope in JavaScript. To avoid this, define a JavaImporter
object and use the with
statement to limit the scope of the imported Java packages and classes, as shown in the following example:
// Create a JavaImporter object with specified packages and classes to import var Gui = new JavaImporter(java.awt, javax.swing); // Pass the JavaImporter object to the "with" statement and access the classes // from the imported packages by their simple names within the statement's body with (Gui) { var awtframe = new Frame("AWT Frame"); var jframe = new JFrame("Swing JFrame"); };
To create a Java array object, you first have to get the Java array type object, and then instantiate it. The syntax for accessing array elements and the length
property in JavaScript is the same as in Java, as shown in the following example:
var StringArray = Java.type("java.lang.String[]"); var a = new StringArray(5); // Set the value of the first element a[0] = "Scripting is great!"; // Print the length of the array print(a.length); // Print the value of the first element print(a[0]);
Given a JavaScript array, you can convert it to a Java array using the Java.to()
method. You must pass the JavaScript array variable to this method and the type of array to be returned, either as a string or a type object. You can also omit the array type argument to return an Object[]
array. Conversion is performed according to the ECMAScript conversion rules. The following example shows you how to convert a JavaScript array to a Java array using various Java.to()
method arguments:
// Create a JavaScript array var anArray = [1, "13", false]; // Convert the JavaScript array to a Java int[] array var javaIntArray = Java.to(anArray, "int[]"); print(javaIntArray[0]); // prints the number 1 print(javaIntArray[1]); // prints the number 13 print(javaIntArray[2]); // prints the number 0 // Convert the JavaScript array to a Java String[] array var javaStringArray = Java.to(anArray, Java.type("java.lang.String[]")); print(javaStringArray[0]); // prints the string "1" print(javaStringArray[1]); // prints the string "13" print(javaStringArray[2]); // prints the string "false" // Convert the JavaScript array to a Java Object[] array var javaObjectArray = Java.to(anArray); print(javaObjectArray[0]); // prints the number 1 print(javaObjectArray[1]); // prints the string "13" print(javaObjectArray[2]); // prints the boolean value "false"
Given a Java array, you can convert it to a JavaScript array using the Java.from()
method. The following example shows you how to convert a Java array that contains a list of files in the current directory to a JavaScript array with the same contents:
// Get the Java File type object var File = Java.type("java.io.File"); // Create a Java array of File objects var listCurDir = new File(".").listFiles(); // Convert the Java array to a JavaScript array var jsList = Java.from(listCurDir); // Print the JavaScript array print(jsList);
Note: In most cases, you can use the Java array in scripts without converting the Java array to a JavaScript array. |
The syntax for implementing a Java interface in JavaScript is similar to how anonymous classes are declared in Java. You instantiate an interface and implement its methods (as JavaScript functions) in the same expression. The following example shows you how to implement the Runnable
interface:
// Create an object that implements the Runnable interface by implementing // the run() method as a JavaScript function var r = new java.lang.Runnable() { run: function() { print("running...\n"); } }; // The r variable can be passed to Java methods that expect an object implementing // the java.lang.Runnable interface var th = new java.lang.Thread(r); th.start(); th.join();
If a method expects an object that implements an interface with only one method, you can pass a script function to this method instead of the object. For instance, in the previous example, the Thread()
constructor expects an object that implements the Runnable
interface, which defines only one method. You can take advantage of automatic conversion and pass a script function to the Thread()
constructor instead of the object. The following example shows you how you can create a Thread
object without implementing the Runnable
interface:
// Define a JavaScript function function func() { print("I am func!"); }; // Pass the JavaScript function instead of an object that implements // the java.lang.Runnable interface var th = new java.lang.Thread(func); th.start(); th.join();
You can implement multiple interfaces in a subclass by passing the relevant type objects to the Java.extend()
function. For more information, see Extending Concrete Java Classes.
You can instantiate an anonymous subclass of an abstract Java class by passing to its constructor a JavaScript object with properties whose values are functions that implement the abstract methods. If a method is overloaded, then the JavaScript function will provide implementations for all variants of the method. The following example shows you how to instantiate a subclass of the abstract TimerTask
class:
var TimerTask = Java.type("java.util.TimerTask"); var task = new TimerTask({ run: function() { print("Hello World!") } });
Instead of invoking the constructor and passing an argument to it, you can provide the argument directly after the new
expression. The following example shows you how this syntax (similar to Java anonymous inner class definition) can simplify the second line in the previous example:
var task = new TimerTask { run: function() { print("Hello World!") } };
If the abstract class has a single abstract method (a SAM type), then instead of passing a JavaScript object to the constructor, you can pass the function that implements the method. The following example shows how you can simplify the syntax when using a SAM type:
var task = new TimerTask(function() { print("Hello World!") });
Whichever syntax you choose, if you need to invoke a constructor with arguments, you can specify the arguments after the implementation object or function.
If you want to invoke a Java method that takes a SAM type as the argument, you can pass a JavaScript function to the method. Nashorn will instantiate a subclass of the expected class and use the function to implement its only abstract method. The following example shows you how to invoke the Timer.schedule()
method, which expects a TimerTask
object as the argument:
var Timer = Java.type("java.util.Timer"); Timer.schedule(function() { print("Hello World!") });
Note: The previous syntax assumes that the expected SAM type is either an interface or it has a default constructor, which is used by Nashorn to instatiate a subclass of the expected class. It is not possible to use a non-default constructor. |
To avoid ambiguity, the syntax for extending abstract classes is not allowed for concrete classes. Because a concrete class can be instantiated, such syntax may be interpreted as an attempt to create a new instance of the class and pass to it an object of the type expected by the constructor (in case when the expected object type is an interface). As an illustration of this, consider the following example:
var t = new java.lang.Thread({ run: function() { print("Thread running!") } });
This code can be interpreted both as extending the Thread
class with the specified implementation of the run()
method, and the instantiation of the Thread
class by passing to its constructor an object that implenents the Runnable
interface (for more information, see Implementing Java Interfaces).
To extend a concrete Java class, pass its type object to the Java.extend()
function that returns a type object of the subclass. Then, use the type object of the subclass as a JavaScript-to-Java adapter to create instances of the subclass with the specified method implementations. The following example shows you how to extend the Thread
class with the specified implementation of the run()
method:
var Thread = Java.type("java.lang.Thread"); var threadExtender = Java.extend(Thread); var t = new threadExtender() { run: function() { print("Thread running!") }};
The Java.extend()
function can take a list of multiple type objects. You can specify no more than one type object of a Java class, and as many type objects of Java interfaces as you want. The returned type object extends the specified class (or java.lang.Object
if no class is specified) and implements all interfaces. The class type object does not have to be first in the list.
To access methods in the superclass, you can use the Java.super()
function. Example 3-1 shows you how to extend the java.lang.Exception
class and access the methods in the superclass.
Example 3-1 Accessing Methods of a Supreclass (super.js)
var Exception = Java.type("java.lang.Exception"); var ExceptionAdapter = Java.extend(Exception); var exception = new ExceptionAdapter("My Exception Message") { getMessage: function() { var _super_ = Java.super(exception); return _super_.getMessage().toUpperCase(); } } try { throw exception; } catch (ex) { print(exception); }
If you run the code in Example 3-1, the following will be printed to standard output:
jdk.nashorn.javaadapters.java.lang.Exception: MY EXCEPTION MESSAGE
The previous sections described how to extend Java classes and implement interfaces using an extra JavaScript object parameter in the constructor that specifies the implementation. The implementation is therefore bound to the actual instance created with new
, and not to the whole class. This has some advantages, for example, in the memory footprint of the runtime, because Nashorn can create a single universal adapter for every combination of types being implemented. However, the following example shows that different instances have the same Java class regardless of them having different JavaScript implementation objects:
var Runnable = java.lang.Runnable; var r1 = new Runnable(function() { print("I'm runnable 1!") }); var r2 = new Runnable(function() { print("I'm runnable 2!") }); r1.run(); r2.run(); print("We share the same class: " + (r1.class === r2.class));
The previous example prints the following:
I'm runnable 1! I'm runnable 2! We share the same class: true
If you want to pass the class for instantiation to an external API (for example, when using the JavaFX framework, the Application
class is passed to the JavaFX API, which instantiates it), you must extend a Java class or implement an interface with the implementation bound to the class, rather than to its instances. You can bind the implementation to the class by passing a JavaScript object with the implementation as the last argument to the Java.extend()
function. This creates a class with the same constructors as the original class, because they do not need an extra implementation object parameter. The following example shows you how to bind implementations to the class, and demonstrates that in this case the implementation classes for different invocations are different:
var RunnableImpl1 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") }); var RunnableImpl2 = Java.extend(java.lang.Runnable, function() { print("I'm runnable 2!") }); var r1 = new RunnableImpl1();var r2 = new RunnableImpl2(); r1.run(); r2.run(); print("We share the same class: " + (r1.class === r2.class));
The previous example prints the following:
I'm runnable 1! I'm runnable 2! We share the same class: false
Moving the implementation objects from the constructor invocations to the invocations of the Java.extend()
functions eliminates the need for an extra argument in the constructor invocations. Every invocation of the Java.extend()
function with a class-specific implementation object produces a new Java adapter class.The adapter classes with class-bound implementations can still take an additional constructor argument to further override the behavior for certain instances. Thus, you can combine the two approaches: you can provide part of the implementation in a class-based JavaScript implementation object passed to the Java.extend()
function, and provide implementations for instances in objects passed to the constructor. A function defined by the object passed to the constructer overrides the function defined by the class-bound object. The following example shows you how to override the function defined in the class-bound object with a function passed to the constructor:
var RunnableImpl = Java.extend(java.lang.Runnable, function() { print("I'm runnable 1!") }); var r1 = new RunnableImpl(); var r2 = new RunnableImpl(function() { print("I'm runnable 2!") }); r1.run(); r2.run(); print("We share the same class: " + (r1.class === r2.class));
The previous example prints the following:
I'm runnable 1! I'm runnable 2! We share the same class: true
Java methods can be overloaded by argument types. The Java Compiler (javac
) selects the correct method variant during compilation. Overload resolution for Java methods called from Nashorn is performed when the method is invoked. The correct variant is selected automatically based on the argument types. However, if you run into genuine ambiguity with actual argument types, you can specify a particular overload variant explicitly. This may also improve performance, because the Nashorn engine will not need to perform overload resolution during invocation.
Overload variants are exposed as special properties. You can refer to them in the form of strings that contain the name of the method followed by the argument types within parantheses. The following example shows how to invoke the variant of the System.out.println()
method that expects an Object
class as the argument, and pass "hello"
to it:
var out = java.lang.System.out; out["println(Object)"]("hello");
In the previous example, the unqualified class name (Object
) is sufficient, because it uniquely identifies the correct signature. The only case when you must use the fully qualified class names in the signature is when two overload variants use different parameter types with identical unqualified names (this is possible if parameter types with the same name are from different packages).
Most conversions between Java and JavaScript work as you expect. Previous sections described some of the less evident data type mappings between Java and JavaScript. For example, arrays must be explicitly converted, and JavaScript functions are automatically converted to SAM types when they are passed as parameters to Java methods.Every JavaScript object implements the java.util.Map
interface to enable APIs to receive maps directly. When numbers are passed to a Java API, they are converted to the expected target numeric type, either boxed or primitive. However, if the target type is less specific (for example, Number
), you can only expect them to be of type Number
, and must test specifically for whether the type is a boxed Double
, Integer
, Long
, and so on. The number can be any boxed type due to internal optimizations. Also, you can pass any JavaScript value to a Java API expecting either a boxed or primitive number, because the ToNumber
conversion algorithm defined by the JavaScript specification will be applied to the value.If a Java method expects a String
or a Boolean
object, the values will be converted using all conversions allowed by the ToString
and ToBoolean
conversions defined by the JavaScript specification.
Caution: Due to internal performance optimizations of string operations, JavaScript strings are not always necessarily of type |