Eine Ruby-Implemtierung in Java

JRuby

Markus Jais

Script-Sprachen scheinen in letzter Zeit auch bei den Großen der Branche an Beliebtheit zu gewinnen. IBM liefert ein Rails Development Kit für seine DB2-Datenbank, Microsoft stellt Entwickler ein, um Python und Ruby auf .NET lauffähig zu machen, und Sun hat jüngst die beiden Hauptentwickler von JRuby eingestellt, um eine vollständige Ruby-Implementierung für die Java-Plattform zur Verfügung zu stellen.

Das JRuby-Projekt gibt es schon seit längerer Zeit, aber die Entwicklung ging meist nur schleppend voran, was mit Sicherheit auch daran liegt, dass es sich hierbei um kein einfaches Unterfangen handelt. Vor einiger Zeit haben dann Charles Oliver Nutter und Thomas E. Enebo die Entwicklung übernommen und einige beachtliche Fortschritte erzielt. Gleichzeitig wurde Ruby durch das Ruby on Rails-Framework [1] immer beliebter. Der Erfolg von Rails ist so groß ausgefallen, dass auch Sun das nicht mehr übersehen kann, u.a. auch deshalb, weil viele Java-Entwickler begonnen haben, mehr und mehr Projekte mit Rails umzusetzen. Natürlich ist auch Rails nicht ohne Probleme und keineswegs perfekt (aber welche Software ist das schon?), aber für viele Projekte ist Rails hervorragend geeignet und man kann bei diesen wesentlich schneller und produktiver zu einem Ergebnis kommen als mit entsprechenden Java-Technologien. Auf diversen Java-Webseiten gab es schon bald viele hitzige Diskussionen darüber, ob nun Ruby oder Java besser sei. Während manche Ruby nur als Spielzeug betrachten und andere schon das Ende von Java herauf beschwören, erkennen einige das Potenzial in der Zusammenarbeit von Ruby und Java. Wenn man etwa Rails auch auf Java laufen lassen könnte, z.B. durch einen Ruby-Interpreter in Java, und wenn man zusätzlich noch die Möglichkeit schaffen könnte, von Ruby aus auch alle Java-Klassen zu nutzen, dann hätte man eine sehr leistungsfähige und produktive Lösung zur Entwicklung von Webanwendungen (mit Rails) oder allen möglichen Anwendungen (mit Ruby als Sprache und Java als Laufzeitumgebung). Sun auf jeden Fall scheint von der Idee überzeugt zu sein und hat kürzlich Nutter und Enebo eingestellt, um an JRuby zu arbeiten [2] [3].

JRuby installieren

Viele Programmierer lernen eine neue Technologie am schnellsten kennen, wenn sie ein wenig damit spielen. Darum wollen wir JRuby erst einmal installieren. Dazu laden wir uns die neueste Version von der JRuby-Website [4] herunter (auch auf Heft-CD). Zum Zeitpunkt des Schreibens war das die Version 0.9.1. Entpacken Sie die Datei und kopieren Sie den Inhalt in ein Verzeichnis Ihrer Wahl. Bei mir liegt JRuby unter Linux immer unter /opt/jruby. Anschließend setzen Sie noch die Umgebungsvariable JRUBY_HOME auf das gewählte Verzeichnis. Um den JRuby-Interpreter aufrufen zu können, ist es ratsam, das Verzeichnis JRUBY_HOME/bin noch in die PATH-Variable aufzunehmen. Neben dem eigentlichen Interpreter befinden sich in diesem Verzeichnis noch einige andere nützliche Werkzeuge. Eines davon ist ein interaktiver Ruby-Interpreter. Diesen könnten Sie mit

psenv::pushli(); eval($_oclass[„“]); psenv::popli(); ?>

jirb starten (unter Windows jirb.bat). jirb (oder irb für die C-Version von Ruby) eignet sich hervorragend für die ersten Schritte mit Ruby, aber auch, um mal schnell ein Stück Code auszuprobieren. Schauen wir uns nachfolgend zwei kleine Ruby-Beispiele an, um ein wenig ein Gefühl für diese Sprache zu bekommen. Natürlich kann hier nur ein ganz kleiner Teil von Ruby gezeigt werden. Falls Sie sich näher mit Ruby beschäftigen möchten (und das lohnt sich meiner Meinung nach auf jeden Fall), dann empfehle ich Ihnen dringend „Programming Ruby“, das Standardwerk zu Ruby. Eine alte Auflage finden Sie online unter [5]. Unter [6] findet sich eine weitere gute Einführung in Ruby.

Ruby-Eigenschaften

Ruby ist im Gegensatz zu Java eine vollständig objektorientierte Sprache. Zum Beispiel sind auch normale Zahlen, für welche man bei Java normalerweise den Datentyp int benutzt, in Ruby Objekte:

irb(main):001:0> -5.abs
=> 5

Hier sehen Sie eine jirb-Session. Wir rufen die Methode abs für ein Fixnum-Objekt auf. Fixnum repräsentiert in Ruby ganze Zahlen. Wie erwartet, bekommen wir den absoluten Betrag von 5 ausgegeben.

irb(main):001:0> h = { "eins" => "uno", "zwei" => "dos" }
=> {"zwei"=>"dos", "eins"=>"uno"}
irb(main):002:0> h.each do |k,v|
irb(main):003:1* puts "#{k} -> #{v}"
irb(main):004:1> end
zwei -> dos
eins -> uno

Eines der interessantesten Features von Ruby sind so genannte Code-Blocks. Die Methode each ist ein Iterator über den zuvor erzeugten Hash. Der Code zwischen den do/end-Keywords ist der eigentliche Code-Block und wird für jedes Schlüssel/Wert-Paar im Hash ausgeführt. So können wir mit puts ganz einfach den Hash ausgeben. Den Variablen k und v wird hier der Key und der Value des Hash zugewiesen. Ich habe hier das Hash-Beispiel gewählt, weil ich Ihnen später zeigen möchte, dass Sie mit JRuby auch Java-Datentypen wie HashMap auf diese Art und Weise verwenden können. Das Block-Konzept ist viel mächtiger, als hier dargestellt werden kann, und ich möchte Sie noch mal auf oben genanntes Buch verweisen. Ruby kennt wie Java das Konzept von Klassen und lässt keine Mehrfachvererbung zu (allerdings können Module, welche Klassen recht ähnlich sind, als Mixin für Klassen verwendet werden). Außerdem gibt es in Ruby die Möglichkeit, Methoden als private, protectet und public zu kennzeichnen. Ruby unterstützt ähnlich wie Java Reflection, geht hier aber teilweise noch deutlich weiter. So basieren große Teil von Rails auf den so genannten Metaprogrammierungs-Fähigkeiten von Ruby. Dadurch lässt sich ein Framework wie Rails mit sehr wenigen Zeilen Code (verglichen mit anderen Sprachen) entwickeln. Generell lässt sich sagen, dass ein Java-Entwickler sich sehr schnell in die Grundlagen von Ruby einarbeiten kann. Darum möchte ich jetzt hier nicht weiter auf die Eigenschaften von Ruby, sondern nachfolgend auf JRuby-spezifische Eigenschaften eingehen.

JRuby-Beispiel

Schauen wir uns nun eine kleine Swing-Anwendung mit JRuby an. Diese wird keinen Preis für das tollste GUI gewinnen, aber man sieht hier sehr schön, wie Ruby die notwendigen Java-Klassen verwendet. Den Quellcode dazu sehen Sie in Listing 1. Unser Beispiel ist ein ganz einfaches Deutsch/Spanisch-Wörterbuch. Die Anwendung erzeugt ein Textfeld, in welchem man ein spanisches Wort eingegeben kann, und darauf erscheint ein weiteres kleines Fenster mit der Übersetzung. Mit diesem Programm werden Sie zwar in Spanien nicht weit kommen, da wir nur die Zahlen von eins bis drei übersetzen, aber für Demonstrationszwecke reicht es. Am Anfang müssen wir dem JRuby-Interpreter mitteilen, dass wir auch auf Java-Klassen zugreifen wollen. Dies geschieht mit require ‚java‘. Anschließend inkludieren wir sämtliche benötigten Java-Klassen. Das Wörterbuch selbst wird durch die Klasse SpanishDictionary repräsentiert. Die Methode initialize wird in Ruby bei jeder Erzeugung eines neues Objektes aufgerufen. Sie entspricht daher in etwa dem Konstruktor bei Java. Die Variable @words ist eine Instanzvariable (in Ruby gekennzeichnet durch das @-Symbol), welche die Übersetzung für die deutschen Wörter ins Spanische enthält. Beachten Sie, dass wir die HashMap genauso verwenden können wir einen normalen Ruby Hash, d.h., wir müssen nicht die Methoden put und get verwenden. In der Methode translate mögen Ihnen vielleicht die beiden Fragezeichen komisch vorkommen, wenn Sie bisher noch nicht viel mit Ruby zu tun hatten. Das zweite gehört zum ternären Operator, wie Sie in auch von Java her kennen. Das erste gehört zum Methodennamen nil?. Es ist in Ruby Konvention, Methoden, welche eine Frage darstellen, ein Fragezeichen anzuhängen. In Java würde man schreiben isNull() (nil entspricht in Ruby in etwa null in Java). Die Klasse DictGui ist die eigentliche Anwendung. Analog zu Java erben wir von ActionListener und implementieren die Methode actionPerformed. Der Rest dürfte jedem klar sein, der schon mal ein wenig mit Swing gearbeitet hat. Wichtig ist noch der Aufruf von super gleich zu Beginn von initialize. Dies ist für JRuby 0.9.2 noch notwendig. Das Problem ist bereits bekannt und wird bestimmt bald gefixt sein.

require 'java'

include_class "java.util.HashMap"
include_class "java.awt.event.ActionListener"
include_class "java.awt.FlowLayout"
include_class ["JButton", "JFrame", "JLabel", "JOptionPane", "JTextField"].map {|e| "javax.swing." + e}

class SpanishDictionary
 def initialize
   @words = HashMap.new
   @words["eins"] = "uno"
   @words["zwei"] = "dos"
   @words["drei"] = "tres"
 end
 
 def translate(german_word)
   !@words[german_word].nil? ? @words[german_word] :
      "keine ahnung"
 end
end

class DictGui Translation: " + result);
  end
  
  def initialize
     super
     @dict = SpanishDictionary.new
     @textField = JTextField.new(20)
     
     frame = JFrame.new("Hello Swing")
     frame.setSize(250, 100);
     frame.setLayout(FlowLayout.new);
     translateButton = JButton.new("translate")
     translateButton.addActionListener(self);
     frame.add(@textField);
     frame.add(translateButton);
     frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE);
     frame.setVisible(true);
  end
end

gui = DictGui.new
Ruby innerhalb von Java

Sie haben nun gesehen, wie man innerhalb von Ruby aus auf Java-Klassen zugreifen kann. In Listing 2 sehen Sie ein Beispiel dafür, wie man von Java aus Ruby-Quellcode ausführen kann. Das Beispiel verwendet das Apache Bean Scripting Framework [7]. Zum Zeitpunkt des Schreibens ist das die empfohlene Methode. Wenn Sie bereits Java 6 verwenden, haben Sie auch die Möglichkeit, über das neue Java Scripting API [8] [9] (JSR 223) zu verwenden. Der Code ist nicht weiter kompliziert und sollte relativ selbsterklärend sein. In Zukunft gibt es vielleicht auch noch einfachere und nicht vom BSF abhängende Lösungen. Es lohnt sich hier, die weitere Entwicklung zu verfolgen.

import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;


public class TestBSF {

    private static final String SCRIPT = "require 'java'n"
        + "        puts "hello JRuby"n";
    
    
    public static void main(String[] _args) {
         
               BSFManager.registerScriptingEngine("ruby",
                 "org.jruby.javasupport.bsf.JRubyEngine",
                 new String[] { "rb" });
               BSFManager manager_ = new BSFManager();
         try {
               manager_.exec("ruby", "(java)", 1, 1, SCRIPT);
          } catch (BSFException e) {
               System.out.println("error executing Ruby script: " + e.getMessage());
          }     
    }
}
Vergleich mit Groovy

Nachdem Sun die JRuby-Entwickler eingestellt hatte, befürchteten einige, dass damit Groovy [10] ins Hintertreffen geraten könnte. Das ist aber zum Glück nicht der Fall und die Groovy-Community ist nach wie vor sehr aktiv. Vor allem das Grails Framework [11], welches deutlich von Ruby on Rails inspiriert wurde, aber auf Java-Technologien wie Hibernate, Spring und anderen aufbaut, dürfte für Groovy den Durchbruch bringen, ähnlich wie ihn Ruby on Rails für Ruby brachte. Groovy hat den Vorteil gegenüber JRuby, dass es keine Portierung einer bestehenden Sprache ist, sondern direkt für die Java Virtual Machine entwickelt wurde. Dadurch ist die Integration in Java wesentlich einfacher zu implementieren als die von JRuby. Auch ist Groovy in der Lage, direkt Java-Bytecode zu erzeugen. JRuby wird momentan noch interpretiert. An einem Bytecode-Compiler wird allerdings bereits gearbeitet. Wenn JRuby mal die Version 1.0 erreicht hat, dürfte es oft der persönliche Geschmack des jeweiligen Entwicklers sein, welche über den Einsatz der einen oder anderen Programmiersprache entscheidet.

Einsatzmöglichkeiten

Sie werden Sich jetzt vielleicht fragen, wofür man JRuby am besten einsetzen kann. Zum Zeitpunkt des Schreibens (Mitte November 2006) ist JRuby noch nicht so stabil wie die C-Version. Wann immer Sie also Ruby für produktive Anwendungen einsetzen möchten (z.B. mit Rails), dann sollten Sie auf jeden Fall die C-Variante bevorzugen. Diese ist nicht nur stabiler, sondern auch performanter. Die JRuby-Entwickler hoffen allerdings, die Geschwindigkeit der C-Implementierung zu erreichen. Mittlerweile steht für Rails ein Modul ActiveRecord-JDBC [12] zur Verfügung, wodurch man auch mit JRuby und Rails auf alle gängigen Datenbanken zugreifen kann. Für die Zukunft dürfte das eine interessante Alternative sein. Auch haben einige JRuby-Fans Rails bereits auf dem Glassfish Application Server zum Laufen gebracht. Trotzdem dürften viele Ruby-Anhänger erstmal der C-Version treu bleiben. Interessant wird JRuby für Ruby-Programmierer vor allem dann, wenn man auf bestehende Java-Bibliotheken zugreifen möchte. Für Java stehen wesentlich mehr Bibliotheken (oft auch in stabilerem Zustand oder mit mehr Features) zur Verfügung als für Ruby (allerdings holt Ruby hier gerade stark auf). Wenn Sie also z.B. Hibernate, Tapestry oder andere Open-Source-Frameworks verwenden möchten, ist JRuby eine gute Wahl. Auch für Java-Programmierer ist JRuby oft eine Alternative zu Java selbst. Zum einen dann, wenn man auf Ruby-Frameworks zurückgreifen möchte, aber auch für das Schreiben von Tests oder Prototypen. Weitere interessante Möglichkeiten ergeben sich bei der Integration von Java- und Ruby-Anwendungen. Nicht zuletzt erlaubt die Version 2.0 von Spring das Schreiben von Spring Beans in Ruby (und Groovy).

Die Zukunft von JRuby

Es sind aber noch einige Hürden zu überwinden. Momentan ist die Java-Version meist noch deutlich langsamer als die C-Version. Problematisch sind auch die unterschiedlichen Objekt-Modelle von Ruby und Java. In Ruby fehlen abstrakte Klassen und Interfaces. Dafür kann man in Ruby Klassen jederzeit wieder „öffnen“ und erweitern. Solange JRuby als reiner Interpreter implementiert ist, stellt z.B. Letzteres kein großes Problem dar. Aber wenn man Ruby-Code in Java-Bytecode kompilieren möchte, wird es schwierig, da Java dieses Konzept nicht kennt. Die JRuby-Entwickler arbeiten bereits an einer Lösung. Überlegt wird z.B., Ruby-Klassen in mehrere Methodenobjekte aufzuspalten und diese in Java-Bytecode zu übersetzen. Durch diese Aufteilung kann man Ruby-Klassen und deren Methoden viel flexibler handhaben, als wenn man sie einfach in Java-Klassen übersetzt.

Viele Ruby-Objekte können nicht direkt auf Java-Objekte abgebildet werden. So sind Strings in Ruby z.B. veränderbar, während Java-Strings nicht verändert werden können. Strings werden momentan von einem RubyString-Objekt, welches intern StringBuffer verwendet, repräsentiert. Das Ziel der JRuby-Entwickler ist es aber, möglichst viele Ruby-Objekte durch ein einfaches Java-Objekt abzubilden, z.B. Rubys String als StringBuffer und Rubys Fixnum als Java Long. Diesen Java-Objekten könnte man dann zusätzliche Eigenschaften hinzufügen, ähnlich wie es Groovy mit seinem Meta-Objekt-Protokoll macht. Was bei Groovy funktioniert sollte auch bei JRuby klappen.
Ruby hat im Gegensatz zu Java auch keine eingebaute, umfassende Sicherheitsarchitektur. Die Ruby selbst hier nur über wenige Mechanismus verfügt ist diese in Java leicht abbildbar.

JRuby verwendet im Gegensatz zur C-Version Native Threads, da auch Java diese verwendet. Dadurch hat JRuby im Gegensatz zu der C-Version meiner Meinung nach einen deutlichen Vorteil, wenn es um die Implementierung von nebenläufigen Anwendungen geht.

Wie Sie sehen können, gibt es noch einige Schwierigkeiten bei der Entwicklung von JRuby und ob es jemals eine zu 100 Prozent zur C-Version kompatible Version gibt, bleibt abzuwarten. Trotzdem sehe ich die Zukunft von JRuby als sehr positiv an, nicht zuletzt deswegen, weil Sun jetzt dahinter steht. Das JRuby-Projekt stößt sowohl in der Ruby- als auch in der Java-Community auf großes Interesse. Und bei Open Source wurden schon oft Probleme gelöst, die man für (zu) kompliziert hielt.

Fazit

In letzter Zeit wurde oft darüber gestritten, ob Ruby oder Java nun besser ist. Das kann zwar auf Java- oder Ruby-Usergroup-Treffen weiterhin ein beliebtes und spannendes Thema bleiben, aber für den heutigen Programmierer ist es immer sinnvoll, seinen Horizont zu erweitern und sich nach neuen Möglichkeiten umzuschauen, wie man seinen Job produktiver und angenehmer gestalten kann. Java-Programmierern möchte ich daher raten, sich mal ganz unvoreingenommen Ruby etwas näher anzuschauen. Und Ruby-Programmierer sollten sich auch Java einmal näher anschauen, falls Sie es noch nicht kennen. Dabei kann jeder noch etwas lernen und so profitiert man immer davon, egal ob man Ruby, Java oder JRuby dann später einmal produktiv einsetzt oder nicht. Ich wünsche Ihnen viel Spaß beim Experimentieren.

Markus Jais ist Softwareentwickler. In seiner Freizeit trifft er sich mit Freunden, geht Wandern, beobachtet die heimische Vogelwelt und beschäftigt sich mit Java, C++, Ruby, Python, PHP und anderen Programmierthemen. Im Internet ist er unter www.mjais.de zu finden.
Weitere Infos: Dave Thomas, Chad Fowler, Andy Hunt: Programming Ruby, Pragmatic Bookshelf, 2004
Geschrieben von
Markus Jais
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.