scala scala-java-interop javap

¿Cómo aprender sobre el uso de scala.None desde Java usando javap?



scala-java-interop (2)

En una pregunta anterior, al acceder a scala.None desde Java , parece que las personas habían usado javap para descubrir cómo acceder a scala.None desde Java. Me gustaría saber cómo lo hicieron. FYI, la respuesta es:

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

que puede acortarse 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 sobre esto así:

javap -s -c -l -private OptionTest

Esta es una parte de la salida de javap :

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 ejecuté javap en scala.None y scala.Option . ¿Cómo se podría deducir de la salida javap que:

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

?


Existen reglas para compilar el código de Scala en JVM-bytecode. Debido a posibles conflictos de nombres, el código generado no siempre es intuitivo, pero si se conocen las reglas, es posible acceder al código Scala compilado dentro de Java.

Atención: Al escribir esto, noté que javac y eclipse-javac se comportan de forma diferente al acceder al código Scala desde Java. Es posible que el siguiente código compile con uno de ellos, pero no con el otro.

Clases, constructores, métodos

No hay reglas especiales aquí. La siguiente clase 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 están fusionadas a una sola.

Campos, valores

Para una clase de class X(var i: Int) se crean Cazadores y Setters. Para una clase de 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

Tenga en cuenta 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 las operaciones:

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 de class X { var i = 5 } se traduce por el mismo esquema que cuando el campo se crea en el constructor. El acceso directo a la variable i desde Java no es posible porque es privado.

Objetos

No existe tal cosa como un objeto Scala en Java. Por lo tanto, Scalac tiene que hacer algo de magia. Para 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 los 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, denominada 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 caso y Getters para los campos. Se puede acceder fácilmente a la clase de case class X(i: Int) :

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

Rasgos

Un rasgo de trait T { def m } , que contiene solo miembros abstractos, se compila en una interfaz, que se coloca en un archivo de clase llamado 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, además de la interfaz normal. El rasgo

trait T { def m1 def m2 = 5 }

también se puede implementar fácilmente dentro de Java. El archivo de clase T$class.class contiene los miembros concretos del rasgo, pero parece que es imposible acceder desde Java. Ni javac ni el eclipse-javac compilarán un acceso a esta clase.

Más detalles sobre cómo se compilan los rasgos se pueden encontrar here .

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 en los archivos de clase normales, como se describe arriba. Además, cada función literal obtiene su propio archivo de clase. Por lo tanto, para los valores de función se genera un archivo de clase llamado <class_name>$$anonfun$<N>.class , donde N es un número continuo. Para los métodos de función (métodos, que devuelven una función) se genera un archivo de clase llamado <class_name>$$anonfun$<method_name>$<N>.class . Las partes del nombre de la función están separadas por signos de dólar y delante del identificador anonfun también hay dos signos de dólar. Para las funciones anidadas, el nombre de la función anidada se agrega a la función externa, 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 , se apply el nombre.

Esto significa que en nuestro caso obtenemos:

  • X$$anonfun$1.class para f
  • X$$anonfun$g$1.class para 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 i y j

Para acceder a ellos usa 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);

Responde la pregunta

Usted debe saber:

  • una clase normal de Scala puede acceder por sus constructores o sus métodos de aplicación
  • cuando no hay constructor, hay un método de aplicación
  • cuando no hay un constructor ni un método de aplicación, existe otro archivo de clase con el mismo nombre que se llama a la clase, que agrega un signo de dólar al final. Busque en esta clase un campo MODULE$
  • los constructores y los métodos de aplicación se heredan, de modo que busque en las superclases 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);

Algunos

// 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 opción y Algunas se extiende). 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, no aplica método y no extiende Opción. Por lo tanto, le 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 campo MODULE$ y el método apply de Option. 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 tanto, 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 una Seq. Pero si miramos a Seq, no hay un método de aplicación. Además, cuando miramos las scala.collection.Seq$ de Seq y en scala.collection.Seq$ , solo podemos encontrar un método de aplicación que espera una Seq. ¿Entonces lo que hay que hacer?

Tenemos que echar un vistazo cómo Scalac crea una instancia de List o Seq. Primero crea una clase 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 entradas (l 12) que se llena con 1, 2 y 3. (l 14-25). Después de eso, este conjunto se entrega a scala.Predef$.wrapIntArray (l 26). Esta 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 bonita biblioteca que envuelve el acceso a la biblioteca de Scala, será fácil usar Scala desde Java.

Resumen

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


No estoy compitiendo con la otra respuesta, pero dado que las personas a menudo no lo notan, puedes hacer esto en la respuesta.

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