Mik*_*inn 33 scala javap scala-java-interop
在上一个问题中,从Java访问scala.None,似乎人们已经习惯javap了解如何scala.None从Java 访问.我想知道他们是怎么做到的.仅供参考,答案是:
scala.Option$.MODULE$.apply(null);
Run Code Online (Sandbox Code Playgroud)
可以缩短为:
scala.Option.apply(null);
Run Code Online (Sandbox Code Playgroud)
鉴于此计划(OptionTest.scala):
object OptionTest extends App {
val x = scala.None
val y = scala.Some("asdf")
}
Run Code Online (Sandbox Code Playgroud)
我这样跑javap了:
javap -s -c -l -private OptionTest
Run Code Online (Sandbox Code Playgroud)
这是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
Run Code Online (Sandbox Code Playgroud)
我也javap的上运行scala.None和scala.Option.如何从javap输出中找出:
None是唯一None.type延伸的类型的对象Optionapply()伴侣对象的方法是必需的?
kir*_*uku 71
Scala代码是如何编译为JVM字节码的.由于潜在的名称冲突,生成的代码并不总是直观易懂,但如果规则已知,则可以在Java中访问已编译的Scala代码.
注意:在编写本文时,我注意到javac和eclipse-javac在从Java访问Scala代码时表现不同.下面的代码可能会使用其中一个编译但不能与另一个编译.
这里没有特殊规定.以下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
}
Run Code Online (Sandbox Code Playgroud)
可以像普通的Java类一样访问.它被编译为一个名为的文件X.class:
X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);
Run Code Online (Sandbox Code Playgroud)
请注意,对于没有参数列表的方法,会创建一个空参数列表.多个参数列表合并为一个参数列表.
对于类class X(var i: Int)Getters和Setter的创建.对于一个类,class X(val i: Int)只创建一个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
Run Code Online (Sandbox Code Playgroud)
请注意,在Java中,标识符不允许包含特殊符号.因此,scalac为每个特殊标志生成一个特定的名称.有一个scala.reflect.NameTransformer类可以对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)
Run Code Online (Sandbox Code Playgroud)
类class X { var i = 5 }通过与在构造函数中创建字段时相同的模式进行转换.无法i从Java 直接访问变量,因为它是私有的.
Java中没有Scala对象.因此scalac必须做一些魔术.对于一个对象object X { val i = 5 },生成两个JVM类文件:X.class和X$.class.第一个工作就像一个接口,它包括访问Scala对象的字段和方法的静态方法.后者是一个单例类,无法实例化.它有一个Field,它保存类的单例实例,名为MODULE$,允许访问单例:
X.i();
X$.MODULE$.i();
Run Code Online (Sandbox Code Playgroud)
Scala编译器自动为案例类生成apply-method,为字段生成Getters.案例类case class X(i: Int)很容易访问:
new X(3).i();
X$.MODULE$.apply(3);
Run Code Online (Sandbox Code Playgroud)
trait T { def m }只包含抽象成员的trait 被编译为一个接口,该接口放在一个名为的类文件中T.class.因此,它可以通过Java类轻松实现:
class X implements T {
public void m() {
// do stuff here
}
}
Run Code Online (Sandbox Code Playgroud)
如果特征包含具体成员,则<trait_name>$class.class除了普通接口外,还有一个名为generated 的类文件.特质
trait T {
def m1
def m2 = 5
}
Run Code Online (Sandbox Code Playgroud)
也可以在Java中轻松实现.类文件T$class.class包含特征的具体成员,但似乎无法从Java访问它们.javac和eclipse-javac都不会编译对这个类的访问.
关于如何编译特征的更多细节可以在这里找到.
函数文字被编译为FunctionN类的匿名实例.一个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
}
}
Run Code Online (Sandbox Code Playgroud)
如上所述,编译为正常的类文件.此外,每个函数文字都有自己的类文件.因此,对于函数值,<class_name>$$anonfun$<N>.class生成名为的类文件,其中N是连续数.对于函数方法(返回函数的方法),<class_name>$$anonfun$<method_name>$<N>.class生成一个名为的类文件.函数名称的各部分用美元符号分隔,在anonfun标识符前面还有两个美元符号.对于嵌套函数,嵌套函数的名称将附加到外部函数,这意味着内部函数将获得类文件<class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class.当内部函数没有名称时,如中所示h得到名称apply.
这意味着我们得到:
X$$anonfun$1.class 对于fX$$anonfun$g$1.class 为了gX$$anonfun$h$1$$anonfun$apply$1.class 为了hX$$anonfun$i$1.class而X$$anonfun$i$1$$anonfun$j$1$1.class对于i和j要访问它们,请使用他们的apply-method:
X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);
Run Code Online (Sandbox Code Playgroud)
你应该知道:
MODULE$字段// 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();
}
Run Code Online (Sandbox Code Playgroud)
javap说它有一个构造函数和一个apply方法.此外,它说这个课程是抽象的.因此,只能使用apply-method:
Option.apply(3);
Run Code Online (Sandbox Code Playgroud)
// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
...
public scala.Some(java.lang.Object);
}
Run Code Online (Sandbox Code Playgroud)
它有一个构造函数和一个apply-method(因为我们知道Option有一个,Some扩展了Option).使用其中一个并开心:
new Some<Integer>(3);
Some.apply(3);
Run Code Online (Sandbox Code Playgroud)
// javap scala.None
public final class scala.None extends java.lang.Object{
...
}
Run Code Online (Sandbox Code Playgroud)
它没有构造函数,没有apply-method,也没有扩展Option.所以,我们来看看None$:
// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
...
public static final scala.None$ MODULE$;
private scala.None$();
}
Run Code Online (Sandbox Code Playgroud)
是啊!我们找到了一个MODULE$字段和Option的apply-method.此外,我们找到了私有构造函数:
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
Run Code Online (Sandbox Code Playgroud)
scala.collection.immutable.List是抽象的,因此我们必须使用scala.collection.immutable.List$.它有一个应用方法,期望一个scala.collection.Seq.所以要获得一个List我们首先需要一个Seq.但是如果我们看看Seq,就没有应用方法.此外,当我们查看Seq的超类时,scala.collection.Seq$我们只能找到一个需要Seq的apply-methods.那么该怎么办?
我们必须看看scalac如何创建List或Seq的实例.首先创建一个Scala类:
class X {
val xs = List(1, 2, 3)
}
Run Code Online (Sandbox Code Playgroud)
使用scalac编译它并使用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
}
Run Code Online (Sandbox Code Playgroud)
构造函数很有趣.它告诉我们,创建了一个int数组(l.12),其中填充了1,2和3.(l.14-25).之后,该阵列被传递到scala.Predef$.wrapIntArray(l.26).结果scala.collection.mutable.WrappedArray再次发送到我们的列表(l.29).最后,List存储在字段中(1.32).当我们想在Java中创建List时,我们必须这样做:
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 }));
Run Code Online (Sandbox Code Playgroud)
这看起来很难看,但它确实有效.如果您创建一个外观漂亮的库来包装对Scala库的访问,那么从Java中使用Scala将很容易.
我知道有一些规则如何将Scala代码编译为字节码.但我认为有了上述信息,应该可以自己找到这些规则.
psp*_*psp 15
我没有与其他答案竞争,但由于人们似乎经常不注意这一点,你可以在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
Run Code Online (Sandbox Code Playgroud)