Calling Java from JRuby

From JRubyWiki

Jump to: navigation, search

Contents

Basics: 'include Java' and Accessing Java Classes and Methods

As of JRuby 1.0, a special 'include Java' directive in your file will give you access to the bundled Java libraries. However, this will not give you access to non-bundled libraries; a bit more is needed for that, which will be discussed later.

All of the following examples can be tested using jirb_swing the Swing-based IRB console that comes with JRuby.

The following code shows this. unless I've messed it up while adding wiki tags, it should pop up a small window showing "Hello" on your screen.

   # Valid as of JRuby 1.0
   
   # This is the 'magical Java include line'.
   include Java
   
   # With the 'include Java' above, we can now refer to things that are part of the
   # standard Java platform via their full paths.
   frame = javax.swing.JFrame.new("Window") # Creating a Java JFrame.
   label = javax.swing.JLabel.new("Hello")
   
   # We can transparently call Java methods on Java objects, just as if they were defined in Ruby.
   frame.getContentPane.add(label)  # Invoking the Java method 'getContentPane'.
   frame.setDefaultCloseOperation(javax.swing.JFrame::EXIT_ON_CLOSE)
   frame.pack
   frame.setVisible(true)

Note: if you are testing the example above in the Swing IRB console jirb_swing, change the default close operation to DISPOSE_ON_CLOSE, or HIDE_ON_CLOSE unless you want jirb_swing to also close when you close the second window.

Here's another example (showing results from testing these statements in the jirb console).

Let's say you wanted to get a list of network interfaces. Here's the Java api docs for java.net.NetworkInterface.

Here's how to access the methods from this Java Class from from JRuby:

 irb(main):013:0> ni = java.net.NetworkInterface.networkInterfaces
 => #<#<Class:01x7e666f>:0x855a27 @java_object=java.net.NetworkInterface$1@821453>

ni is Ruby variable holding a Java Enumeration of NetworkInterfaces. You can see the Class ancestry for ni like this:

 irb(main):029:0> ni.class.ancestors
 => [#<Class:01x7e666f>, Java::JavaUtil::Enumeration, Enumerable, Java::JavaLang::Object, ConcreteJavaProxy, 
 JavaProxy, JavaProxyMethods, Object, Java, Kernel]

Enumeration elements can't be accessed using Array#[] syntax but they do appear as Arrays for many other purposes. You can find out both the Java and Ruby methods for an Enumeration of NetworkInterfaces like this:

 irb(main):032:0> java.net.NetworkInterface.networkInterfaces.methods
 => ["__jsend!", "has_more_elements", "hasMoreElements", "next_element", "nextElement", "each", "reject", 
 "member?", "grep", "include?", "min", "sort", "any?", "partition", "each_with_index", "collect", "find_all", "to_a", 
 "inject", "detect", "map", "zip", "sort_by", "max", "entries", "all?", "find", "select", "hashCode", "notifyAll", 
 "getClass", "to_string", "toString", "get_class", "notify_all", "equals", "hash_code", "wait", "notify", "__jcreate!", 
 "java_class", "eql?", "synchronized", "to_java_object", "equal?", "java_object", "java_object=", "to_s", "==", 
 "hash", "java_kind_of?", "handle_different_imports", "include_class", "display", "object_id", "frozen?", "org", 
 "__id__", "clone", "__send__", "id", "__jtrap", "instance_eval", "singleton_methods", "is_a?", "extend", 
 "instance_variable_set", "freeze", "remove_instance_variable", "=~", "private_methods", "methods", 
 "instance_variable_get", "nil?", "send", "untaint", "com", "type", "class", "===", "instance_of?", "protected_methods", 
 "tainted?", "kind_of?", "javax", "inspect", "java", "instance_exec", "taint", "dup", "public_methods", 
 "instance_variable_defined?", "respond_to?", "method", "instance_variables"]

Because JRuby supports the #each method on Java Enumerations you can do this:

 irb(main):011:0> java.net.NetworkInterface.networkInterfaces.each {|i| puts i; puts }
 name:en1 (en1) index: 5 addresses:
 /63.138.152.170;
 /fe80:0:0:0:21b:63ff:febf:4a9d%5;
 
 name:en0 (en0) index: 4 addresses:
 /63.138.152.125;
 /fe80:0:0:0:21b:63ff:fe1e:b2da%4;
 
 name:lo0 (lo0) index: 1 addresses:
 /127.0.0.1;
 /fe80:0:0:0:0:0:0:1%1;
 /0:0:0:0:0:0:0:1%0;

Require a jar file to make resources in the jar discoverable within JRuby

In order to use resources within a jar file from JRuby the jar file must either be on the classpath or you can make it available with the require method:

 require 'path/to/mycode.jar'

Will make the resources in mycode.jar discoverable by commands like import and include_package.

Import a Java Class to use it within JRuby

The import statement can be used to import a Java Class.

Example: import and use the java.lang.System class.

 include Java
 import java.lang.System
 version = System.getProperties["java.runtime.version"]

Use include_package within a Ruby Module to import a Java Package

Use include_package "<package_name>" in a Ruby Module to support namespaced access to the Java classes in the package.

Example: create a Ruby Module called JavaLang that includes the classes in the Java package java.lang.

 module JavaLang
   include_package "java.lang"
 end

Prefix the Class name with JavaLang:: to access the included Classes:

 version = JavaLang::System.getProperties["java.runtime.version"]
 => "1.5.0_13-b05-237"
 processors = JavaLang::Runtime.getRuntime.availableProcessors
 => 2

The Java classes in the package will become available in this class/module, unless a constant with the same name as a Java class is already defined.

The use of the Module name to scope access to the imported Java class is also helpful in cases where the Java class has the same name as an existing Ruby class.

For example if you need to create an instance of a java.io.File object this code will work:

 import java.io.File
 newfile = File.new("file.txt")
 => #<Java::JavaIo::File:0xdc6f00 @java_object=file.txt>

However you've now redefined the Ruby constant File and can no longer access the Ruby File class. Executing this:

 File.open('README', 'r') {|f| puts f.readline }

Will produce this error:

 NoMethodError: private method `open' called for Java::JavaIo::File:Class

If instead you create a module called JavaIO and include the package in the module definition:

 module JavaIO     
   include_package "java.io"
 end

You can now create a new instance of the Java class File without shadowing the Ruby version of the File class.:

 newfile = JavaIO::File.new("file.txt")
 => #<Java::JavaIo::File:0x15619c @java_object=file.txt>

The Ruby File class is still accessible:

 File.open('README', 'r') {|f| puts f.readline }
 JRuby -  A Java implementation of the Ruby language
 => nil

Mapping of Java class in JRuby

Imported classes are mapped into Ruby name space as follows:

  Java: org.foo.department.Widget
  Ruby: Java::OrgFooDepartment::Widget

Conversion of Types

Ruby Array to Java String

 ["a","b","c"].to_java(:string)
 => #<#<Class:01x51c603>:0x9ef43f @java_object=[Ljava.lang.String;@b5950a>

Ruby String to Java Bytes and back again

 bytes = 'a string'.to_java_bytes
 => #<#<Class:01x9fcffd>:0x40e825 @java_object=[B@3d476c>
 string = String.from_java_bytes bytes
 => "a string"

Referencing a java.lang.Class object

If you call a Java class from JRuby and need to pass a Java class as an argument and use this form:

 DoSomethingWithJavaClass(MyJavaClass.class)

You'll get this error:

 TypeError: expected [java.lang.Class]; got: [org.jruby.RubyClass]; error: argument type mismatch

instead use the method: java_class

 DoSomethingWithJavaClass(MyJavaClass.java_class)

Integrating JRuby and Java Classes and Interfaces

Implementing Java Interfaces in JRuby

JRuby classes can now implement more than one Java interface.

 class SomeJRubyObject
   include java.lang.Runnable
   include java.lang.Comparable
 end

Java classes can't inherit from a JRuby class

Hopefully this feature will be added in the planned re-write of the Java integration layer for JRuby 1.2.

Material from Before JRuby 1.0

One powerful feature of JRuby is its ability to invoke the classes of the Java Platform. The following example uses JRuby 0.9.2 to create a Java JFrame with a JLabel.

Note that several package paths are magic: java, javax, org, com. Many package paths will need to be prefixed with Java::

require 'java'

JFrame = javax.swing.JFrame
JLabel = javax.swing.JLabel

frame = JFrame.new()
frame.getContentPane().add(JLabel.new("This is an example."))
frame.pack()
frame.setVisible(true)

MyClass = Java::my.package.MyClass
myClass = MyClass.new()
myClass.doit()

Note: older versions of JRuby require you to use the following code as the import preamble:

require 'java'

include_class "javax.swing.JFrame"
include_class "javax.swing.JLabel"

JRuby also allows you to call Java code using the more Ruby-like underscore_method_naming (the translation rule is that the Java method name is splitted by capital letters, lowercased and prepended with _) and to refer to JavaBean properties as attributes:

frame.content_pane.add(label)
frame.visible = true


JRuby also lets you call Java libraries *not* included in the standard set of class libraries associated with the Java Platform. By copying jars to $JRUBY_HOME/lib or by modifying the CLASSPATH environment variable, you can access third-party Java libraries from JRuby scripts or the jirb, an interactive JRuby shell. It is also possible to add a jar to the classpath by requiring the jar (if it exists in the JRuby library load path):

# TODO Make this an example of a real library.
require 'whatever.jar'
com.whatever.Whatever.doSomething


Gotchas

If you have a class name ambiguity between Java and Ruby, the class name will reference the Ruby construct within the Ruby code. For instance, if you import java.lang.Thread, and then write JThread < Thread, JThread will in fact inherit the Ruby Thread object, not the Java Thread! The solution is to rather use the full Java Class name such as: JThread < java.lang.Thread

Personal tools