如何学习有关使用scala的信息。
在前一个问题中, 从Java访问scala.None ,似乎人们已经使用javap
来弄清楚如何访问scala.None
。 我想知道他们是怎么做到的。 仅供参考,答案是:
scala.Option$.MODULE$.apply(null);
可以缩短为:
scala.Option.apply(null);
鉴于这个程序( OptionTest.scala
):
object OptionTest extends App { val x = scala.None val y = scala.Some("asdf") }
我运行javap
就像这样:
javap -s -c -l -private OptionTest
这是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
我也在scala.None
和scala.Option
上运行javap。 如何从javap
输出中找出:
-
None
是扩展Option
的None.type
types的唯一对象 - 伴随对象的
apply()
方法是必需的
?
有一些规则如何将Scala代码编译为JVM字节码。 由于潜在的名称冲突,生成的代码并不总是直观易懂,但是如果规则已知,则可以访问Java中已编译的Scala代码。
注意:在写这篇文章的时候,我注意到javac和eclipse-javac在从Java访问Scala代码时performance不同。 下面的代码可能与其中一个编译,但不与另一个编译。
类,构造函数,方法
这里没有特别的规定。 以下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 }
可以像普通的Java类一样访问。 它被编译成一个名为X.class
的文件:
X x = new X(7); x.m1(); x.m2(3, 5); x.m3(3, 5);
注意,对于没有参数列表的方法,创build一个空的参数列表。 多个参数列表被合并为一个。
字段,值
对于类class X(var i: Int)
,将创buildGetters和Setters。 对于类class X(val i: Int)
只创build一个Getter:
//Scala val x = new X(5) xi = 3 // Setter xi // Getter //Java X x = new X(5); x.i_$eq(3); // Setter xi(); // Getter
请注意,在Java中,标识符不允许包含特殊符号。 因此,scalac为每个特殊符号生成一个特定的名字。 有一个类scala.reflect.NameTransformer可以编码/解码操作:
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)
一个类class X { var i = 5 }
与构造函数中创build该字段时的架构相同。 从Java直接访问variablesi
是不可能的,因为它是私有的。
对象
Java中没有像Scala对象那样的东西。 所以Scalac必须要做一些魔术。 对于对象object X { val i = 5 }
将生成两个JVM类文件: X.class
和X$.class
。 第一个工作就像一个接口,它包括静态方法来访问Scala对象的字段和方法。 后者是一个单例类,不能被实例化。 它有一个字段,它包含了类的单例实例,名为MODULE$
,它允许访问单例:
Xi(); X$.MODULE$.i();
案例类
Scala编译器会自动为一个case类和Getters字段生成一个apply-method。 案例类case class X(i: Int)
很容易被访问:
new X(3).i(); X$.MODULE$.apply(3);
性状
一个只包含抽象成员的特征trait T { def m }
被编译到一个接口中,该接口放置在一个名为T.class
的类文件中。 因此,它可以很容易地由一个Java类实现:
class X implements T { public void m() { // do stuff here } }
如果特征包含具体成员,则除正常接口外,还有一个名为<trait_name>$class.class
的类文件。 特质
trait T { def m1 def m2 = 5 }
也可以在Java中轻松实现。 类文件T$class.class
包含trait的具体成员,但似乎无法从Java访问。 javac和eclipse-javac都不会编译对这个类的访问。
关于如何编译特征的更多细节可以在这里find。
function
函数文字被编译为类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 } }
被编译为正常的类文件,如上所述。 而且每个函数文字都有它自己的类文件。 因此,为函数值生成一个名为<class_name>$$anonfun$<N>.class
的类文件,其中N是一个连续的数字。 对于函数方法(返回函数的方法),会生成一个名为<class_name>$$anonfun$<method_name>$<N>.class
的类文件。 function名称的部分用美元符号分隔,在anonfun
标识符前面还有两个美元符号。 对于嵌套函数,将嵌套函数的名称附加到外部函数中,这意味着内部函数将获得类文件,如<class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class
。 当一个内部函数没有名字时,如h
它得到名称apply
。
这意味着我们得到:
-
X$$anonfun$1.class
for f -
X$$anonfun$g$1.class
for g -
X$$anonfun$h$1$$anonfun$apply$1.class
for h -
X$$anonfun$i$1.class
和X$$anonfun$i$1$$anonfun$j$1$1.class
for i and j
要访问他们使用他们的申请方法:
Xf().apply(7); Xg().apply(7); Xh().apply(3).apply(5); Xi().apply(3).apply(5);
回答问题
你应该知道:
- 一个普通的Scala类可以通过它们的构造函数或者apply方法来访问
- 当没有构造函数的时候就有一个apply-method
- 当没有构造函数和没有应用方法时,就会有另外一个以类似方式命名的类文件,这个类会在最后附加一个美元符号。 在这个类中search一个
MODULE$
字段 - 构造函数和apply-methods都是inheritance的,所以如果在子类中找不到任何东西的话,search超类
一些例子
选项
// 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说它有一个构造函数和一个apply方法。 而且它说class级是抽象的。 因此只能使用apply-method:
Option.apply(3);
一些
// javap scala.Some public final class scala.Some extends scala.Option implements ... { ... public scala.Some(java.lang.Object); }
它有一个构造函数和一个apply方法(因为我们知道Option有一个而且有一些extends的选项)。 使用其中之一,并开心:
new Some<Integer>(3); Some.apply(3);
没有
// javap scala.None public final class scala.None extends java.lang.Object{ ... }
它没有构造函数,没有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$(); }
是啊! 我们find了一个MODULE$
字段和Option的应用方法。 而且我们发现了私有构造函数:
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
名单
scala.collection.immutable.List
是抽象的,因此我们必须使用scala.collection.immutable.List$
。 它有一个应用程序的方法,期望scala.collection.Seq
。 所以要得到一个List,我们首先需要一个Seq。 但是如果我们看Seq,就没有应用方法。 而且,当我们查看Seq的超类,并在scala.collection.Seq$
我们只能find一个期望Seq的apply-methods。 那么该怎么办?
我们必须看看scalac如何创build一个List或Seq的实例。 首先创build一个Scala类:
class X { val xs = List(1, 2, 3) }
用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 }
构造函数很有趣。 它告诉我们,创build了一个整数(l.12),其中填充了1,2和3(l.14-25)。 之后,这个数组被传递给scala.Predef$.wrapIntArray
( scala.Predef$.wrapIntArray
)。 这导致scala.collection.mutable.WrappedArray
再次被传送到我们的列表( scala.collection.mutable.WrappedArray
)。 最后,列表存储在字段(1.32)中。 当我们想要在Java中创build一个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 }));
这看起来很丑,但它的工作。 如果你创build一个很好看的库来包装对Scala库的访问,那么使用Java的Scala将会很容易。
概要
我知道还有更多的规则是如何将Scala代码编译为字节码的。 但是我认为用上面的信息应该可以自己find这些规则。
我不会与其他答案竞争,但由于人们似乎经常不注意到这一点,你可以在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