在JavaScript中调⽤Java类和接⼝的⽅法
前⾔
本⽂中所有的代码使⽤ JavaScript 编写,但你也可以⽤其他兼容 JSR 223 的脚本语⾔。这些例⼦可作为脚本⽂件也可以在交互式 Shell 中⼀次运⾏⼀个语句的⽅式来运⾏。在 JavaScript 中访问对象的属性和⽅法的语法与 Java 语⾔相同。
本⽂包含如下⼏部分:
1、访问 Java 类
为了在 JavaScript 中访问原⽣类型或者引⽤ Java 类型,可以调⽤pe() 函数,该函数根据传⼊的完整类名返回对应对象的类型。下⾯代码显⽰如何获取不同的对象类型:
var ArrayList = pe("java.util.ArrayList");
var intType = pe("int");
var StringArrayType = pe("java.lang.String[]");
var int2DArrayType = pe("int[][]");
在 JavaScript 中使⽤pe()函数返回的类型对象的⽅法跟在 Java 的类似。
例如你可以使⽤如下⽅法来实例化⼀个类:
var anArrayList = pe("java.util.ArrayList");
Java 类型对象可⽤来实例化 Java 对象。下⾯的代码显⽰如何使⽤默认的构造函数实例化⼀个新对象以及调⽤包含参数的构造函数:
var ArrayList = pe("java.util.ArrayList");
var defaultSizeArrayList = new ArrayList;
var customSizeArrayList = new ArrayList(16);
你可以使⽤pe()⽅法来获取对象类型,可以使⽤如下⽅法来访问静态属性以及⽅法:
var File = pe("java.io.File");
如果要访问内部静态类,可以传递美元符号 $ 给pe() ⽅法。
下⾯代码显⽰如何返回 Arc2D的Float 内部类:
var Float = pe("Arc2D$Float");
如果你已经有⼀个外部类类型对象,那么你可以像访问属性⼀样访问其内部类,如下所⽰:
var Arc2D = pe("Arc2D")
var Float = Arc2D.Float
由于是⾮静态内部类,必须传递的是外部类实例作为参数给构造函数。
虽然在 JavaScript 中使⽤类型对象跟在 Java 中类似,但其与java.lang.Class 对象还是有些区别的,这个区别就是getClass() ⽅法的返回值。你可以使⽤class 和static 属性来获取这个信息。
下⾯代码显⽰⼆者的区别:
var ArrayList = pe("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 Class()));
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));
在语法和语义上,JavaScript 在编译时类表达式和运⾏时对象都和 Java 语义类似。不过在 Java 中 Class 对象是没有名为static 这样的属性,因为编译时的类表达式不作为对象。
2、导⼊ Java 包和类
为了根据其简单的名称来访问 Java 类,我们可以使⽤ importPackage() 和 importClass() 函数来导⼊ Java 的包和类。这些函数存在
于兼容性脚本⽂件 (mozilla_compat.js) 中。
下⾯例⼦展⽰如何使⽤ importPackage() 和 importClass() 函数:
// 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);
可以通过 Packages 全局变量来访问 Java 包,例如Packages.java.util.Vector或者Packages.javax.swing.JFrame。但标准的 Java SE 包有更简单的访问⽅式,如: java 对应 Packages.java, javax 对应 Packages.javax, 以及 org 对应 。
java.lang 包默认不需要导⼊,因为这会和Object、Boolean、Math 等其他 JavaScript 内建的对象在命名上冲突。此外,导⼊任何 Java 包和类也可能导致 JavaScript 全局作⽤域下的变量名冲突。为了避免冲突,我们定义了⼀个 JavaImporter 对象,并通过with 语句来限制导⼊的 Java 包和类的作⽤域,如下列代码所⽰:
// 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");
};
3、使⽤ Java 数组
为了创建 Java 数组对象,⾸先需要获取 Java 数组类型对象并进⾏初始化。JavaScript 访问数组元素的语法以及length 属性都跟 Java ⼀样,如下列代码所⽰:
var StringArray = pe("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]);
给定⼀个 JavaScript 数组我们还可以⽤() ⽅法将它转成 Java 数组。我们需要将 JavaScript 数组作为参数传给该⽅法,并指定要返回的数组类型,可以是⼀个字符串,或者是类型对象。我们也可以忽略类型对象参数来返回 Object[] 数组。转换操作是根据 ECMAScript 转换规则进⾏的。下⾯代码展⽰如何通过不同的() 的参数将 JavaScript 数组变成 Java 数组:
// 创建⼀个 JavaScript 数组
var anArray = [1, "13", false];
// 将数组转换成 java 的 int[] 数组
var javaIntArray = (anArray, "int[]");
print(javaIntArray[0]); // prints the number 1
print(javaIntArray[1]); // prints the number 13
print(javaIntArray[2]); // prints the number 0
// 将 JavaScript 数组转换成 Java 的 String[] 数组
var javaStringArray = (anArray, pe("java.lang.String[]"));
print(javaStringArray[0]); // prints the string "1"
print(javaStringArray[1]); // prints the string "13"
print(javaStringArray[2]); // prints the string "false"
// 将 JavaScript 数组转换成 Java 的 Object[] 数组
var javaObjectArray = (anArray);
print(javaObjectArray[0]); // prints the number 1
print(javaObjectArray[1]); // prints the string "13"
print(javaObjectArray[2]); // prints the boolean value "false"
你可以使⽤Java.from() ⽅法来将⼀个 Java 数组转成 JavaScript 数组。
下⾯代码演⽰如何将⼀个包含当前⽬录下⽂件列表的数组转成 JavaScript 数组:
// Get the Java File type object
var File = pe("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);
注意:
⼤多数情况下,你可以在脚本中使⽤ Java 对象⽽⽆需转换成 JavaScript 对象。
4、实现 Java 接⼝
在 JavaScript 实现 Java 接⼝的语法与在 Java 总定义匿名类的⽅法类似。我们只需要实例化接⼝并⽤ JavaScript 函数实现其⽅法即可。
下⾯代码演⽰如何实现Runnable 接⼝:
// 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("\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();
如果⼀个⽅法希望⼀个对象,这个对象实现了只有⼀个⽅法的接⼝,你可以传递⼀个脚本函数给这个⽅法,⽽不是传递对象。例如,在上⾯的例⼦中Thread() 构造函数要求⼀个实现了Runnable 接⼝的对象作为参数。我们可以利⽤⾃动转换的优势传递⼀个脚本函数给Thread() 构造器。
下⾯的例⼦展⽰如何创建⼀个Thread 对象⽽⽆需实现Runnable 接⼝:
// 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();
你可以通过传递相关类型对象给d() 函数来实现多个接⼝。
5、扩展抽象 Java 类
你可以实例化⼀个匿名的抽象类的⼦类,只需要给构造函数传递⼀个 JavaScript 对象,对象中包含了
⼀些属性对应了抽象类⽅法实现的值。如果⼀个⽅法是重载的,JavaScript 函数将会提供所有⽅法变种的实现。下⾯例⼦显⽰如何初始化抽象类TimerTask 的⼦类:
var TimerTask = pe("java.util.TimerTask");
var task = new TimerTask({ run: function() { print("Hello World!") } });
除了调⽤构造函数并传递参数,我们还可以在new 表达式后⾯直接提供参数。
下⾯的例⼦显⽰该语法的使⽤⽅法(类似 Java 匿名内部类的定义),这⽐上⾯的例⼦要简单⼀些:
var task = new TimerTask {
run: function() {
print("Hello World!")
}
};
如果抽象类包含单个抽象⽅法(SAM 类型),那么我们就⽆需传递 JavaScript 对象给构造函数,我们可以传递⼀个实现了该⽅法的函数接⼝。下⾯的例⼦显⽰如何使⽤ SAM 类型来简化代码:
var task = new TimerTask(function() { print("Hello World!") });
不管你选择哪种语法,如果你需要调⽤⼀个包含参数的构造函数,你可以在实现对象和函数中指定参数。
如果你想要调⽤⼀个要求 SAM 类型参数的 Java ⽅法,你可以传递⼀个 JavaScript 函数给该⽅法。Nashorn 将根据⽅法需要来实例化⼀个⼦类并使⽤这个函数去实现唯⼀的抽象⽅法。
下⾯的代码显⽰如何调⽤Timer.schedule() ⽅法,该⽅法要求⼀个 TimerTask 对象作为参数:
var Timer = pe("java.util.Timer");
Timer.schedule(function() { print("Hello World!") });
注意:
前⾯的语法假设所要求的 SAM 类型是⼀个接⼝或者包含⼀个默认构造函数,Nashorn ⽤它来初始化⼀个⼦类。这⾥是⽆法使⽤不包含默认构造函数的类的。
6、扩展具体 Java 类
为了避免混淆,扩展抽象类的语法不能⽤于扩展具体类。因为⼀个具体类是可以被实例化的,这样的语法会被解析成试图创建⼀个新的类实例并传递构造函数所需类的对象(如果预期的对象类型是⼀个接⼝)。为了演⽰这个问题,请看看下⾯的⽰例代码:
var t = new java.lang.Thread({ run: function() { print("Thread running!") } });
这⾏代码被解析为扩展了Thread 类并实现了 run() ⽅法,⽽Thread 类的实例化是通过传递给其构造函数⼀个实现了 Runnable 接⼝的对象。
为了扩展⼀个具体类,传递其类型对象给d() 函数,然后返回其⼦类的类型对象。紧接着就可以使⽤这个⼦类的类型对象来创建实例并提供额外的⽅法实现。
下⾯的代码将向你展⽰如何扩展Thread 类并实现run() ⽅法:
var Thread = pe("java.lang.Thread");
var threadExtender = d(Thread);
var t = new threadExtender() {
run: function() { print("Thread running!") }};
7、访问超类(⽗类)的⽅法
想要访问⽗类的⽅法可以使⽤Java .super() 函数。
下⾯的例⼦中显⽰如何扩展java.lang.Exception 类,并访问⽗类的⽅法。
Example 3-1 访问⽗类的⽅法 (super.js)
var Exception = pe("java.lang.Exception");
var ExceptionAdapter = d(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);
}
如果你运⾏上⾯代码将会打印如下内容:
jdk.nashorn.javaadapters.java.lang.Exception: MY EXCEPTION MESSAGE
8、绑定实现到类
前⾯的部分我们描述了如何扩展 Java 类以及使⽤⼀个额外的 JavaScript 对象参数来实现接⼝。实现是绑定的具体某个实例上的,这个实例是通过 new 来创建的,⽽不是整个类。这样做有⼀些好处,例如运⾏时的内存占⽤,因为 Nashorn 可以为每个实现的类型组合创建⼀个单⼀的通⽤适配器。
下⾯的例⼦展⽰不同的实例可以是同⼀个 Java 类,⽽其 JavaScript 实现对象却是不同的:
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));
上述代码将打印如下结果:
javascript全局数组I'm runnable 1!
I'm runnable 2!
We share the same class: true
如果你想传递类的实例给外部 API(如 JavaFX 框架,传递 Application 实例给 JavaFX API),你必须扩展⼀个 Java 类或者实现了与该类绑定的接⼝,⽽不是它的实例。你可以通过传递⼀个 JavaScript 对象绑定实现类并传递给 d() 函数的最后⼀个参数。这个会创建⼀个跟原有类包含⼀样构造函数的新类,因为它们不需要额外实现对象参数。
下⾯的例⼦展⽰如何绑定实现到类中,并演⽰在这种情况下对于不同调⽤的实现类是不同的:
var RunnableImpl1 = d(java.lang.Runnable, function() { print("I'm runnable 1!") });
var RunnableImpl2 = d(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));
上⾯例⼦执⾏结果如下:
I'm runnable 1!
I'm runnable 2!
We share the same class: false
将实现对象从构造函数调⽤移到d() 函数调⽤可以避免在构造函数调⽤中所需的额外参数。每⼀个d()函数的调⽤都需要⼀个指定类的实现对象⽣成⼀个新的 Java 适配器类。带类边界实现的适配器类仍可以使⽤⼀个额外的构造参数⽤来进⼀步重写特定实例的⾏为。因此你可以合并这两种⽅法:你可以在⼀个基础类中提供部分 JavaScript 实现,然后传递给d() 函数,以及在对象中提供实例实现并传递给构造函数。对象定义的函数并传递给构造函数时将覆盖对象的⼀些函
数定义。
下⾯的代码演⽰如何通过给构造函数传递⼀个函数来覆盖类边界对象的函数:
var RunnableImpl = d(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));
上⾯例⼦执⾏后打印结果如下:
I'm runnable 1!
I'm runnable 2!
We share the same class: true
9、选择⽅法重载变体
Java 的⽅法可以通过使⽤不同的参数类型进⾏重载。Java 编译器 (javac) 会在编译时选择正确的⽅法来执⾏。在 Nashorn 中对Java 重载⽅法的解析实在⽅法被调⽤的时候执⾏的。也是根据参数类型来确定正确的⽅法。但如果实际的参数类型会导致模棱两可的情况下,我们可以显式的指定具体某个重载变体。这会提升程序执⾏的性能,因为 Nashorn 引擎⽆需在调⽤过程中去辨别该调⽤哪个⽅法。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论