Developer

JRuby: An Introduction

JRuby is a 100 percent Java implementation of the Ruby interpreter, and while it does not have all the features of Ruby it does have most of built-in classes of the language.

From the same "good + good == doublegood" mindset that brought us peanut butter and chocolate (together at last, as God intended) — JRuby binds Ruby and Java together in an east meets west, loose type meets strictly typed world that we shall peer into.

JRuby is a 100 percent Java implementation of the Ruby interpreter, and while it does not have all the features of Ruby it does have most of built-in classes of the language.

Needless to say that in order to make best use of JRuby, you should have an understanding of Java and Ruby. If you are a Java programmer, check out our introductory articles to Ruby as this will be the limit of what we shall explore in this article.

Getting JRuby

First thing to do is head over to http://dist.codehaus.org/jruby/ and grab the latest jruby-bin archive, it contains everything we need. Simply extract it to a place of your choosing and add your-jruby-dir/bin to your path variable.

You should be able to execute the following now:

jruby -v
ruby 1.8.5 (2007-08-23 rev 4201) [ppc-jruby1.0.1]

On with the code

To begin with we will be using the interactive console that is invoked using the command:

jirb
irb(main):001:0> 

JRuby being a Ruby interpreter means that we can run any of the Ruby code we've seen previously.

irb(main):001:0> puts "Hello World"
Hello World
=> nil
irb(main):002:0> [1, 2, 3, 4, 5].each { |n| puts n*n }
1
4
9
16
25
=> [1, 2, 3, 4, 5]

No surprises there, but the real reason we are using this over the native Ruby interpreter is to make use of Java, so let's bring the Java framework in:

irb(main):003:0> require 'java'
=> true

However attempting to use some standard Java code will fail:

irb(main):004:0> System.out.println("Hello world")
NameError: uninitialized constant System
        from (irb):4:in `binding'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:150:in `eval_input'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:70:in `signal_status'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:147:in `eval_input'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:70:in `each_top_level_statement'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:146:in `loop'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:146:in `catch'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:146:in `eval_input'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:70:in `start'
        from :1:in `catch'
        from /Users/chris.duckett/tmp/jruby/jruby-1.0.1/lib/ruby/1.8/irb.rb:69:in `start'
        from :1

We are going to need to dig far back into the things that are taken for granted within Java to get the standard Hello World statement going.

A visit to an API page that you would not have seen for a long time reveals the solution. System is actually a member of the java.lang package and out is a field on System with println being a method on out.

That means that in order to call it properly we need to do this:

irb(main):005:0> java.lang.System::out.println("Hello World")
Hello World
=> nil

just to keep it interesting, the following also works:

irb(main):005:0> java.lang.System.out.println("Hello World")
Hello World
=> nil

and just to prove that we can have Ruby and Java on the same line

irb(main):006:0>  puts java.lang.System::err.println("Hello World")
Hello World
nil
=> nil

Up to this point we have been using the Java way of giving parameters, but thanks to Ruby we can dismiss the parenthesis.

irb(main):007:0> java.lang.System::out.println "Hello World"       
Hello World
=> nil

Another of the benefits of JRuby is that it allows easy introspection on objects, you may never use the Java reflection API again.

To see all the methods, we simply call the methods function on the object as we would in Ruby

irb(main):008:0> java.lang.Math.methods
=> ["log", "abs", "cosh", "round", "tanh", "IEEEremainder", "max", "log1p", "sqrt", "rint", "pow", "atan", "min", "to_radians", "asin", "expm1", "ulp", "acos", "signum", "sin", "tan", "to_degrees", "toRadians", "floor", "atan2", "ieeeremainder", "ceil", "exp", "cbrt", "hypot", "cos", "log10", "toDegrees", "sinh", "random", "inherited", "new_proxy", "new", "java_class", "singleton_class", "java_class=", "to_java_object", "[]", "new_instance_for", "superclass", "initialize_copy", "allocate", ">", "autoload", "private_instance_methods", "const_defined?", "instance_method", "const_get", "", "=", "method_defined?", "java_kind_of?", "include_class", "object_id", "frozen?", "eql?", "=~", "methods", "to_a", "tainted?", "javax", "taint", "public_methods", "dup", "method", "==", "singleton_methods", "is_a?", "instance_eval", "equal?", "instance_variable_get", "com", "instance_of?", "hash", "inspect", "java", "org", "__id__", "clone", "__send__", "id", "instance_variable_set", "send", "untaint", "class", "protected_methods", "kind_of?", "instance_variables", "display", "extend", "freeze", "private_methods", "nil?", "type", "instance_exec", "respond_to?"]

Similiarly we can find out the constants as well:

irb(main):009:0> java.lang.Math.constants                          
=> ["PI", "E"]

View the ancestors of a class:

irb(main):011:0> java.lang.Math.ancestors
=> [Java::JavaLang::Math, Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy, JavaProxyMethods, Object, Kernel]

and even the class type:


irb(main):012:0> java.lang.Math.class    
=> Class
irb(main):013:0> java.lang.class     
=> Module

Typing out full package names gets tedious, so you can use the include_class function to import a class.

irb(main):015:0> include_class java.lang.System
=> Java::JavaLang::System

You can then use the class in the same way as though you had used an import statement in Java:

irb(main):016:0> System.out.println("We've been imported")
We've been imported
=> nil

Including a class will fail though if you try to load a class that exists in the Ruby namespace already:

irb(main):014:0> include_class java.lang.Math
(eval):1 warning: already initialized constant Math
=> Java::JavaLang::Math

The way to get around this is to rename the class, in this instance the Math class becomes JMath:

irb(main):017:0> include_class('java.lang.Math') {|package,name| "J#{name}"}
=> ["java.lang.Math"]

and we can use it like so:

irb(main):018:0> JMath::PI
=> 3.141592653589793
irb(main):019:0> JMath.class
=> Class
irb(main):020:0> JMath.ancestors
=> [Java::JavaLang::Math, Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy, JavaProxyMethods, Object, Kernel]

Swinging with JRuby

So far we've done some quaint examples, but nothing large. We will take the code from this page and produce the same results with Ruby.

Some of the changes that are needed:

  • converting new <Object>(par1,par2) syntax into <Object>.new(par1,par2)
  • accessing fields with GridBagConstraints::HORIZONTAL syntax instead of GridBagConstraints.HORIZONTAL
  • removing the old array used to construct the JComboBox:
    String[] options = {"all", "any"};
            modeCombo = new JComboBox(options);
    since this will try to populate the combo box with a Ruby object and the JComboBox will not like that at all. To get around this we had the items one at a time:
     modeCombo = JComboBox.new 
            modeCombo.addItem "all"
            modeCombo.addItem "any"
  • The typical verbose way in Java of exiting the program when the windows closes goes from:
    addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                        System.exit(0);
                }
            });
    to:
     window.defaultCloseOperation = JFrame::EXIT_ON_CLOSE
  • main method is removed

The full code is:


require 'java'

include_class(['javax.swing.JFrame','javax.swing.JLabel','javax.swing.JComboBox','javax.swing.JButton','javax.swing.JPanel','javax.swing.JTable','javax.swing.JTextField', 'java.awt.GridBagLayout', 'java.awt.GridBagConstraints'])

        window= JFrame.new
        contentPane = window.getContentPane
        gridbag = GridBagLayout.new
        contentPane.setLayout gridbag
        
        c = GridBagConstraints.new

        #setting a default constraint value
        c.fill = GridBagConstraints::HORIZONTAL
   
        tagLbl = JLabel.new("Tags")
        c.gridx = 0 
        c.gridy = 0 
        gridbag.setConstraints tagLbl, c 
        contentPane.add(tagLbl)
        
        tagModeLbl = JLabel.new("Tag Mode")
        c.gridx = 0
        c.gridy = 1
        gridbag.setConstraints(tagModeLbl, c)
        contentPane.add(tagModeLbl)
        
        tagTxt = JTextField.new("plinth")
        c.gridx = 1
        c.gridy = 0
        c.gridwidth = 2
        gridbag.setConstraints(tagTxt, c)
        contentPane.add(tagTxt)
        
        modeCombo = JComboBox.new
        modeCombo.addItem "all"
        modeCombo.addItem "any"
        c.gridx = 1
        c.gridy = 1
        c.gridwidth = 1
        gridbag.setConstraints(modeCombo, c)
        contentPane.add(modeCombo)
        
        searchBtn = JButton.new("Search")
        c.gridx = 1
        c.gridy = 2
        gridbag.setConstraints(searchBtn, c)
        contentPane.add(searchBtn)

        resTable = JTable.new(5,3)
        c.gridx = 0
        c.gridy = 3
        c.gridwidth = 3
        gridbag.setConstraints(resTable, c)
        contentPane.add(resTable)
        
        previewLbl = JLabel.new("Preview goes here")
        c.gridx = 0
        c.gridy = 4
        gridbag.setConstraints(previewLbl, c)
        contentPane.add(previewLbl)
        
        window.defaultCloseOperation = JFrame::EXIT_ON_CLOSE
        
        window.pack()
        window.setVisible(true)

If you create a file from this listing, you can invoke it in JRuby like so:

 jruby testwin.rb

Superficially, there is no difference between our JRuby and Java versions of code. Besides the obvious hit to performance incurred by using JRuby, the upside is that it is far quicker to create the code with the same functionality.

It will be interesting to see the speed improvements that occur when JRuby gets a JIT compiler, but in the meantime, if you do not care about performance, you can certainly take JRuby for a spin and see what can be done.

About Chris Duckett

Some would say that it is a long way from software engineering to journalism, others would correctly argue that it is a mere 10 metres according to the floor plan.During his first five years with CBS Interactive, Chris started his journalistic advent...

Editor's Picks

Free Newsletters, In your Inbox