JVM-Sprachen im Mix - JAXenter
Java Blend

JVM-Sprachen im Mix

Eckard Buchner
Gourmet Java Blend

Welche Mischformen gibt es konkret? Zunächst muss man unterscheiden, auf welcher Ebene die Mischung stattfindet:

  • Wiederverwenden vorkompilierter Bibliotheken
  • Einbinden von Scala und Groovy als Skript
  • Gemischte Klassenhierarchien

Wenn sich die neuen Sprachen verbreiten, wird man häufiger auf „fremdsprachige“ Bibliotheken stoßen und man muss mit den jeweiligen Besonderheiten zurechtkommen. Nehmen wir als Beispiel die Ermittlung des MD5-Fingerabdrucks einer Datei.

class FingerPrint {
  def fingerPrint(file) {
    def digest = MessageDigest.getInstance("MD5").digest(file.bytes)
    digest.collect {String.format("%02X", it)}.join(":")
  }
}
(FingerPrint.groovy)

Zunächst wird der Inhalt der Datei eingelesen und mit der Klasse MessageDigest aus dem Java API die MD5-Checksumme gebildet. Dabei erhält man ein Byte Array, dessen Elemente in hexadezimalem Format mit Doppelpunkt getrennt aufbereitet werden. Hier ein Beispiel: 12:0E:9E:09:1F:AC:F0:FC:7D:08:43:84:7A:4C:6B:2B. Der Charme von Groovy ist nebenbei bemerkt auch der, dass man dazu nur zwei Zeilen Code benötigt. Auch in Java (oder Scala) lässt sich die Klasse verwenden:

// Java
File f = new File(...);
System.out.println(new FingerPrint().fingerPrint(f));

In Groovy könnte man einen Schritt weiter gehen und die Methode der java.io.File-Klasse hinzufügen. Mit dem Category-Mechanismus, einer AST-Transformation, geht das wie folgt:

@Category(File)
class FingerPrintCategory {
  def fingerPrint() {
     new FingerPrint().fingerPrint(this)
  }
} 
(FingerPrintCategory.groovy)

Groovy-Entwickler erhalten den MD5-Fingerabdruck nun direkt von jeder File-Instanz:

use(FingerPrintCategory) {
      File f = new File(...)
      println f.fingerPrint()
    }

In anderen Sprachen kann man den Groovy Code von der Eval-Klasse aus dem Groovy API ausführen lassen:

// Scala
    val f = new File(...)
    println (Eval.x(f, "use(FingerPrintCategory) {x.fingerPrint()}"))

Die Einbindung mit Eval ist auch möglich, wenn die Methode als Expando erst zur Laufzeit zu java.io.File hinzufügt wird:

File.metaClass.fingerPrint = {
  new FingerPrint().fingerPrint(delegate)
}

Allerdings müssen Sie dafür sorgen, dass die dynamische Erweiterung erfolgt, bevor Sie sie im Skript verwenden. In einer Grails-Webapplikation könnte man das sicherstellen, indem man die Erweiterung in der init()-Methode einer Bootstrapping-Klasse durchführt [9].

Mit List und Groovy

Betrachten wir einen weiteren Punkt bei der Wiederverwendung von Bibliotheken: Während Groovy die Java Collections verwendet und dafür eine native Syntax anbietet, enthält das Scala API eine sehr umfangreiche Bibliothek mit völlig eigenständiger Klassenhierarchie. Für ein Scala-List-Objekt muss man einen Scala-Iterator verwenden, um über die Elemente zu iterieren (Listing 2).

Listing 2

class SListProvider {
  def createList = List[Int](10,20,30)
}
(SListProvider.scala)

import scala.Iterator;
import scala.List;

public class JListClient {
    public static void main(String[] args) {
        List scalaList = new SListProvider().createList();
        for (Iterator it = scalaList.elements(); it.hasNext();) {
            System.out.println(it.next() + 10);
        }
    }
} 
(JListClient.java)

Der Scala-Iterator bietet die gewohnten hasNext()– und next()-Methoden an, die man auch in Groovy verwenden könnte. Wir wollen aber zeigen, dass der Iterator auch mit eleganteren Methoden wie each(), eachWithIndex(), any() usw. zusammenarbeitet, bei denen jedes Element durch eine Closure verarbeitet wird:

[10,20,30].each {
      println it + 10
    }

Dazu muss ein Objekt nur eine iterator()-Methode mit dem Rückgabewert java.util.Iterator anbieten. Die fehlende Methode kann man mithilfe der Groovy-Metaklassen ergänzen. Wir schreiben zunächst eine Klasse, die die Java-Iterator-Schnittstelle mithilfe eines Scala-Iterators implementiert (Listing 3).

Listing 3

class GroovyIterator implements java.util.Iterator {
  scala.Iterator iterator

  def GroovyIterator(scala.Iterator iterator) {
    this.iterator = iterator
  }

  boolean hasNext() {
    return iterator.hasNext()
  }

  Object next() {
    return iterator.next()
  }

  void remove() {
    throw new UnsupportedOperationException()
  }
}
(GroovyIterator.groovy)

Die remove()-Methode, mit der die zugrunde liegende Collection modifiziert werden kann, ist im Scala API so nicht vorgesehen. Die Diskussion würde hier zu weit führen. Für unsere Zwecke genügen die ersten beiden Methoden. Wir benutzen die Hilfsklasse, um alle Scala-Objekte zu erweitern, die einen Iterator liefern können (also von scala.Iterable abgeleitet sind):

scala.Iterable.metaClass.iterator  new GroovyIterator(elements())
    }

Jeder iterator()-Aufruf für ein Objekt aus den Scala-Collections erzeugt jetzt eine Instanz von GroovyIterator, die mit Groovy-üblichen Mitteln durchlaufen werden kann. Damit können wir in Groovy schreiben:

// Java
File f = new File(...);
System.out.println(new FingerPrint().fingerPrint(f));

Wie bereits erwähnt, ist diese Erweiterung nur für Groovy-Klassen sichtbar.

Geschrieben von
Eckard Buchner
Kommentare

Schreibe einen Kommentar

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