Preface
All the code in this article is written in JavaScript, but you can also use other JSR 223-compatible scripting languages. These examples can be run as script files or in interactive shells by running one statement at a time. The syntax for accessing properties and methods of objects in JavaScript is the same as that of Java language.
This article contains the following parts:
1. Access Java classes
In order to access native types or reference Java types in JavaScript, you can call the Java.type() function, which returns the type of the corresponding object based on the passed in full class name. The following code shows how to get different object types:
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 method of returning a type object using Java.type() function in JavaScript is similar to that in Java.
For example, you can instantiate a class using the following method:
var anArrayList = new Java.type("java.util.ArrayList");Java type objects can be used to instantiate Java objects. The following code shows how to instantiate a new object using the default constructor and call the constructor containing the parameters:
var ArrayList = Java.type("java.util.ArrayList");var defaultSizeArrayList = new ArrayList;var customSizeArrayList = new ArrayList(16); You can use Java.type() method to get the object type, and you can use the following methods to access static properties and methods:
var File = Java.type("java.io.File");File.createTempFile("nashorn", ".tmp"); If you want to access the internal static class, you can pass the dollar sign $ to Java.type() method.
The following code shows how to return Float inner class of java.awt.geom.Arc2D :
var Float = Java.type("java.awt.geom.Arc2D$Float");If you already have an external class type object, then you can access its inner class just like you would access the property, as shown below:
var Arc2D = Java.type("java.awt.geom.Arc2D")var Float = Arc2D.FloatSince it is a non-static inner class, the external class instance must be passed as a parameter to the constructor.
Although using type objects in JavaScript is similar to that in Java, it is still somewhat different from the java.lang.Class object. This difference is the return value of getClass() method. You can use class and static properties to get this information.
The following code shows the difference between the two:
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));Syntax and semantics, JavaScript class expressions and runtime objects are similar to Java semantics. However, in Java, the Class object does not have a property named static because the compiled class expression is not used as an object.
2. Import Java packages and classes
In order to access Java classes based on its simple name, we can use importPackage() and importClass() functions to import Java packages and classes. These functions exist in the compatibility script file (mozilla_compat.js).
The following example shows how to use importPackage() and importClass() functions:
// Load compatibility scriptload("nashorn:mozilla_compat.js");// Import the java.awt packageimportPackage(java.awt);// Import the java.awt.Frame classimportClass(java.awt.Frame);// Create a new Frame objectvar frame = new java.awt.Frame("hello");// Call the setVisible() methodframe.setVisible(true);// Access a JavaBean propertyprint(frame.title); Java packages can be accessed through the Packages global variable, such as Packages.java.util.Vector or Packages.javax.swing.JFrame . However, the standard Java SE package has simpler access methods, such as: java corresponds to Packages.java, javax corresponds to Packages.javax, and org corresponds to Packages.org.
The java.lang package does not require import by default, because this will conflict with other JavaScript built-in objects such as Object , Boolean , and Math . In addition, importing any Java packages and classes may also cause variable name conflicts under the global scope of JavaScript. To avoid conflicts, we define a JavaImporter object and limit the scope of imported Java packages and classes through with statement, as shown in the following code:
// Create a JavaImporter object with specified packages and classes to importvar 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 bodywith (Gui) { var awtframe = new Frame("AWT Frame"); var jframe = new JFrame("Swing JFrame");};3. Use Java arrays
In order to create a Java array object, you first need to obtain the Java array type object and initialize it. The syntax and length attribute of JavaScript access to array elements are the same as Java, as shown in the following code:
var StringArray = Java.type("java.lang.String[]");var a = new StringArray(5);// Set the value of the first elementa[0] = "Scripting is great!";// Print the length of the arrayprint(a.length);// Print the value of the first elementprint(a[0]); Given a JavaScript array, we can also convert it into a Java array using Java.to() method. We need to pass the JavaScript array as a parameter to the method and specify the type of the array to be returned, which can be a string or a type object. We can also ignore the type object parameters to return the Object[] array. The conversion operation is performed according to the ECMAScript conversion rules. The following code shows how to turn a JavaScript array into a Java array through different Java.to() parameters:
// Create a JavaScript array var anArray = [1, "13", false];// Convert an array to java int[] array var javaIntArray = Java.to(anArray, "int[]");print(javaIntArray[0]); // prints the number 1print(javaIntArray[1]); // prints the number 13print(javaIntArray[2]); // prints the number 0// Convert JavaScript array to 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 JavaScript array to Java Object[] array var javaObjectArray = Java.to(anArray);print(javaObjectArray[0]); // prints the number 1print(javaObjectArray[1]); // prints the string "13"print(javaObjectArray[2]); // prints the boolean value "false" You can use Java.from() method to convert a Java array into a JavaScript array.
The following code shows how to convert an array containing the list of files in the current directory into a JavaScript array:
// Get the Java File type objectvar File = Java.type("java.io.File");// Create a Java array of File objectsvar listCurDir = new File(".").listFiles();// Convert the Java array to a JavaScript arrayvar jsList = Java.from(listCurDir);// Print the JavaScript arrayprint(jsList);Notice:
In most cases, you can use Java objects in your script without converting them to JavaScript objects.
4. Implement Java interface
The syntax of implementing Java interfaces in JavaScript is similar to the method of defining anonymous classes in Java. We just need to instantiate the interface and implement its methods with JavaScript functions.
The following code demonstrates how to implement the Runnable interface:
// Create an object that implements the Runnable interface by implementing// the run() method as a JavaScript functionvar 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 interfacevar th = new java.lang.Thread(r);th.start();th.join(); If a method wants an object, this object implements an interface with only one method, you can pass a script function to this method instead of passing the object. For example, in the above example, Thread() constructor requires an object that implements Runnable interface as a parameter. We can take advantage of the automatic conversion to pass a script function to the Thread() constructor.
The following example shows how to create a Thread object without implementing a Runnable interface:
// Define a JavaScript functionfunction function func() { print("I am func!");};// Pass the JavaScript function instead of an object that implements// the java.lang.Runnable interfacevar th = new java.lang.Thread(func);th.start();th.join(); You can implement multiple interfaces by passing related type objects to Java.extend() function.
5. Extend abstract Java classes
You can instantiate an anonymous abstract class subclass, just pass a JavaScript object to the constructor, which contains some properties corresponding to the values implemented by the abstract class method. If a method is overloaded, the JavaScript function will provide implementations of all method variants. The following example shows how to initialize a subclass of the abstract class TimerTask:
var TimerTask = Java.type("java.util.TimerTask");var task = new TimerTask({ run: function() { print("Hello World!") } }); In addition to calling the constructor and passing parameters, we can also provide parameters directly after the new expression.
The following example shows how to use this syntax (similar to the definition of anonymous internal classes in Java), which is a bit simpler than the above example:
var task = new TimerTask { run: function() { print("Hello World!") }};If the abstract class contains a single abstract method (SAM type), then we do not need to pass a JavaScript object to the constructor, we can pass a function interface that implements the method. The following example shows how to use SAM types to simplify the code:
var task = new TimerTask(function() { print("Hello World!") });Regardless of the syntax you choose, if you need to call a constructor that contains parameters, you can specify parameters in the implementation object and function.
If you want to call a Java method that requires SAM type parameters, you can pass a JavaScript function to the method. Nashorn will instantiate a subclass according to the method needs and use this function to implement the unique abstract method.
The following code shows how to call Timer.schedule() method, which requires a TimerTask object as a parameter:
var Timer = Java.type("java.util.Timer");Timer.schedule(function() { print("Hello World!") });Notice:
The previous syntax assumes that the required SAM type is an interface or contains a default constructor, which Nashorn uses to initialize a subclass. This is not possible to use a class that does not contain the default constructor.
6. Extend specific Java classes
To avoid confusion, the syntax for extending abstract classes cannot be used to extend concrete classes. Because a concrete class can be instantiated, such a syntax is parsed into an attempt to create a new class instance and pass the object of the class required by the constructor (if the expected object type is an interface). To demonstrate this problem, please take a look at the following sample code:
var t = new java.lang.Thread({ run: function() { print("Thread running!") } }); This line of code is parsed to extend the Thread class and implement run() method, and the instantiation of Thread class is passed to its constructor an object that implements the Runnable interface.
To extend a concrete class, pass its type object to the Java.extend() function, and then return its type object to its subclass. Then you can use the type object of this subclass to create instances and provide additional method implementations.
The following code will show you how to extend Thread class and implement run() method:
var Thread = Java.type("java.lang.Thread");var threadExtender = Java.extend(Thread);var t = new threadExtender() { run: function() { print("Thread running!") }}; Java.extend() function can get a list of multiple types of objects. You can specify no more than one Java type object, or you can specify the number of type objects as many as Java interfaces. The returned type object extends the specified class (or java.lang.Object , if there is no specified type object), this class implements all interfaces. The type objects of the class do not need to be at the top of the list.
7. Methods to access superclass (parent class)
Methods that want to access the parent class can use the Java .super() function.
The following example shows how to extend java.lang.Exception class and access the methods of the parent class.
Example 3-1 Method to access the parent class (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 above code, you will print the following:
jdk.nashorn.javaadapters.java.lang.Exception: MY EXCEPTION MESSAGE
8. Binding to implement to class
In the previous section we described how to extend Java classes and implement the interface using an additional JavaScript object parameter. The implementation is bound on a specific instance, which is created through new, not the entire class. There are some benefits to doing this, such as the memory footprint at runtime, as Nashorn can create a single universal adapter for each implementation's combination of types.
The following example shows that different instances can be the same Java class, but their JavaScript implementation objects are different:
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 above code will print the following result:
I'm runnable 1!I'm runnable 2!We share the same class: true
If you want to pass an instance of a class to an external API (such as the JavaFX framework, passing an Application instance to the JavaFX API), you must extend a Java class or implement an interface bound to that class, rather than its instance. You can implement the class by passing a JavaScript object binding and passing it to the last parameter of the Java.extend() function. This creates a new class with the same constructor as the original class, because they do not require additional implementation of object parameters.
The following example shows how to bind an implementation into a class and demonstrates that implementation classes are different for different calls in this case:
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 above example execution results are as follows:
I'm runnable 1!I'm runnable 2!We share the same class: false
Moving the implementation object from the constructor call to Java.extend() function call avoids the extra parameters required in the constructor call. Each call to Java.extend() function requires an implementation object of the specified class to generate a new Java adapter class. Adapter classes implemented with class boundaries can still use an additional constructor parameter to further override the behavior of a specific instance. So you can merge these two methods: you can provide part of the JavaScript implementation in a base class, then pass it to the Java.extend() function, and provide an instance implementation in the object and pass it to the constructor. Some function definitions of the object will be overwritten when the object is defined and passed to the constructor.
The following code demonstrates how to overwrite a function of a class boundary object by passing a function 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 printing results after the above example is executed are as follows:
I'm runnable 1!I'm runnable 2!We share the same class: true
9. Select method overload variant
Java methods can be overloaded by using different parameter types. The Java compiler (javac) will select the correct method to execute at compile time. The parsing of Java overloaded methods in Nashorn is executed when the method is called. It is also a way to determine the correct method based on the parameter type. But if the actual parameter type will cause ambiguity, we can explicitly specify a specific overloaded variant. This improves the performance of program execution, because the Nashorn engine does not need to distinguish which method to call during the call.
Overloaded variants are exposed as special properties. We can refer to them in the form of strings, which contain method names and parameter types, and are surrounded by parentheses.
The following example shows how to call System.out.println() method with Object parameter variant, we pass a "hello" string to it:
var out = java.lang.System.out;out["println(Object)"]("hello");In the above example, using the Object class name alone is enough because it is the signature that uniquely identifies the correct one. The case where you have to use the full class name is that two overloaded variant functions use different parameter types, but the type has the same name (this is possible, for example, different packages contain the same class name).
10. Mapping data types
The vast majority of Java and JavaScript previous conversions work well as you would expect. In the previous chapters, we mentioned some simple data type mappings between Java and JavaScript. For example, array type data can be explicitly converted, JavaScript functions can be automatically converted to SAM types when passed as parameters to Java methods. Each JavaScript object implements the java.util.Map interface to allow the API to directly accept mappings. When passing values to the Java API, they will be converted to the expected target numerical type, which can be an encapsulation type or a primitive data type. If the target type is not very certain (such as Number), you can only require it to be the Number type, and then specifically encapsulate the type, such as Double, Integer, Long, etc. Internal optimization makes the numerical value of any package type. Colleagues, you can pass any JavaScript value to the Java API, whether it is an encapsulated type or a primitive type, because JavaScript's ToNumber conversion algorithm will automatically process its value. If a Java method requires a String or Boolean object parameter, JavaScript will use ToString and ToBoolean transformations to obtain its value.
Notice:
Because of internal performance optimization considerations for string operations, JavaScript strings do not always correspond to the java.lang.String type, or they may also be java.lang.CharSequence type. If you pass a JavaScript string to a Java method that requires the java.lang.String parameter, then the JavaScript string is the java.lang.String type, but if your method signature wants to be more generic (for example, the accepted parameter type is java.lang.Object), then the parameter object you get will make an object that implements CharSequence class, not a Java string object.
Summarize
The above is the entire content of this article. I hope it will be of some help to everyone's study and work. If you have any questions, you can leave a message to communicate.