¿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:
-
None
es un único objeto de tipoNone.type
que se extiendeOption
- 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
yX$$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