Comment apprendre à utiliser scala.None de Java en utilisant javap?
-
27-10-2019 - |
Question
Dans une question précédente, Accès scala.None de Java , il semble que les gens avaient utilisé javap
pour savoir comment scala.None
d'accès de Java. Je voudrais savoir comment ils ont fait cela. Pour votre information, la réponse est:
scala.Option$.MODULE$.apply(null);
qui peut être court-circuité à:
scala.Option.apply(null);
Compte tenu de ce programme (OptionTest.scala
):
object OptionTest extends App {
val x = scala.None
val y = scala.Some("asdf")
}
J'ai couru sur javap
comme ceci:
javap -s -c -l -private OptionTest
Ceci est une partie de la sortie 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
J'ai aussi couru javap sur scala.None
et scala.Option
. Comment serait un chiffre à partir de la sortie javap
que:
-
None
est un seul objet de typeNone.type
qui se prolongeOption
- La méthode
apply()
pour l'objet compagnon est requis
La solution
Il existe des règles comment le code Scala est compilé à JVM-bytecode. En raison du nom potentiel heurte le code généré est pas toujours intuitive à comprendre, mais si les règles sont connues, il est possible d'avoir accès au code Scala compilé au sein de Java.
Attention: Tout en écrivant, je remarque que javac et eclipse-javac se comportent différemment dans l'accès du code Scala de Java. Il est possible que le code ci-dessous avec l'un de la compilation mais pas avec eux l'autre.
Classes, Constructors, Méthodes
Il n'y a pas de règles particulières ici. Ce qui suit la classe 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
}
est accessible comme une classe Java normale. Il est compilé dans un fichier nommé X.class
:
X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);
Avis, que pour les méthodes sans parameterList un parameterList vide est créé. parameterlists multiples sont fusionnés à un seul.
Les champs, les valeurs
Pour un getters class X(var i: Int)
de classe et Setters sont créés. Pour une class X(val i: Int)
classe seulement Getter est créé:
//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
Notez que dans Java un identifiant ne peut pas inclure des signes spéciaux. Par conséquent scalac génère pour chacun de ces signes spéciaux un nom spécifique. Il y a une classe scala.reflect.NameTransformer qui peut coder / décoder les opérations:
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)
Une classe class X { var i = 5 }
est traduit par le même schéma que lorsque le champ est créé dans le constructeur. Accès direct à la i
variable de Java n'est pas possible, car il est privé.
Objets
Il n'y a pas une telle chose comme un objet Scala en Java. Par conséquent, scalac doit faire de la magie. Pour un object X { val i = 5 }
objet deux fichiers de classe JVM sont générés: X.class
et X$.class
. Le premier fonctionne comme une interface, elle comprend des procédés statiques à des champs d'accès et les méthodes de l'objet Scala. Ce dernier est une classe singleton qui ne peut pas être instancié. Il a un champ qui contient l'instance singleton de la classe, MODULE$
nommé, qui permet d'accéder au singleton:
X.i();
X$.MODULE$.i();
Classes de cas
Le compilateur Scala génère automatiquement une méthode pour appliquer une classe de cas et Getters pour les champs. La classe de cas case class X(i: Int)
est facilement accessible:
new X(3).i();
X$.MODULE$.apply(3);
caractères
Un trait trait T { def m }
, qui ne contient que des membres abstraits, est compilé à une interface, qui est placé dans un fichier de classe nommé T.class
. Par conséquent, il peut facilement mis en œuvre par une classe Java:
class X implements T {
public void m() {
// do stuff here
}
}
Si le trait contient des éléments en béton il y a un fichier de classe nommé <trait_name>$class.class
généré, en plus de l'interface normale. Le trait
trait T {
def m1
def m2 = 5
}
peut également facilement mis en œuvre dans Java. Le fichier de classe T$class.class
contient les éléments en béton du trait, mais il semble qu'ils sont impossibles d'accès de Java. Ni javac ni l'éclipse-javac compileront un accès à cette classe.
Un peu plus de détails sur la façon dont les traits sont compilés sont disponibles .
Fonctions
littéraux de fonction sont compilés comme des instances anonymes des classes fonction personnaliséeN. Un objet 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
}
}
est compilé les fichiers de classe normale, comme décrit ci-dessus. En outre, chaque fonction obtient littéral son propre fichier de classe. Ainsi, pour les valeurs de la fonction d'un fichier de classe nommée <class_name>$$anonfun$<N>.class
est générée, où N est un nombre continu. Pour les méthodes de fonction (méthodes, qui renvoient une fonction) un fichier de classe nommé <class_name>$$anonfun$<method_name>$<N>.class
est généré. Les parties du nom de la fonction sont séparés par des signes de dollar et en face de l'identificateur de anonfun
il y a aussi deux signes dollar. Pourfonctions imbriquées le nom de la fonction imbriquée est ajoutée à la fonction externe, cela signifie une fonction intérieure obtiendra un fichier de classe comme <class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class
. Lorsqu'une fonction intérieure ne porte pas de nom, comme on le voit dans h
il obtient le nom apply
.
Ce moyen dans notre cas nous obtenons:
-
X$$anonfun$1.class
pour f -
X$$anonfun$g$1.class
pour g -
X$$anonfun$h$1$$anonfun$apply$1.class
pour h -
X$$anonfun$i$1.class
etX$$anonfun$i$1$$anonfun$j$1$1.class
pour i et j
Pour y accéder utiliser leur appliquent méthode:
X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);
Répondez à la question
Vous devez savoir:
- une classe normale Scala peut accéder par leurs constructeurs ou leurs méthodes apply-
- quand il n'y a pas de constructeur que il y a une méthode applicable
- quand il n'y a pas de constructeur et aucune méthode apply que il y a un autre fichier de classe nommé de la même manière la classe est appelée, qui ajoute un signe de dollar à la fin. Rechercher dans cette classe pour un champ
MODULE$
- les constructeurs et apply-méthodes sont héritées, recherche donc les super-classes si vous ne trouvez rien dans les sous-classes
Quelques exemples
Option
// 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 dit qu'il a un constructeur et une méthode apply. En outre, il dit que la classe est abstraite. Ainsi, seule la méthode apply peut utiliser:
Option.apply(3);
Certains
// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
...
public scala.Some(java.lang.Object);
}
Il a un constructeur et une méthode apply-(parce que nous savons Option a un et certains étend en option). Utilisez l'un d'entre eux et être heureux:
new Some<Integer>(3);
Some.apply(3);
Aucun
// javap scala.None
public final class scala.None extends java.lang.Object{
...
}
Il n'a pas de constructeur, pas applicable méthode et ne couvre pas l'option. Donc, nous allons jeter un coup d'oeil à None$
:
// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
...
public static final scala.None$ MODULE$;
private scala.None$();
}
Ouais! Nous avons trouvé un champ MODULE$
et appliquer méthode de l'option. De plus, nous avons trouvé le constructeur privé:
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
Liste
scala.collection.immutable.List
est abstraite, nous avons donc à utiliser scala.collection.immutable.List$
. Il a une méthode applicable qui attend un scala.collection.Seq
. Donc, pour obtenir une liste il faut d'abord une Seq. Mais si nous regardons Seq il n'y a pas apply-méthode. De plus, lorsque nous regardons les super-classes de Seq et à scala.collection.Seq$
nous ne pouvons trouver une apply-méthodes qui attend une Seq. Alors, quoi faire?
Nous devons regarder comment scalac crée une instance de liste ou Seq. Tout d'abord créer une classe Scala:
class X {
val xs = List(1, 2, 3)
}
Compile avec scalac et regardez le fichier de classe avec 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
}
Le constructeur est intéressant. Il nous apprend que un tableau d'entiers est créé (l. 12) qui est rempli avec 1, 2 et 3. (l. 14-25). Après que cet ensemble est livré à scala.Predef$.wrapIntArray
(l. 26). Ce scala.collection.mutable.WrappedArray
résultant est livré de nouveau à notre liste (l. 29). A la fin, la liste est mémorisée dans le champ (l. 32).
Lorsque nous voulons créer une liste en Java, nous devons faire la même chose:
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 }));
Cela semble laid, mais ça fonctionne. Si vous créez une belle bibliothèque à la recherche qui enveloppe l'accès à la bibliothèque Scala, il sera facile à utiliser Scala de Java.
Résumé
Je sais qu'il ya des règles plus comment le code Scala est compilé en bytecode. Mais je pense que les informations ci-dessus, il devrait être possible de trouver ces règles par vous-même.
Autres conseils
Je ne suis pas en concurrence avec l'autre réponse, mais puisque les gens semblent souvent ne pas remarquer cela, vous pouvez le faire dans le rempl.
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