Scala Scalamethode in Java aufrufen, die Scalamaps benutzt

kirdie

Bekanntes Mitglied
Ich möchte von Java aus eine Scalamethode mit folgender Signatur aufrufen:

Java:
case class Configuration(val prefixes : Map[String, String], val dataSources : Map[String, DataSource],val linkSpecs : Map[String, LinkSpecification], val outputs : Traversable[Output] = Traversable.empty)

Problem ist jetzt, dass die Signatur nach Scalacollections verlangt, nicht nach Javacollections. Jetzt habe ich beim googlen nach dem Problem schon 1000 Lösungen gefunden, die jedoch alle *von scala aus* beschrieben werden (also sie zeigen scalaquelltext).
Was ich aber brauche ist eine Lösung *in Javaquelltext*. Kann mir da jemand helfen?

Was ich z.B. bis jetzt gefunden habe ist eine Library namens ScalaJ-Collection die mehrfach empfohlen wurde (scalaj's scalaj-collection at master - GitHub). Problem ist, auf der Seite gibt es weder eine Dokumentation, das Wiki ist leer und ich finde auch kein jarfile zum Download. Es gibt jedoch die Sources herunterzuladen und in der Readme wird die Benutzung folgendermaßen gezeigt:

Java:
    val list = new java.util.ArrayList[java.lang.Integer]
    list.add(1)
    list.add(2)
    list.add(3)
    list.asScala

Das Problem ist natürlich, dass ich in Java nicht einfach new ArrayList<Integer>().asScala() aufrufen kann, weil eine ArrayList diese Methode nicht aufweist.

Wie kann ich dies jetzt von Java aus aufrufen?
 

Landei

Top Contributor
Ein bisschen von hinten durch die Brust ins Auge, aber man kann eine Scala-HashMap "ganz einfach" direkt befüllen. Meines Wissens ist HashMap die Default-Implementierung für Map, es sollte also beim Aufruf eigentlich(?) keine Probleme damit geben...

Java:
import scala.collection.immutable.HashMap$;
import scala.collection.immutable.HashMap;
import scala.Tuple2;

public class CallMap {
   public static void main(String... args) {
      HashMap<String, String> map = HashMap$.MODULE$.empty();
      for(int i = 0; i < 10; i ++) {
        Tuple2<String, String> tuple = new Tuple2<String, String>("key"+i, "value"+i);
        map = map.$plus(tuple);
      }
      System.out.println(map);
   }
}

Und wenn man dieses Grauen ordentlich in eine Utility-Methode verpackt, erspart man sich auch den Augenkrebs...
 
Zuletzt bearbeitet:

kirdie

Bekanntes Mitglied
Super, das wird akzeptiert erstmal!
Jetzt habe ich aber ein weiteres Problem und zwar ist ja einer der Parameter vom Typ "Datasource".
Das Problem ist, dass die Scalaklasse keinen Konstruktor hat, wie erzeuge ich denn da eine Instanz dazu?
Der Scalaquelltext ist:

Java:
import de.fuberlin.wiwiss.silk.util.{Factory, Strategy}
import de.fuberlin.wiwiss.silk.instance.{InstanceSpecification, Instance}

trait DataSource extends Strategy
{
    def retrieve(instanceSpec : InstanceSpecification, prefixes : Map[String, String]) : Traversable[Instance]
}

object DataSource extends Factory[DataSource]

Und erzeugt wird das Objekt dort so:

Java:
 private def loadDataSources(xml : Elem) : Map[String, DataSource] =
    {
        (xml \ "DataSources" \ "DataSource")
            .map(ds => (ds \ "@id" text, DataSource(ds \ "@type" text, loadParams(ds)))).toMap
    }

P.S.
Java:
DataSource(ds \ "@type" text, loadParams(ds))

Sieht ja nach einen Konstruktoraufruf aus (nur fehlt da das "new" aber das ist wohl bei scala nicht nötig).
Aber wenn ich in Java DataSource d=new DataSource(); mache, dann kommt "cannot instantiate type datasource".
 
Zuletzt bearbeitet:

Landei

Top Contributor
Dass die komische Syntax mit den "\" zum Auseinandernehmen eines XML-Literals ist (also ein ungefähres Äquivalent zu Path-Ausdrücken in XSLT), weißt du sicher schon.

Auch in Scala muss ein Konstruktor mit new aufgerufen werden. Ohne new handelt es sich um eine Factory-Methode des Begleit-Objekts (companion object) - so ähnlich wie weiter oben Map.empty() aufgerufen wurde, nur diesmal mit einer "magischen" apply-Methode, bei der man statt DataSource.appy(...) einfach DataSource(...) schreiben darf. Der Aufruf in Java dürfte also ungefähr so aussehen:

Java:
DataSource sd = DataSource$.MODULE$.apply(text, params);

Gibt es einen speziellen Grund, warum der Aufruf unbedingt von Java aus erfolgen muss? Ein kleineres Java-Projekt ist ratz-fatz zu Scala konvertiert.

Und wenn es schon Java sein muss: Ein paar kleine Adapterklassen auf Scala-Seite würden die Aufrufe von Java-Seite aus wesentlich erleichtern.
 
Zuletzt bearbeitet:

kirdie

Bekanntes Mitglied
Also dass das \ irgendwas mit XML zu tun hat weiß ich, wie das genau funktioniert, nicht.

Vielen Dank, dass du das so ausführlich erklärst! Ich probiere das gleich mal aus mit dem $.MODULE$.apply.

Die Sache ist so: Es gibt ein Scalaprojekt namens "Silk" und wir versuchen gerade, ein Programm zu schreiben, dass darauf aufsetzt, d.h. eine GUI drum rum baut, dessen Resultate auswertet um das Programm mit neuen Parametern erneut zu starten usw.. Wir benutzten also das jarfile von silk und ich kann darin auch nichts ändern, da ich es nicht kompiliert bekomme, da es ziemlich groß ist und ich mit der Eclipseintegration von Scala (Edit, hatte ausversehen Silk geschrieben) und Maven nicht so richtig klarkomme. Ich kann das natürlich auch nochmal probieren aber das frisst halt immer ewig Zeit und wenn dann ein neues Release da ist muss ich das auch jedesmal wieder ändern.

Es können auch glaub ich auch nicht alle von den Programmierern Scala, d.h. das Programm muss soweit ich weiß auch in Java geschrieben werden.

Edit:
Es funktioniert leider nicht mit dem "Module" aber es gibt zumindest noch keinen IDE-Error bei

Java:
DataSource sd = (DataSource) new Factory().apply("string",params);

Jetzt muss ich nur noch die Parameter befüllen und gucken ob es funktioniert...

P.S.: Die Factoryklasse sieht so aus:

Java:
class Factory[T <: Strategy] extends Function2[String, Map[String, String], T]
{
    private val classes = HashMap[String, StrategyDefinition]()

    def register[U <: T](name : String, implementationClass : Class[U], defaultParams : Map[String, String] = Map.empty)
    {
        //TODO check if implementationClass provides a constructor of the type (Map[String, String])
        classes.update(name, new StrategyDefinition(implementationClass, defaultParams))
    }

    def apply(name : String, params : Map[String, String] = Map.empty) : T =
    {
        classes.get(name) match
        {
            case Some(strategy) => strategy.clazz.getConstructor(classOf[Map[String, String]])
                                                 .newInstance(strategy.params ++ params)
                                                 .asInstanceOf[T]
            case None => throw new IllegalArgumentException("No implementation found with name " + name)
        }
    }

    def unapply(t : T) : Option[(String, Map[String, String])]  =
    {
        classes.find { case (name, strategy) => strategy.clazz.isAssignableFrom(t.getClass) } match
        {
            case Some((name, strategy)) => Some((name, t.params))
            case None => None
        }
    }

    private class StrategyDefinition(val clazz : Class[_], val params : Map[String, String])
}
 
Zuletzt bearbeitet:

Landei

Top Contributor
Gut, dann ist die Methode wohl im Supertrait (?) implementiert. Ohne Code ist das schwierig zu sagen, aber ich vermute mal, der Aufruf muss
Java:
DataSource sd = new Factory<DataSource>().apply("string",params);

lauten.

[Edit] Ja, das müsste stimmen.

Unter den geschilderten Umständen wäre mein zweiten Vorschlag (ein paar scala-seitige Hilfsklassen) eine mögliche Lösung. Die Scala-Schicht muss ja nicht unbedingt in Eclipse programmiert werden (ich nehme Netbeans, was auch Maven unterstützt, und es gibt ein Plugin für IntelliJ IDEA). Langfristig würde sich ein Umstieg auf Scala sicher lohnen, selbst wenn einige Programmierer es nur als "besseres Java" verwenden würden.
 
Zuletzt bearbeitet:

kirdie

Bekanntes Mitglied
Da kommt bei mir der Fehler "the type Factory is not generic". Aber wenn ich die diese Typisierung (oder wie das heißt) weglasse, dann nimmt er es erstmal an. Trotzdem warnt er mich jetzt "Factory is a raw type. References to generic type Factory<T> should be parameterized". Ich verstehs nicht...

Ich verstehe auch nicht, warum man nicht einfach nen Konstruktor schreiben kann sondern 50 Millionen Factories hin und herwerfen muss, bei denen man Jahr braucht um durchzublicken, wie man so ein Objekt überhaupt initialisiert :)

P.S.: Ok, ich werde auf jeden Fall mal nachfragen was mein Chef davon hält, das alles in Scala zu machen.
 
Zuletzt bearbeitet:

Landei

Top Contributor
Die Variante ohne Generics sollte funktionieren, aber nur weil es hier entsprechend implementiert ist, anderswo kann das gleiche "Muster" in die Hose gehen.

Warum der Fehler kommt, kann ich nicht sagen. Man merkt eben hin und wieder, dass es die "falsche" Aufruf-Richtung ist. Wenn es dich trotzdem stört, kannst du das Problem auch bei Scala Forum oder der Scala-Mailingliste posten. Ansonsten bleiben noch scalaseitigen Hilfsklassen, was ich hier noch für die beste Lösung halte.
 

kirdie

Bekanntes Mitglied
So, also vielen Dank für die ganze Hilfe aber ich glaube ich gebe es auf.
Ich werde jetzt einfach die Möglichkeit nutzen, das jarfile mit einer xml aufzurufen.
D.h. in meinem Programm erzeuge ich zuerst eine XML-Datei mit den Parametern und rufe dann die Methode auf, der man als String einfach nur den Namen der XML-Datei übergibt, das krieg ich auch von Java noch hin :)
Ich dachte ja am Anfang, es wäre einfacher, die Skalaklasse aufzurufen als ein zwischenxml zu erzeugen aber jetzt merke ich, dass das doch die deutlich einfachere Lösung ist.
 

Landei

Top Contributor
Wirf die Flinte nichts ins Korn, ich denke du bist dichter dran als du glaubst. Die Variante

Java:
DataSource sd = (DataSource) new Factory().apply(name, params);

sollte ja funktionieren, trotz der Warnung. Der Name soll für irgendeine "Strategie" stehen, vielleicht muss da "DataSource" selbst eingetragen werden.

Für die Aufrufe hilft übrigens oft, die Scala-Klasse mal durch javap zu jagen, um zu sehen, was "wirklich" drinsteckt.
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben