DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 在JavaScript中調用Java類和接口的方法
在JavaScript中調用Java類和接口的方法
編輯:關於JavaScript     

前言

本文中所有的代碼使用 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.Arc2DFloat 內部類:

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 包默認不需要導入,因為這會和 ObjectBooleanMath 等其他 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 字符串對象。

總結
以上就是這篇文章的全部內容,希望對大家的學習和工作能有一定的幫助,如果有疑問大家可以留言交流。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved