gecko-dev/js/rhino/docs/ScriptingJava.html

495 строки
16 KiB
HTML
Исходник Ответственный История

<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
<title>Scripting Java</title>
</head>
<body>
<h1>Scripting Java</h1>
<p>This paper shows how to use Rhino to reach beyond JavaScript into
Java <a href="#ref1">[1]</a>. Scripting Java has many uses. It allows
us to write powerful scripts quickly by making use of the many Java
libraries available. We can test Java classes by writing scripts. We
can also aid our Java development by using scripting for <em>exploratory
programming</em>. Exploratory programming is the process of
learning about what a library or API can do by writing quick programs
that use it. As we will see, scripting makes this process easier.</p>
<p>Note that the ECMA standard doesn't cover communication with
Java (or with any external object system for that matter). All the
functionality covered in this chapter should thus be considered an
extension.</p>
<h2>Accessing Java packages and classes</h2>
<p>Every piece of Java code is part of a class. Every Java
class is part of a package. In JavaScript, however, scripts exist outside of
any package hierarchy. How then, do we access classes in Java packages?</p>
<p>Rhino defines a top-level variable named <code>Packages</code>. The
properties of the <code>Packages</code>
variable are all the top-level Java packages, such as <code>java</code> and
<code>com</code>. For example,
we can access the value of the <code>java</code>package:</p>
<pre>
js&gt; Packages.java
[JavaPackage java]
</pre>
<p>As a handy shortcut, Rhino defines a top-level variable <code>java</code>
that is equivalent to <code>Packages.java</code>.
So the previous example could be even shorter:</p>
<pre>
js&gt; java
[JavaPackage java]
</pre>
<p>We can access Java classes simply by stepping down the
package hierarchy:</p>
<pre>
js&gt; java.io.File
[JavaClass java.io.File]
</pre>
<p>If your scripts access a lot of different Java classes it
can get awkward to use the full package name of the class every time. Rhino
provides a top-level function <code>importPackage</code> that serves the same
purpose as Java's <code>import</code> declaration. For example, we could
import all of the classes in the <code>java.io</code>package and
access class <code>java.io.File</code>
using just the name <code>File</code>:</p>
<pre>
js&gt; importPackage(java.io)
js&gt; File
[JavaClass java.io.File]
</pre>
<p>Here <code>importPackage(java.io)</code> makes all the classes in
the <code>java.io</code> package<67>(such as <code>File</code>)
available at the top level. It's equivalent in effect to the Java
declaration <code>import java.io.*;</code>.</p>
<p>It's important to note that Java imports <code>java.lang.*</code>
implicitly, while Rhino does not. The reason is that JavaScript has
its own top-level objects <code>Boolean</code>, <code>Math</code>,
<code>Number</code>, <code>Object</code>, and <code>String</code> that
are different from the classes by those names defined in the
<code>java.lang</code> package. Because of this conflict, it's a good
idea not to use <code>importPackage</code> on the
<code>java.lang</code> package.</p>
<p>One thing to be careful of is Rhino's handling of errors in
specifying Java package or class names. If <code>java.MyClass</code>
is accessed, Rhino attempts to load a class named
<code>java.MyClass</code>. If that load fails, it assumes that
<code>java.MyClass</code> is a package name, and no error is
reported:</p>
<pre>
js&gt; java.MyClass
[JavaPackage java.MyClass]
</pre>
<p>Only if you attempt to use this object as a class will an
error be reported.</p>
<h2>Working with Java objects</h2>
<p>Now that we can access Java classes, the next logical step is to
create an object. This works just as in Java, with the use of the
<code>new</code> operator:</p>
<pre>
js&gt; new java.util.Date()
Thu Jan 24 16:18:17 EST 2002
</pre>
<p>If we store the new object in a JavaScript variable, we can then
call methods on it:</p>
<pre>
js&gt; f = new java.io.File("test.txt")
test.txt
js&gt; f.exists()
true
js&gt; f.getName()
test.txt
</pre>
<p>Static methods and fields can be accessed from the class object
itself:</p>
<pre>
js&gt; java.lang.Math.PI
3.141592653589793
js&gt; java.lang.Math.cos(0)
1
</pre>
<p>In JavaScript, unlike Java, the method by itself is an
object and can be evaluated as well as being called. If we just view the method
object by itself we can see the various overloaded forms of the method:</p>
<pre>
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>This output shows that the <code>File</code> class defines three
overloaded methods <code>listFiles</code>: one that takes no
arguments, another with a <code>FilenameFilter</code> argument, and a
third with a <code>FileFilter</code> argument. All the methods return
an array of <code>File</code> objects. Being able to view the
parameters and return type of Java methods is particularly useful in
exploratory programming where we might be investigating a method and
are unsure of the parameter or return types. </p>
<p>Another useful feature for exploratory programming is the ability
to see all the methods and fields defined for an object. Using the
JavaScript <code>for..in</code> construct, we can print out all these
values:</p>
<pre>
js&gt; for (i in f) { print(i) }
exists
parentFile
mkdir
toString
wait
<em>[44 others]</em>
</pre>
<p>Note that not only the methods of the <code>File</code> class are
listed, but also the methods inherited from the base class
<code>java.lang.Object</code> (like <code>wait</code>). This makes it
easier to work with objects in deeply nested inheritance hierarchies
since you can see all the methods that are available for that
object.</p>
<p>Rhino provides another convenience by allowing properties of
JavaBeans to be accessed directly by their property names. A JavaBean
property <code>foo</code> is defined by the methods
<code>getFoo</code> and <code>setFoo</code>. Additionally, a boolean
property of the same name can be defined by an <code>isFoo</code>
method <a href="#ref2">[2]</a>. For example, the following code
actually calls the <code>File</code> object's <code>getName</code> and
<code>isDirectory</code> methods.</p>
<pre>
js&gt; f.name
test.txt
js&gt; f.directory
false
</pre>
<h2>Calling overloaded methods</h2>
<p>The process of choosing a method to call based upon the types of
the arguments is called <em>overload resolution</em>. In Java,
overload resolution is performed at compile time, while in Rhino it
occurs at runtime. This difference is inevitable given JavaScript's
use of dynamic typing as was discussed in Chapter 2: since the type of
a variable is not known until runtime, only then can overload
resolution occur.</p>
<p>As an example, consider the following Java class that defines a
number of overloaded methods and calls them.</p>
<pre>
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>When we compile and execute the program, it produces the output</p>
<pre>
f(Object)
f(Object)
f(Object)
</pre>
<p>However, if we write a similar script</p>
<pre>
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>and execute it, we get the output</p>
<pre>
f(int)
f(String)
f(Object)
</pre>
<p>Because Rhino selects an overloaded method at runtime, it calls the
more specific type that matches the argument. Meanwhile Java selects
the overloaded method purely on the type of the argument at compile
time. </p>
<p>Although this has the benefit of selecting a method that may be a
better match for each call, it does have an impact on performance
since more work is done at each call. In practice this performance
cost hasn't been noticeable in real applications.</p>
<p>Because overload resolution occurs at runtime, it can fail at
runtime. For example, if we call <code>Overload</code>'s method
<code>g</code> with two integers we get an error because neither form
of the method is closer to the argument types than the other:</p>
<pre>
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 more precise definition of overloading semantics can be
found at <a
href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html</a>.</p>
<h2>Implementing Java interfaces</h2>
<p>Now that we can access Java classes, create Java objects, and
access fields, methods, and properties of those objects, we have a
great deal of power at our fingertips. However, there are a few
instances where that is not enough: many APIs in Java work by
providing interfaces that clients must implement. One example of this
is the <code>Thread</code> class: its constructor takes a
<code>Runnable</code> that contains a single method <code>run</code>
that will be called when the new thread is started. </p>
<p>To address this need, Rhino provides the ability to create new Java
objects that implement interfaces. First we must define a JavaScript
object with function properties whose names match the methods required
by the Java interface. To implement a <code>Runnable</code>, we need
only define a single method <code>run</code> with no parameters. If
you remember from Chapter 3, it is possible to define a JavaScript
object with the <code>{propertyName: value}</code> notation. We can
use that syntax here in combination with a function expression to
define a JavaScript object with a <code>run</code> method:</p>
<pre>
js&gt; obj = { run: function () { print("\nrunning"); } }
[object Object]
js&gt; obj.run()
running
</pre>
<p>Now we can create an object implementing the <code>Runnable</code> interface
by constructing a <code>Runnable</code>:</p>
<pre>
js&gt; r = new java.lang.Runnable(obj);
[object JavaObject]
</pre>
<p>In Java it is not possible to use the <code>new</code> operator on
an interface because there is no implementation available. Here Rhino
gets the implementation from the JavaScript object
<code>obj</code>. Now that we have an object implementing
<code>Runnable</code>, we can create a <code>Thread</code> and run
it. The function we defined for <code>run </code>will be called on a
new thread.</p>
<pre>
js&gt; t = new java.lang.Thread(r)
Thread[Thread-2,5,main]
js&gt; t.start()
js&gt;
running
</pre>
<p>The final <code>js</code> prompt and the output from the new thread
may appear in either order, depending on thread scheduling.</p>
<p>Behind the scenes, Rhino generates the bytecode for a new Java
class that implements <code>Runnable</code> and forwards all calls to
its <code>run</code> method over to an associated JavaScript
object. The object that implements this class is called a <em>Java
adapter</em>. Because the forwarding to JavaScript occurs at runtime,
it is possible to delay defining the methods implementing an interface
until they are called. While omitting a required method is bad
practice for programming in the large, it's useful for small scripts
and for exploratory programming.</p>
<h2>The <code>JavaAdapter</code> constructor</h2>
<p>In the previous section we created Java adapters using the
<code>new</code> operator with Java interfaces. This approach has its
limitations: it's not possible to implement multiple interfaces, nor
can we extend non-abstract classes. For these reasons there is a
<code>JavaAdapter</code> constructor. </p>
<p>The syntax of the <code>JavaAdapter</code> constructor is</p>
<pre>
new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)
</pre>
<p>Here <code>javaIntfOrClass</code> is an interface to implement or a
class to extend and <code>javaIntf</code> are aditional interfaces to
implement. The <code>javascriptObject</code> is the JavaScript object
containing the methods that will be called from the Java adapter. </p>
<p>In practice there's little need to call the
<code>JavaAdapter</code> constructor directly. Most of the time the
previous syntaxes using the <code>new</code> operator will be
sufficient.</p>
<h2>JavaScript functions as Java interfaces</h2>
<p>Often we need to implement an interface with only one method, like in the
previous <code>Runnable</code> example or when providing various event
listener implementations. To facilitate this Rhino allows to pass
JavaScript function when such interface is expected. The
function is called as the implementation of interface method.</p>
<p>Here is the simplified <code>Runnable</code> example:</p>
<pre>
js&gt; t = java.lang.Thread(function () { print("\nrunning"); });
Thread[Thread-0,5,main]
js&gt; t.start()
js&gt;
running
</pre>
Rhino also allows to use JavaScript function as implementation of Java
interface with more then method if all the methods has the same
signature. When calling the function, Rhino passes method's name as
the additional argument. Function can
use it to distinguish on behalf of which method it was called:
<pre>
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>
<h2>Creating Java arrays</h2>
<p>Rhino provides no special syntax for creating Java arrays. You
must use the <code>java.lang.reflect.Array</code> class for this
purpose. To create an array of five Java strings you would make the
following call:</p>
<pre>
js&gt; a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
[Ljava.lang.String;@7ffe01
</pre>
<p>To create an array of primitive types, we must use the special TYPE
field defined in the associated object class in the
<code>java.lang</code> package. For example, to create an array of
bytes, we must use the special field
<code>java.lang.Byte.TYPE</code>:</p>
<pre>
js&gt; a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
[C@7a84e4
</pre>
<p>The resulting value can then be used anywhere a Java array
of that type is allowed.</p>
<pre>
js&gt; a[0] = 104
104
js&gt; a[1] = 105
105
js&gt; new java.lang.String(a)
hi
</pre>
<h2>Java strings and JavaScript strings</h2>
<p>It's important to keep in mind that Java strings and JavaScript
strings are <strong>not</strong> the same. Java strings are instances
of the type <code>java.lang.String</code> and have all the methods
defined by that class. JavaScript strings have methods defined by
<code>String.prototype</code>. The most common stumbling block is
<code>length</code>, which is a method of Java strings and a dynamic
property of JavaScript strings:</p>
<pre>
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 provides some help in reducing the differences between the
two types. First, you can pass a JavaScript string to a Java method
that requires a Java string and Rhino will perform the conversion. We
actually saw this feature in action on the call to the
<code>java.lang.String</code> constructor in the preceding
example.</p>
<p>Rhino also makes the JavaScript methods available to Java strings
if the java.lang.String class doesn't already define them. For
example:</p>
<pre>
js&gt; javaString.match(/a.*/)
ava
</pre>
<hr align=left size=1 width="33%">
<p><a name="ref1">[1]</a>
The ability to call Java from JavaScript was first implemented as part
of a Netscape browser technology called
<em>LiveConnect</em>. However, since that technology also
encompassed communication with browser plugins, and since the way of
calling JavaScript from Java in Rhino is entirely different, that term
won't be used in this paper.</p>
<p><a name="ref2">[2]</a>
For more information on JavaBeans, see <em>Developing Java Beans</em>
by Robert Englander.</p>
</body>
</html>