aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/mozilla/projects/rhino/scripting_java/index.html
blob: 013ad3aa8967b7744d7bfccafe08c678714493dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
---
title: Scripting Java
slug: Mozilla/Projects/Rhino/Scripting_Java
translation_of: Mozilla/Projects/Rhino/Scripting_Java
---
<p>这篇文章描述了如何在rhino中使用java。使用脚本调用Java有很多用途,它使得我们可以利用Java中现有的库,来帮助我们构建强大的脚本。我们可以通过编写脚本,来对Java程序进行测试。可以通过脚本来进行探索式编程,辅助Java的开发,所谓探索式编程,就是通过快速地编程调用库或API来探索这些库或API可以做什么,显而易见,脚本语言很适合探索式编程。</p>

<p>这里注意,ECMA标准并没有包含和Java(或者其他任何对象系统)交互的标准。本文所描述的所有内容,应该被认为是一个扩展。</p>



<h3 id="访问_Java_Packages_和_Classes">访问 Java Packages 和 Classes</h3>

<p>Java的每段代码都是类的一部分,每一个JAVA类都是包的一部分。在Javascript中,脚本不属于任何package。我们可以访问Java包中的类么?</p>

<p>Rhino定义了一个顶层的变量Packages。Packages的所有属性都是Java中顶层的包,比如java和com。比如我们可以访问java包:</p>

<pre class="code">js&gt; Packages.java
[JavaPackage java]</pre>

<p>还有一种更方便的方式,Rhino定义了一个顶层的变量java,等价于Packages.java。所以上面的例子可以更简介地写成:</p>

<pre class="code">js&gt; java
[JavaPackage java]
</pre>

<p>我们可以通过访问包的下层,来直接访问java类:</p>

<pre class="code">js&gt; java.io.File
[JavaClass java.io.File]
</pre>

<p>如果你的脚本需要访问很多的Java类,每次都附带完整的包名会使得编程很麻烦。Rhino提供了一个顶层的方法importPackage,它的功能和Java的import一样。比如,我们可以导入java.io包中的所有类,然后直接通过类名File来访问java.io.File:</p>

<pre class="code">js&gt; importPackage(java.io)
js&gt; File
[JavaClass java.io.File]
</pre>

<p>这里importPackage(java.io)使得java.io包中的所有类(例如File)可以在顶层被访问。这和Java中的java.io.*;等价。</p>

<p>要注意Java会暗中导入java.lang.*,但是Rhino不会。因为JavaScript的顶层对象Boolean、Math、Number、Object和String和java.lang包中同名的类并不相同。因为这种冲突,建议不要用importPackage来导入java.lang包。</p>

<p>有一点要注意的,就是Rhino对于指定包名或类名时是如何处理错误的。如果java.Myclass是可访问的,Rhino会试图加载名为java.MyClass的类,如果加载失败,它会假设java.MyClass是一个包名,不会报错:</p>

<pre class="code">js&gt; java.MyClass
[JavaPackage java.MyClass]
</pre>

<p>只有在你试图将这个对象当作类使用时,才会报错。</p>

<h4 id="额外的包和类">额外的包和类</h4>

<p>额外的包和类也可以在Rhino中使用。确认你的.jar或.class文件在你的classpath里,你就可以在你的JavaScript应用中导入它们。这些包基本不会在java包中,所以你在使用时,需要在包前加上前缀"Packages"。 比如你想导入 <code>org.mozilla.javascript</code> 包,你应该像下面这样去使用importPackage():</p>

<pre class="code">$ java org.mozilla.javascript.tools.shell.Main
js&gt; importPackage(Packages.org.mozilla.javascript);
js&gt; Context.currentContext;
org.mozilla.javascript.Context@bb6ab6
</pre>

<p>偶尔,我们也会见到在一些例子中使用包的完整名称,而没有使用importPackage()。这也是可以的,只是会让你多打一些字。如果使用完整的名称,上面的例子就会变成下面这样:</p>

<pre class="code">$ java org.mozilla.javascript.tools.shell.Main
js&gt; jsPackage = Packages.org.mozilla.javascript;
[JavaPackage org.mozilla.javascript]
js&gt; jsPackage.Context.currentContext;
org.mozilla.javascript.Context@bb6ab6
</pre>

<p>同样,你可以通过importClass()来导入一个类,上面的例子也可以像这样写:</p>

<pre>$ java org.mozilla.javascript.tools.shell.Main
js&gt; importClass(Packages.org.mozilla.javascript.Context);
js&gt;  Context.currentContext;
org.mozilla.javascript.Context@bb6ab6</pre>

<h3 id="和Java一起工作">和Java一起工作</h3>

<p>现在我们可以访问Java类,下一步就是要创建一个对象。方法就和在Java中一样, 用new来创建对象:</p>

<pre class="code">js&gt; new java.util.Date()
Thu Jan 24 16:18:17 EST 2002
</pre>

<p>如果我们将创建的对象存放在JavaScript变量中,我们可以调用它的方法:</p>

<pre class="code">js&gt; f = new java.io.File("test.txt")
test.txt
js&gt; f.exists()
true
js&gt; f.getName()
test.txt
</pre>

<p>静态方法和属性可以直接通过类对象来访问:</p>

<pre class="code">js&gt; java.lang.Math.PI
3.141592653589793
js&gt; java.lang.Math.cos(0)
1
</pre>

<p>不像Java,在JavaScript里,方法就是一个对象。它可以被评估,也可以被调用。如果我们去查看这个方法,我们可以看到这个方法所有重载的形式:</p>

<pre class="code">js&gt; f.listFiles
function listFiles() {/*
java.io.File[] listFiles()
java.io.File[] listFiles(java.io.FilenameFilter)
java.io.File[] listFiles(java.io.FileFilter)
*/}
</pre>

<p>输出告诉我们,File类有listFiles方法的三种重载:一种不包含参数的,另一种包含一个FilenameFilter类型的参数,第三个包含一个FileFilter类型的参数。所有的方法都返回一个File对象数组。可以观察到Java方法的参数和返回类型在探索式编程中是非常有用的,尤其是在对一个方法的参数和返回对象不确定的时候。</p>

<p>另一个有助于探索式编程的特性,是可以看到对象中定义的所有方法和属性。用JavaScript的<code>for..in</code> , 我们可以打印这些值:</p>

<pre class="code">js&gt; for (i in f) { print(i) }
exists
parentFile
mkdir
toString
wait
<em>[44 others]</em>
</pre>

<p>注意这里不仅列出了File类中的所有方法,也列出了从基类java.lang.Object中继承的方法,例如wait。这使得我们可以更好地处理那些有复杂继承关系的对象,因为我们可以看到对象中所有可用的方法。</p>

<p>Rhino可以通过属性名来方便地访问JavaBean的属性。一个JavaBean的属性foo被方法getFoo和setFoo定义,另外,一个也叫foo的boolean类型的属性,可以被isFoo来定义。比如, 下面的代码实际上调用了File对象的getName和isDirectory方法。</p>

<pre class="code">js&gt; f.name
test.txt
js&gt; f.directory
false
</pre>

<h3 id="调用重载方法"><font><font>调用重载方法</font></font></h3>

<p> <span id="noHighlight_0.8335956993210636">根据参数类型选择调用方法的过程称为重载决议。在 Java 中, 重载决议在编译时执行, 而在rhino中则在运行时发生。这种差异是不可避免的, 因为 JavaScript 使用动态类型, 在2章中: 由于变量的类型直到运行时才知道, 才会发生重载决议。</span></p>

<div class="textArea" id="destText" style="direction: ltr;">
<div><span id="noHighlight_0.37802431709856343">例如, 请查看下面的 Java 类, 它定义了许多重载方法并调用它们。</span> </div>
</div>



<pre class="code">public class Overload {

    public String f(Object o) { return "f(Object)"; }
    public String f(String s) { return "f(String)"; }
    public String f(int i)    { return "f(int)"; }

    public String g(String s, int i) { return "g(String,int)"; }
    public String g(int i, String s) { return "g(int,String)"; }

    public static void main(String[] args) {
        Overload o = new Overload();
        Object[] a = new Object[] { new Integer(3), "hi", Overload.class };
        for (int i = 0; i != a.length; ++i)
            System.out.println(o.f(a[i]));
    }
}
</pre>

<p><span id="noHighlight_0.42609135329922554">当我们编译和执行程序, 它产生输出</span></p>

<pre class="code">f(Object)
f(Object)
f(Object)
</pre>

<p><span id="noHighlight_0.9335258512242988">但是, 如果我们编写一个类似的脚本</span></p>

<pre class="code">var o = new Packages.Overload();
var a = [ 3, "hi", Packages.Overload ];
for (var i = 0; i != a.length; ++i)
    print(o.f(a[i]));
</pre>

<p>并且运行它,将会输出</p>

<pre class="code">f(int)
f(String)
f(Object)
</pre>

<p>因为Rhino在运行时选择重载方法, <span id="noHighlight_0.45030814581159195">所以它会调用与该参数匹配的更具体的类型。</span> <span id="noHighlight_0.45030814581159195">同时, 在编译时, Java 只在参数的类型上选择重载方法。</span></p>

<p>尽管这有利于选择一种方法,这种方法可能是每个调用的更好匹配,但它确实对性能有影响,因为每次调用时都要做更多的工作。事实上,这种性能代价在实际应用中并不明显。</p>

<p>因为重载决议发生在运行时,它可能在运行时失败。例如,如果我们用两个整数调用重载方法g,我们就会得到一个错误,因为方法的两个形式都比另一个更接近参数类型:</p>

<pre class="code">js&gt; o.g(3,4)
js:"&lt;stdin&gt;", line 2: The choice of Java method Overload.g
matching JavaScript argument types (number,number) is ambiguous;
candidate methods are:
class java.lang.String g(java.lang.String,int)
class java.lang.String g(int,java.lang.String)
</pre>

<p><a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html</a> 提供了<font><font>一个更精确的重载语义定义</font><font></font></font></p>

<h3 id="实现Java接口">  实现Java接口</h3>



<p>现在我们可以访问Java类,创建Java对象,并访问这些对象的字段、方法和属性,我们就可以轻松掌握大量的功能。但是,在少数情况下是不够用的:Java中的很多API通过提供客户端必须实现的接口来工作。其中一个例子就是<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.498039);">Thread</span></font>类:其构造函数Runnable包含一个<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.498039);">run</span></font>方法,这个方法在新线程启动时被调用。</p>

<p>为了满足这种需求,Rhino提供了创建新的Java对象实现的接口的能力。首先,我们必须定义一个JavaScript对象,其中的函数属性的名称与Java接口所需的方法名称相匹配。要实现一个<code>Runnable</code> ,我们只需要定义一个不带参数的<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.498039);">run</span></font>单方法。如果你还记得第3章,可以用{ propertyName: value}符号定义一个JavaScript对象。我们可以在这里结合函数表达式使用这个语法来用一个 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.498039);">run</span></font>方法定义一个JavaScript对象:</p>

<pre class="code">js&gt; obj = { run: function () { print("\nrunning"); } }
[object Object]
js&gt; obj.run()

running
</pre>



<p>现在我们可以通过构建一个 <code>Runnable</code> 来实现 <code>Runnable</code> 接口的对象:</p>

<pre><code>js&gt; r = new java.lang.Runnable(obj);</code></pre>



<pre class="code">js&gt; r = new java.lang.Runnable(obj);
[object JavaObject]
</pre>

<p><font><font>在Java中,不可能</font></font><font><font>在接口上</font><font>使用</font></font><code>new</code><font><font>运算符,因为没有可用的实现。</font><font>Rhino从JavaScript对象中获取实现</font></font><code>obj</code><font><font></font><font>现在我们有一个对象实现</font></font><code>Runnable</code><font><font>,我们可以创建</font></font><code>Thread</code><font><font>并运行它。</font><font>我们定义的函数</font></font><code>run </code><font><font>将在新线程上调用。</font></font></p>

<pre class="code">js&gt; t = new java.lang.Thread(r)
Thread[Thread-2,5,main]
js&gt; t.start()
js&gt;

running
</pre>



<p><font><font>最终</font></font><code>js</code><font><font>提示和新线程的输出可能以任意顺序显示,具体取决于线程调度。</font></font></p>

<p>在后台,Rhino为一个新的Java类生成字节码,该类实现 <code>Runnable</code> 并转发对其 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.498039);">run</span></font>方法的所有调用,并转发给关联的JavaScript对象。实现此类的对象称为Java适配器。因为转发到JavaScript是在运行时发生的,所以可能会延迟定义实现接口的方法直到它们被调用。虽然省略必要的方法对大编程来说是一种糟糕的做法,但它对小脚本和探索性编程很有用。</p>



<h3 id="JavaAdapter构造函数"><font><font>JavaAdapter构造函数</font></font></h3>

<p>在前面的章节中,我们使用 <code>new</code> 运算符与Java接口创建Java适配器。这种方法有其局限性:不可能实现多个接口,也不能扩展非抽象类。因为这些<font><font>原因,有一个</font></font> <code>JavaAdapter</code> 构造函数。</p>

<p><code>JavaAdapter</code><font><font>构造函数</font><font>的语法</font><font>是:</font></font></p>

<pre class="code">new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)
</pre>

<p><code><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">这里</span></font>javaIntfOrClass</code>是一个实现的接口或一个扩展的类,并且<code>javaIntf</code>是实现接口的接口。而<code>javascriptObject</code> 则包含从Java适配器调用的方法的JavaScript对象。</p>

<p>在实践中,几乎不需要<code>JavaAdapter</code> 直接调用构造函数。大多数情况下,使用<code>new</code>运算符之前的语法就足够了。</p>

<h3 id="作为Java接口的JavaScript函数"><font><font>作为Java接口的JavaScript函数</font></font></h3>

<p>通常我们只需要使用一种方法实现一个接口,<font><font>就像前面的</font></font> <code>Runnable</code> <font><font>例子或者提供各种事件监听器实现一样。</font>为了方便这个,Rhino允许在这种接口传递JavaScript函数。</font><font>该函数被称为接口方法的实现。</font></p>

<p>这里是简化的 <code>Runnable</code> 实例:</p>

<pre class="code">js&gt; t = java.lang.Thread(function () { print("\nrunning"); });
Thread[Thread-0,5,main]
js&gt; t.start()
js&gt;
running
</pre>

<p><font>如果所有的方法都具有相同的签名,Rhino还允许使用JavaScript函数作为Java接口的实现方法。当调用函数时,Rhino将方法的名称作为附加参数传递。函数可以使用它</font>来代表被调用<font>的方法:</font></p>

<pre class="code">js&gt; var frame = new Packages.javax.swing.JFrame();
js&gt; frame.addWindowListener(function(event, methodName) {
	if (methodName == "windowClosing") {
            print("Calling System.exit()..."); java.lang.System.exit(0);
	}
    });
js&gt; frame.setSize(100, 100);
js&gt; frame.visible = true;
true
js&gt; Calling System.exit()...
</pre>

<h3 id="创建Java数组"><font><font>创建Java数组</font></font></h3>

<p>Rhino不提供创建Java数组的特殊语法。你必须使用这个 <code>java.lang.reflect.Array</code> 类来达到这个目的。要创建一个由五个Java字符串组成的数组,可以进行以下调用:</p>

<pre class="code">js&gt; a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
[Ljava.lang.String;@7ffe01
</pre>

<p>要创建一个基本类型数组,我们必须使用 <code>java.lang</code> 包中相关对象类中定义的特殊TYPE字段。例如,要创建一个字节数组,我们必须使用特殊字段 <code>java.lang.Byte.TYPE</code>:</p>

<pre class="code">js&gt; a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
[C@7a84e4
</pre>

<p>而且结果值是允许被使用在该类型的Java数组的任何地方。</p>

<pre class="code">js&gt; a[0] = 104
104
js&gt; a[1] = 105
105
js&gt; new java.lang.String(a)
hi
</pre>

<h3 id="Java字符串和JavaScript字符串"><font><font>Java字符串和JavaScript字符串</font></font></h3>

<p><font><font>请记住,Java字符串和JavaScript字符串是</font></font><strong><font><font></font></font></strong><font><font>一样的。</font><font>Java字符串类型的实例,</font></font><code>java.lang.String</code> ,<font><font>并具有由该类定义的所有方法。</font><font>JavaScript字符串具有由...定义的方法,</font></font><code>String.prototype</code>. <font><font>最常见的绊脚石是</font></font> <code>length</code>, <font><font><font face="consolas, Liberation Mono, courier, monospace">这是Java</font>字符串方法和JavaScript字符串的动态属性:</font></font></p>

<pre class="code">js&gt; javaString = new java.lang.String("Java")
Java
js&gt; jsString = "JavaScript"
JavaScript
js&gt; javaString.length()
4
js&gt; jsString.length
10
</pre>

<p>Rhino <font>在减少这两种类型之间的差异方面提供了一些帮助。</font><font>首先,您可以将JavaScript字符串传递给需要Java字符串的Java方法,Rhino将执行转换。</font><font>实际上,我们在前面</font><code>java.lang.String</code> <font>例子中</font><font></font><font>构造函数</font><font>调用中看到了这个特性</font><font></font></p>

<p><font>如果</font><code>java.lang.String</code> <font>类尚未定义它们,Rhino还会使JavaScript方法可用于Java字符串。例如:</font></p>

<pre class="code">js&gt; javaString.match(/a.*/)
ava
</pre>

<h3 id="JavaImporter_构造函数">JavaImporter 构造函数</h3>

<p><code>JavaImporter</code>是一个新的全局构造函数,它允许在脚本化Java时省略显式的包名称:</p>

<pre>var SwingGui = JavaImporter(Packages.javax.swing,
                            Packages.javax.swing.event,
                            Packages.javax.swing.border,
                            java.awt.event,
                            java.awt.Point,
                            java.awt.Rectangle,
                            java.awt.Dimension);
...

with (SwingGui) {
    var mybutton = new JButton(test);
    var mypoint = new Point(10, 10);
    var myframe = new JFrame();
...
}
</pre>

<p>以前,这样的功能仅适用于将 <code>org.mozilla.javascript.ImporterTopLevel</code> 用作顶级作用域的嵌入。这个类提供额外的 <code>importPackage()</code> 和<code>importClass()</code> 全局函数的脚本,但其广泛的使用有污染Java类名的全局命名空间的趋势,还有防止垃圾收集加载类。</p>

<p>详情请参阅 <a href="http://bugzilla.mozilla.org/show_bug.cgi?id=245882">Bugzilla 245882</a>.</p>

<h3 id="Java_异常">Java 异常</h3>



<p><font><font>JavaScript代码使用</font></font> <code>try ... catch</code> 语句<font><font>可以捕获Java方法抛出的异常</font><font></font><font>Rhino将Java异常封装到具有以下属性的错误对象中:</font></font></p>

<ul>
 <li><code>javaException</code><font><font>:Java方法抛出的原始异常</font></font></li>
 <li><code>rhinoException</code><font><font>:由Rhino运行时包装的异常</font></font></li>
</ul>

<p><code>instanceof</code><font><font>运算符可用于查询异常的类型:</font></font></p>



<pre>try {
    java.lang.Class.forName("NonExistingClass");
} catch (e) {
    if (e.javaException instanceof java.lang.ClassNotFoundException) {
       print("Class not found");
    }
}
</pre>

<p>Rhino 还支持对 <code>try... catch</code> 语句的扩展,允许定义条件捕获异常:</p>

<pre>function classForName(name) {
    try {
        return java.lang.Class.forName(name);
    } catch (e if e.javaException instanceof java.lang.ClassNotFoundException) {
        print("Class " + name + " not found");
    } catch (e if e.javaException instanceof java.lang.NullPointerException) {
        print("Class name is null");
    }
}

classForName("NonExistingClass");
classForName(null);
</pre>