如何学习有关使用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.Nonescala.Option上运行javap。 如何从javap输出中找出:

  1. None是扩展OptionNone.typetypes的唯一对象
  2. 伴随对象的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.classX$.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.classX$$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$.wrapIntArrayscala.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