Pregunta

En una pregunta anterior, Acceso a Scala.None desde Java, parece que la gente había usado javap para descubrir cómo acceder scala.None de Java. Me gustaría saber cómo hicieron eso. FYI, la respuesta es:

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

que se puede acortar a:

scala.Option.apply(null);

Dado este programa (OptionTest.scala):

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

Corrí javap en él como esto:

javap -s -c -l -private OptionTest

Esta es una parte del javap producción:

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

También corrí javap en scala.None y scala.Option. ¿Cómo se encontraría con el javap salida que:

  1. None es un único objeto de None.type tipo que se extiende Option
  2. los apply() Se requiere el método para el objeto complementario

?

¿Fue útil?

Solución

Existen reglas de cómo se compila el código Scala a JVM-ByTecode. Debido a los posibles enfrentamientos de nombre, el código generado no siempre es intuitivo de entender, pero si las reglas se conocen, es posible obtener acceso al código SCALA compilado dentro de Java.

Atención: Mientras escribí esto, noté que Javac y Eclipse-Javac se comportan de manera diferente al acceder al código SCALA desde Java. Es posible que el código a continuación se compile con uno de ellos, pero no con el otro.

Clases, constructores, métodos

No hay reglas especiales aquí. La siguiente clase de Scala

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
}

Se puede acceder como una clase Java normal. Se compila en un archivo llamado X.class:

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

Observe que para los métodos sin una lista de parámetros se crea una lista de parámetros vacía. Múltiples listas de parámetros se fusionan con una sola.

Campos, valores

Para una clase class X(var i: Int) Se crean getters y setters. Para una clase class X(val i: Int) Solo se crea un Getter:

//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

Observe que en Java un identificador no puede incluir signos especiales. Por lo tanto, Scalac genera para cada uno de estos signos especiales un nombre específico. Hay una clase Scala.Reflect.nametransformer que puede codificar/decodificar el OPS:

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)

Una clase class X { var i = 5 } se traduce por el mismo esquema que cuando el campo se crea en el constructor. Acceso directo a la variable i De Java no es posible, porque es privado.

Objetos

No existe un objeto Scala en Java. Por lo tanto, Scalac tiene que hacer algo de magia. Por un objeto object X { val i = 5 } Se generan dos archivos de clase JVM: X.class y X$.class. El primero funciona como una interfaz, incluye métodos estáticos para acceder a campos y métodos del objeto Scala. Este último es una clase singleton que no puede ser instanciada. Tiene un campo que contiene la instancia singleton de la clase, nombrada MODULE$, que permite el acceso al singleton:

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

Clases de casos

El compilador de Scala genera automáticamente un método de aplicación para una clase de casos y obteniendo para campos. La clase de casos case class X(i: Int) se accede fácilmente:

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

Rasgos

Un rasgo trait T { def m }, que contiene solo miembros abstractos, se compila en una interfaz, que se coloca en los archivos de clase llamados T.class. Por lo tanto, puede implementarse fácilmente mediante una clase Java:

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

Si el rasgo contiene miembros concretos, hay un archivo de clase llamado <trait_name>$class.class Generado, adicionalmente a la interfaz normal. El rasgo

trait T {
  def m1
  def m2 = 5
}

También puede implementarse fácilmente dentro de Java. El archivo de clase T$class.class Contiene los miembros concretos del rasgo, pero parece que son imposibles de acceder desde Java. Ni Javac ni el Eclipse-Javac compilarán un acceso a esta clase.

Se pueden encontrar algunos detalles más sobre cómo se compilan los rasgos aquí.

Funciones

Los literales de funciones se compilan como instancias anónimas de las clases Functionn. Un objeto Scala

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
  }
}

se compila a los archivos de clase normales, como se describe anteriormente. Además, cada función literal obtiene su propio archivo de clase. Entonces, para los valores de función un archivo de clase llamado <class_name>$$anonfun$<N>.class se genera, donde n es un número continuo. Para métodos de función (métodos, que devuelven una función) un archivo de clase llamado <class_name>$$anonfun$<method_name>$<N>.class es generado. Las partes del nombre de la función están separadas por signos de dólar y frente al anonfun Identificador también hay dos signos de dólar. Para las funciones anidadas, el nombre de la función anidada se adjunta a la función exterior, esto significa que una función interna obtendrá un archivo de clase como <class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class. Cuando una función interna no tiene un nombre, como se ve en h obtiene el nombre apply.

Esto significa que en nuestro caso obtenemos:

  • X$$anonfun$1.class para F
  • X$$anonfun$g$1.class por g
  • X$$anonfun$h$1$$anonfun$apply$1.class Para H
  • X$$anonfun$i$1.class y X$$anonfun$i$1$$anonfun$j$1$1.class para yo y j

Para acceder a ellos, use su método de aplicación:

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

Responder a la pregunta

Usted debe saber:

  • Se puede acceder a una clase SCALA normal por sus constructores o sus métodos de aplicación
  • Cuando no hay constructor de lo que hay un método de aplicación
  • Cuando no hay un constructor ni un método de aplicación, hay otro archivo de clase llamado de la misma manera que se llama la clase que agrega un signo de dólar al final. Buscar esta clase en busca de un MODULE$ campo
  • Los constructores y los métodos de aplicación se heredan, por lo que busque las súper clases si no puede encontrar nada en las subclases

Algunos ejemplos

Opción

// 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 dice que tiene un constructor y un método de aplicación. Además, dice que la clase es abstracta. Por lo tanto, solo se puede usar el método de aplicación:

Option.apply(3);

Alguno

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

Tiene un constructor y un método de aplicación (porque sabemos que la opción tiene una y algunas opciones). Usa uno de ellos y sé feliz:

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

Ninguna

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

No tiene constructor, ningún método de aplicación y no extiende la opción. Entonces, echaremos un vistazo a None$:

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

¡Sí! Encontramos un MODULE$ campo y el método de aplicación de la opción. Además, encontramos el constructor privado:

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

Lista

scala.collection.immutable.List es abstracto, por lo que tenemos que usar scala.collection.immutable.List$. Tiene un método de aplicación que espera un scala.collection.Seq. Entonces, para obtener una lista, primero necesitamos un SEQ. Pero si buscamos SEQ, no hay método aplicado. Además, cuando miramos las súper clases de SEQ y en scala.collection.Seq$ Solo podemos encontrar los métodos de aplicación que espera un SEQ. ¿Entonces lo que hay que hacer?

Tenemos que echar un vistazo a cómo Scalac crea una instancia de lista o SEQ. Primero cree una clase de Scala:

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

Compile con Scalac y mire el archivo de clase con Javap:

// 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

}

El constructor es interesante. Nos dice que se crea una matriz de ints (l. 12) que se llena con 1, 2 y 3. (l. 14-25). Después de eso, esta matriz se entrega a scala.Predef$.wrapIntArray (l. 26). Esto resultante scala.collection.mutable.WrappedArray se entrega nuevamente a nuestra lista (l. 29). Al final, la lista se almacena en el campo (l. 32). Cuando queremos crear una lista en Java, tenemos que hacer lo mismo:

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 }));

Esto se ve feo, pero funciona. Si crea una biblioteca bonita que envuelve el acceso a la biblioteca Scala, será fácil usar Scala de Java.

Resumen

Sé que hay algunas reglas más cómo se compila el código SCALA para bytecode. Pero creo que con la información anterior debería ser posible encontrar estas reglas por usted mismo.

Otros consejos

No estoy compitiendo con la otra respuesta, pero dado que las personas a menudo parecen no darse cuenta de esto, puedes hacer esto en el repl.

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top