在JavaScript中调用Java类和接口的方法
作者:daisy 发布时间:2024-04-10 10:42:49
前言
本文中所有的代码使用 JavaScript 编写,但你也可以用其他兼容 JSR 223 的脚本语言。这些例子可作为脚本文件也可以在交互式 Shell 中一次运行一个语句的方式来运行。在 JavaScript 中访问对象的属性和方法的语法与 Java 语言相同。
本文包含如下几部分:
1、访问 Java 类
为了在 JavaScript 中访问原生类型或者引用 Java 类型,可以调用 Java.type()
函数,该函数根据传入的完整类名返回对应对象的类型。下面代码显示如何获取不同的对象类型:
var ArrayList = Java.type("java.util.ArrayList");
var intType = Java.type("int");
var StringArrayType = Java.type("java.lang.String[]");
var int2DArrayType = Java.type("int[][]");
在 JavaScript 中使用 Java.type()
函数返回的类型对象的方法跟在 Java 的类似。
例如你可以使用如下方法来实例化一个类:
var anArrayList = new Java.type("java.util.ArrayList");
Java 类型对象可用来实例化 Java 对象。下面的代码显示如何使用默认的构造函数实例化一个新对象以及调用包含参数的构造函数:
var ArrayList = Java.type("java.util.ArrayList");
var defaultSizeArrayList = new ArrayList;
var customSizeArrayList = new ArrayList(16);
你可以使用 Java.type()
方法来获取对象类型,可以使用如下方法来访问静态属性以及方法:
var File = Java.type("java.io.File");
File.createTempFile("nashorn", ".tmp");
如果要访问内部静态类,可以传递美元符号 $ 给 Java.type()
方法。
下面代码显示如何返回 java.awt.geom.Arc2D
的 Float
内部类:
var Float = Java.type("java.awt.geom.Arc2D$Float");
如果你已经有一个外部类类型对象,那么你可以像访问属性一样访问其内部类,如下所示:
var Arc2D = Java.type("java.awt.geom.Arc2D")
var Float = Arc2D.Float
由于是非静态内部类,必须传递的是外部类实例作为参数给构造函数。
虽然在 JavaScript 中使用类型对象跟在 Java 中类似,但其与 java.lang.Class
对象还是有些区别的,这个区别就是 getClass()
方法的返回值。你可以使用 class
和 static
属性来获取这个信息。
下面代码显示二者的区别:
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));
在语法和语义上,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 对应 Packages.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 = 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]);
给定一个 JavaScript 数组 我们还可以用 Java.to()
方法将它转成 Java 数组。我们需要将 JavaScript 数组作为参数传给该方法,并指定要返回的数组类型,可以是一个字符串,或者是类型对象。我们也可以忽略类型对象参数来返回 Object[] 数组。转换操作是根据 ECMAScript 转换规则进行的。下面代码展示如何通过不同的 Java.to()
的参数将 JavaScript 数组变成 Java 数组:
// 创建一个 JavaScript 数组
var anArray = [1, "13", false];
// 将数组转换成 java 的 int[] 数组
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
// 将 JavaScript 数组转换成 Java 的 String[] 数组
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"
// 将 JavaScript 数组转换成 Java 的 Object[] 数组
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"
你可以使用 Java.from()
方法来将一个 Java 数组转成 JavaScript 数组。
下面代码演示如何将一个包含当前目录下文件列表的数组转成 JavaScript 数组:
// 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);
注意:
大多数情况下,你可以在脚本中使用 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("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();
如果一个方法希望一个对象,这个对象实现了只有一个方法的接口,你可以传递一个脚本函数给这个方法,而不是传递对象。例如,在上面的例子中 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();
你可以通过传递相关类型对象给 Java.extend()
函数来实现多个接口。
5、扩展抽象 Java 类
你可以实例化一个匿名的抽象类的子类,只需要给构造函数传递一个 JavaScript 对象,对象中包含了一些属性对应了抽象类方法实现的值。如果一个方法是重载的,JavaScript 函数将会提供所有方法变种的实现。下面例子显示如何初始化抽象类 TimerTask 的子类:
var TimerTask = Java.type("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 = Java.type("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 接口的对象。
为了扩展一个具体类,传递其类型对象给 Java.extend()
函数,然后返回其子类的类型对象。紧接着就可以使用这个子类的类型对象来创建实例并提供额外的方法实现。
下面的代码将向你展示如何扩展 Thread
类并实现 run()
方法:
var Thread = Java.type("java.lang.Thread");
var threadExtender = Java.extend(Thread);
var t = new threadExtender() {
run: function() { print("Thread running!") }};
Java.extend()
函数可以获取多个类型对象的列表。你可以指定不超过一个 Java 的类型对象,也可以指定跟 Java接口一样多的类型对象数量。返回的类型对象扩展了指定的类(或者是 java.lang.Object
,如果没有指定类型对象的话),这个类实现了所有的接口。类的类型对象无需在列表中排在首位。
7、访问超类(父类)的方法
想要访问父类的方法可以使用 Java .super()
函数。
下面的例子中显示如何扩展 java.lang.Exception
类,并访问父类的方法。
Example 3-1 访问父类的方法 (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);
}
如果你运行上面代码将会打印如下内容:
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));
上述代码将打印如下结果:
I'm runnable 1!
I'm runnable 2!
We share the same class: true
如果你想传递类的实例给外部 API(如 JavaFX 框架,传递 Application 实例给 JavaFX API),你必须扩展一个 Java 类或者实现了与该类绑定的接口,而不是它的实例。你可以通过传递一个 JavaScript 对象绑定实现类并传递给 Java.extend() 函数的最后一个参数。这个会创建一个跟原有类包含一样构造函数的新类,因为它们不需要额外实现对象参数。
下面的例子展示如何绑定实现到类中,并演示在这种情况下对于不同调用的实现类是不同的:
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));
上面例子执行结果如下:
I'm runnable 1!
I'm runnable 2!
We share the same class: false
将实现对象从构造函数调用移到 Java.extend()
函数调用可以避免在构造函数调用中所需的额外参数。每一个 Java.extend()
函数的调用都需要一个指定类的实现对象生成一个新的 Java 适配器类。带类边界实现的适配器类仍可以使用一个额外的构造参数用来进一步重写特定实例的行为。因此你可以合并这两种方法:你可以在一个基础类中提供部分 JavaScript 实现,然后传递给 Java.extend()
函数,以及在对象中提供实例实现并传递给构造函数。对象定义的函数并传递给构造函数时将覆盖对象的一些函数定义。
下面的代码演示如何通过给构造函数传递一个函数来覆盖类边界对象的函数:
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));
上面例子执行后打印结果如下:
I'm runnable 1!
I'm runnable 2!
We share the same class: true
9、选择方法重载变体
Java 的方法可以通过使用不同的参数类型进行重载。Java 编译器 (javac) 会在编译时选择正确的方法来执行。在 Nashorn 中对Java 重载方法的解析实在方法被调用的时候执行的。也是根据参数类型来确定正确的方法。但如果实际的参数类型会导致模棱两可的情况下,我们可以显式的指定具体某个重载变体。这会提升程序执行的性能,因为 Nashorn 引擎无需在调用过程中去辨别该调用哪个方法。
重载的变种作为特别的属性暴露出来。我们可以用字符串的形式来引用它们,字符串包含方法名称、参数类型,两者使用圆括号包围起来。
下面的例子显示如何调用 System.out.println()
方法带 Object
参数的变种,我们传递一个 “hello” 字符串给它:
var out = java.lang.System.out;
out["println(Object)"]("hello");
上述的例子中,光使用 Object 类名就足够了,因为它是唯一标识正确的签名。你必须使用完整的类名的情况是两个重载变种函数使用不同的参数类型,但是类型的名称相同(这是可能的,例如不同包中包含相同的类名)。
10、映射数据类型
绝大多数 Java 和 JavaScript 之前的转换如你所期待的运行良好。前面的章节中我们提到过一些简单的 Java 和 JavaScript 之间的数据类型映射。例如可以显式地转换数组类型数据,JavaScript 函数可以在当成参数传递给 Java 方法时自动转换成 SAM 类型。每个 JavaScript 对象实现了 java.util.Map
接口来让 API 可以直接接受映射。当传递数值给 Java API 时,会被转成所期待的目标数值类型,可以是封装类型或者是原始数据类型。如果目标类型不太确定(如 Number),你只能要求它必须是 Number 类型,然后专门针对该类型是封装了 Double、Integer 或者是 Long 等等。内部的优化使得数值可以是任何封装类型。同事你可以传递任意 JavaScript 值给 Java API,不管是封装类型还是原始类型,因为 JavaScript 的 ToNumber
转换算法将会自动处理其值。如果 Java 方法要求一个 String
或者 Boolean
对象参数,JavaScript 将会使用 ToString
和 ToBoolean
转换来获得其值。
注意:
因为对字符串操作的内部性能优化考虑,JavaScript 字符串并不总是对应 java.lang.String 类型,也可能是 java.lang.CharSequence
类型。如果你传递一个 JavaScript 字符串给要求 java.lang.String
参数的 Java 方法,那么这个 JavaScript 字符串就是 java.lang.String
类型,但如果你的方法签名想要更加泛型化(例如接受的参数类型是 java.lang.Object),那么你得到的参数对象就会使一个实现了 CharSequence
类的对象,而不是一个 Java 字符串对象。
总结
以上就是这篇文章的全部内容,希望对大家的学习和工作能有一定的帮助,如果有疑问大家可以留言交流。
猜你喜欢
- pyinstaller打包问题简单介绍一下pyinstaller常用的参数:可选参数示例说明-Fpyinstaller -F demo.py
- 本人最近在做字符识别,所以自行在网上寻找方法,接触到tesseract,自己按照网上方法做的时候,也遇到一些问题
- 实际开发过程中,我们经常会被各种宽度,高度计算搞晕。尤其是使用了rem的计算方式,自适应布局难倒一大片程序员。为了解决这类问题,我觉得可以利
- 方法一(只有mdf没有日志文件的可以恢复) 证明有效 1.新建同名数据库。 2.把该数据库设置为脱机。 3.删除其日志文件(.LDF),不删
- 前言通常我们的python代码都是遵循PEP8的规范化格式,目的是为了保持代码的一致性、可读性。,这里给大家推荐几个常用的静态代码检查工具,
- 目录1.列表中存储字典:1.列表中存储多个字典2.访问列表中字典的值3.遍历访问多个值2.字典中存储列表1.访问字典中的列表元素2.访问字典
- 本文实例讲述了Python实现公历(阳历)转农历(阴历)的方法。分享给大家供大家参考,具体如下:两个要点:1、公历转农历用了查表法(第126
- 实现思路:分为两部分,第一部分,获取网页上数据并使用xlwt生成excel(当然你也可以选择保存到数据库),第二部分获取网页数据使用IO流将
- js 中判断某个元素是否存在于某个 js 数组中,相当于 php 语言中的 in_array 函数。Array.prototype.S=St
- 本文实例为大家分享了React实现表格选取的具体代码,供大家参考,具体内容如下在工作中,遇到一个需求,在表格中实现类似于Excel选中一片区
- 首先要知道,修改滚动条样式,利用伪元素-webkit-scrollbar。注意, ::-webkit-scrollbar仅仅支持WebKit
- 在用户注册中最常见的安全验证之一就是邮箱验证。根据行业的一般做法,进行邮箱验证是避免潜在的安全隐患一种非常重要的做法,现在就让我们来讨论一下
- 1. 英雄的简单动画实现需求:在游戏初始化定义一个pygame.Rect的变量记录英雄的初始位置在游戏循环中每次让英雄的y-1--向上移动(
- 特殊情况有 * ^ : | . \一、单个符号作为分隔符String address="上海\上海市|闵行区\吴中路";
- 分别为:1.倒计定时器:timename=setTimeout("function();",delaytime);2.循
- 从最简单的Web浏览器的登录界面开始,登录界面如下:进行Web页面自动化测试,对页面上的元素进行定位和操作是核心。而操作又是以定位为前提的,
- 一、简介urllib 库,它是 Python 内置的 HTTP 请求库,不需要额外安装即可使用,它包含四个模块:`request` 请求模块
- 个人想到的解决方法有两种,一种是 .replace(' old ',' new ')
- asp判断网址格式是否合法代码 具体实现办法见下列代码:<% function checki
- 这样就将你所有微信好友的信息都返回了,我们并不需要这么多的信息,我们选取一些信息存储到 csv 文件中注意:返回的信息是一个 list,其中