Frage

In einer früheren Frage, Zugriff auf Scala.none von Java, Es scheint, dass die Leute benutzt hatten javap um herauszufinden, wie man zugreift scala.None von Java. Ich würde gerne wissen, wie sie das gemacht haben. Zu Ihrer Information, die Antwort lautet:

scala.Option$.MODULE$.apply(null);

das kann kurz gemacht werden zu:

scala.Option.apply(null);

Angesichts dieses Programms (OptionTest.scala):

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

Ich rannte javap darauf wie folgt:

javap -s -c -l -private OptionTest

Dies ist ein Teil der javap Ausgang:

public static final scala.None$ x();
  Signature: ()Lscala/None$;
  Code:
   0:   getstatic  #11; //Field OptionTest$.MODULE$:LOptionTest$;
   3:   invokevirtual  #55; //Method OptionTest$.x:()Lscala/None$;
   6:   areturn

Ich habe auch Javap angelegt scala.None und scala.Option. Wie würde man aus dem herausfinden javap das ausgeben:

  1. None ist ein einziges Objekt von None.type Typ, der sich erstreckt Option
  2. Das apply() Methode für das Begleitobjekt ist erforderlich

?

War es hilfreich?

Lösung

Es gibt Regeln, wie der Scala-Code mit JVM-Bytecode kompiliert wird. Aufgrund des potenziellen Namens ist der generierte Code nicht immer intuitiv zu verstehen, aber wenn die Regeln bekannt sind, ist es möglich, Zugriff auf den kompilierten Scala -Code innerhalb von Java zu erhalten.

Aufmerksamkeit: Während ich dies schrieb, bemerkte ich, dass sich Javac und Eclipse-Javac beim Zugriff auf den Scala-Code von Java unterschiedlich verhalten. Es ist möglich, dass der folgende Code mit einem von ihnen kompiliert wird, aber nicht mit dem anderen.

Klassen, Konstruktoren, Methoden

Hier gibt es keine besonderen Regeln. Die folgende Scala -Klasse

class X(i: Int) {
  def m1 = i*2
  def m2(a: Int)(b: Int) = a*b
  def m3(a: Int)(implicit b: Int) = a*b
}

kann wie eine normale Java -Klasse zugegriffen werden. Es wird in eine Datei mit dem Namen namens zusammengestellt X.class:

X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);

Beachten Sie, dass für Methoden ohne Parameterlist eine leere Parameterlist erstellt wird. Mehrere Parameterlisten werden zu einem einzigen zusammengeführt.

Felder, Werte

Für eine Klasse class X(var i: Int) Getter und Setter werden erstellt. Für eine Klasse class X(val i: Int) Es wird nur ein Getter erstellt:

//Scala
val x = new X(5)
x.i = 3 // Setter
x.i // Getter

//Java
X x = new X(5);
x.i_$eq(3); // Setter
x.i(); // Getter

Beachten Sie, dass in Java eine Kennung nicht spezielle Zeichen enthalten darf. Daher generiert Scalac für jedes dieser Sonderzeichen einen bestimmten Namen. Es gibt eine Klasse scala.reflect.nametransformer Dies kann die OPs codieren/dekodieren:

scala> import scala.reflect.NameTransformer._
import scala.reflect.NameTransformer._

scala> val ops = "~=<>!#%^&|*/+-:\\?@"
ops: String = ~=<>!#%^&|*/+-:\?@

scala> ops map { o => o -> encode(o.toString) } foreach println
(~,$tilde)
(=,$eq)
(<,$less)
(>,$greater)
(!,$bang)
(#,$hash)
(%,$percent)
(^,$up)
(&,$amp)
(|,$bar)
(*,$times)
(/,$div)
(+,$plus)
(-,$minus)
(:,$colon)
(\,$bslash)
(?,$qmark)
(@,$at)

Eine Klasse class X { var i = 5 } wird durch das gleiche Schema übersetzt wie beim Erstellen des Feldes im Konstruktor. Direkter Zugriff auf die Variable i von Java ist nicht möglich, weil es privat ist.

Objekte

Es gibt kein Scala -Objekt in Java. Deshalb muss Scalac etwas Magie machen. Für ein Objekt object X { val i = 5 } Es werden zwei Dateien der JVM-Klasse generiert: X.class und X$.class. Das erste funktioniert wie eine Schnittstelle. Es enthält statische Methoden zum Zugriff auf Felder und Methoden des Scala -Objekts. Letzteres ist eine Singleton -Klasse, die nicht instanziiert werden kann. Es hat ein Feld, das die Singleton -Instanz der Klasse hält, benannt MODULE$, was den Zugang zum Singleton ermöglicht:

X.i();
X$.MODULE$.i();

Fallklassen

Der Scala-Compiler generiert automatisch eine Anwendung für eine Fallklasse und Getters für Felder. Die Fallklasse case class X(i: Int) ist leicht zugänglich:

new X(3).i();
X$.MODULE$.apply(3);

Züge

Ein Merkmal trait T { def m }, das nur abstrakte Mitglieder enthält, wird mit einer Schnittstelle zusammengestellt, die in einem Klassendateien namens platziert ist T.class. Daher kann es leicht durch eine Java -Klasse implementiert werden:

class X implements T {
  public void m() {
    // do stuff here
  }
}

Wenn das Merkmal konkrete Mitglieder enthält, gibt es eine Klassendatei mit dem Namen <trait_name>$class.class Erzeugt, zusätzlich zur normalen Schnittstelle. Das Merkmal

trait T {
  def m1
  def m2 = 5
}

kann auch leicht innerhalb von Java implementieren. Die Klassendatei T$class.class Enthält die konkreten Mitglieder des Merkmals, aber es scheint, dass sie nicht von Java zugreifen können. Weder Javac noch der Eclipse-Javac erstellen einen Zugriff auf diese Klasse.

Einige weitere Details darüber, wie Eigenschaften zusammengestellt werden hier.

Funktionen

Funktionsliterale werden als anonyme Instanzen der Klassenfunktionen zusammengestellt. Ein Scala -Objekt

object X {
  val f: Int => Int = i => i*2
  def g: Int => Int = i => i*2
  def h: Int => Int => Int = a => b => a*b
  def i: Int => Int => Int = a => {
    def j: Int => Int = b => a*b
    j
  }
}

wird wie oben beschrieben mit den normalen Klassendaten zusammengestellt. Darüber hinaus erhält jede Funktion buchstäblich eine eigene Klasse-Datei. Für Funktionswerte eine Klassendatei mit dem Namen <class_name>$$anonfun$<N>.class wird generiert, wobei n eine kontinuierliche Zahl ist. Für Funktionsmethoden (Methoden, die eine Funktion zurückgeben) eine Klassendatei mit dem Namen <class_name>$$anonfun$<method_name>$<N>.class wird erzeugt. Die Teile des Funktionsnamens sind durch Dollarszeichen und vor dem getrennt anonfun Kennung Es gibt auch zwei Dollarschilder. Für verschachtelte Funktionen wird der Name der verschachtelten Funktion an die äußere Funktion beigefügt. Dies bedeutet, dass eine innere Funktion eine Klassendatei wie <class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class. Wenn eine innere Funktion keinen Namen hat, wie in gesehen h Es bekommt den Namen apply.

Dies bedeutet, dass wir in unserem Fall bekommen:

  • X$$anonfun$1.class für f
  • X$$anonfun$g$1.class für g
  • X$$anonfun$h$1$$anonfun$apply$1.class für H
  • X$$anonfun$i$1.class und X$$anonfun$i$1$$anonfun$j$1$1.class Für mich und j

Um auf sie zuzugreifen, verwenden Sie ihre Anwendung-Methoden:

X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);

Beantworte die Frage

Du solltest wissen:

  • Auf eine normale Scala-Klasse kann sie von ihren Konstruktoren oder ihren Anwendungsmethoden zugreifen
  • Wenn es keinen Konstruktor gibt, gibt es einen Antragsmethode
  • Wenn es keinen Konstruktor und keine Methode anwenden als eine andere Klassendatei gibt, die genauso genannt wird, wird die Klasse aufgerufen, die am Ende ein Dollar -Zeichen anhängt. Suchen Sie diese Klasse nach a MODULE$ aufstellen
  • Konstrukteure und Anwendungsmethoden werden vererbt. Suchen Sie also die Superklassen, wenn Sie in den Unterklassen nichts finden können

Einige Beispiele

Möglichkeit

// javap scala.Option
public abstract class scala.Option extends java.lang.Object implements ... {
  ...
  public static final scala.Option apply(java.lang.Object);
  public scala.Option();
}

Javap sagt, dass es einen Konstruktor und eine Methode anwenden hat. Darüber hinaus heißt es, dass die Klasse abstrakt ist. Somit kann nur der anwendende Methode verwendet werden:

Option.apply(3);

Etwas

// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
  ...
  public scala.Some(java.lang.Object);
}

Es verfügt über einen Konstruktor und einen Anwendung-Methoden (da wir wissen, dass die Option eine und einige Erweiterungsoptionen hat). Verwenden Sie einen von ihnen und sei glücklich:

new Some<Integer>(3);
Some.apply(3);

Keiner

// javap scala.None
public final class scala.None extends java.lang.Object{
  ...
}

Es hat keinen Konstruktor, kein Antragsteller und keine Option erweitert. Also werden wir einen Blick darauf werfen None$:

// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
  ...
  public static final scala.None$ MODULE$;
  private scala.None$();
}

Ja! Wir fanden a MODULE$ Feld und die Option anwenden. Darüber hinaus fanden wir den privaten Konstruktor:

None$.apply(3) // returns Some(3). Please use the apply-method of Option instead
None$.MODULE$.isDefined(); // returns false
new None$(); // compiler error. constructor not visible

Aufführen

scala.collection.immutable.List ist abstrakt, daher müssen wir verwenden scala.collection.immutable.List$. Es hat einen Antragsmethode, der eine erwartet scala.collection.Seq. Um eine Liste zu erhalten, brauchen wir zuerst einen SEQ. Aber wenn wir auf SEQ schauen, gibt es keine Anwendung. Außerdem, wenn wir uns die Superklassen von SEQ ansehen und bei scala.collection.Seq$ Wir können nur ein Antragsmethoden finden, das einen SEQ erwartet. Also, was zu tun?

Wir müssen einen Blick darauf werfen, wie Scalac eine Instanz von List oder SEQ erstellt. Erstellen Sie zuerst eine Scala -Klasse:

class X {
  val xs = List(1, 2, 3)
}

Kompilieren Sie es mit Scalac und schauen Sie sich die Klassendatei mit Javap an:

// javap -c -private X
public class X extends java.lang.Object implements scala.ScalaObject{
...
public X();
  Code:
   0:   aload_0
   1:   invokespecial   #20; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   getstatic   #26; //Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
   8:   getstatic   #31; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   11:  iconst_3
   12:  newarray int
   14:  dup
   15:  iconst_0
   16:  iconst_1
   17:  iastore
   18:  dup
   19:  iconst_1
   20:  iconst_2
   21:  iastore
   22:  dup
   23:  iconst_2
   24:  iconst_3
   25:  iastore
   26:  invokevirtual   #35; //Method scala/Predef$.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
   29:  invokevirtual   #39; //Method scala/collection/immutable/List$.apply:(Lscala/collection/Seq;)Lscala/collection/immutable/List;
   32:  putfield    #13; //Field xs:Lscala/collection/immutable/List;
   35:  return

}

Der Konstruktor ist interessant. Es sagt uns, dass eine Reihe von INTs erstellt wird (l. 12), das mit 1, 2 und 3. (l. 14-25) gefüllt ist. Danach wird dieses Array an geliefert scala.Predef$.wrapIntArray (l. 26). Dies resultiert scala.collection.mutable.WrappedArray wird wieder an unsere Liste geliefert (l. 29). Am Ende wird die Liste im Feld gespeichert (l. 32). Wenn wir eine Liste in Java erstellen wollen, müssen wir dasselbe tun:

int[] arr = { 1, 2, 3 };
WrappedArray<Object> warr = Predef$.MODULE$.wrapIntArray(arr);
List$.MODULE$.apply(warr);

// or shorter
List$.MODULE$.apply(Predef$.MODULE$.wrapIntArray(new int[] { 1, 2, 3 }));

Das sieht hässlich aus, aber es funktioniert. Wenn Sie eine gut aussehende Bibliothek erstellen, die den Zugriff auf die Scala -Bibliothek umhüllt, kann Scala von Java einfach verwendet werden.

Zusammenfassung

Ich weiß, dass es weitere Regeln gibt, wie der Scala -Code in Bytecode zusammengestellt wird. Aber ich denke, mit den obigen Informationen sollte es möglich sein, diese Regeln selbst zu finden.

Andere Tipps

Ich konkurriere nicht mit der anderen Antwort, aber da die Leute dies oft nicht bemerken scheinen, können Sie dies in der Wiederholung tun.

scala> :paste
// Entering paste mode (ctrl-D to finish)

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

// Exiting paste mode, now interpreting.

defined module OptionTest

scala> :javap -v OptionTest$
Compiled from "<console>"
public final class OptionTest$ extends java.lang.Object implements scala.App,scala.ScalaObject
  SourceFile: "<console>"
  Scala: length = 0x

  [lots of output etc]   

  public scala.None$ x();
    Code:
     Stack=1, Locals=1, Args_size=1
     0: aload_0
     1: getfield    #65; //Field x:Lscala/None$;
     4: areturn
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top