如何学习使用javap从Java中使用scala.None?

时间:2012-02-19 15:34:51

标签: scala scala-java-interop javap

在上一个问题 Accessing scala.None from Java 中,人们似乎已经使用javap来确定如何从Java访问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. NoneNone.type类型的唯一对象,其扩展为Option
  2. 配对对象的apply()方法是必需的

2 个答案:

答案 0 :(得分: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
}

可以像普通的Java类一样访问。它被编译为名为X.class的文件:

X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);

请注意,对于没有参数列表的方法,会创建一个空参数列表。多个参数列表合并为一个参数列表。

字段,值

创建类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

请注意,在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 }由与在构造函数中创建字段时相同的模式进行转换。无法从Java直接访问变量i,因为它是私有的。

物件

Java中没有Scala对象。因此scalac必须做一些魔术。对于对象object X { val i = 5 },将生成两个JVM类文件:X.classX$.class。第一个工作就像一个接口,它包括访问Scala对象的字段和方法的静态方法。后者是一个单例类,无法实例化。它有一个Field,它保存类的单例实例,名为MODULE$,允许访问单例:

X.i();
X$.MODULE$.i();

案例类

Scala编译器自动为案例类生成apply-method,为字段生成Getters。可以轻松访问案例类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包含特征的具体成员,但似乎无法从Java访问它们。 javac和eclipse-javac都不会编译对这个类的访问。

有关如何编制特征的更多细节可以在here找到。

功能

函数文字被编译为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的类文件。函数名称的各部分用美元符号分隔,在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代表i和j

要访问它们,请使用他们的apply-method:

X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);

回答问题

你应该知道:

  • 正常的Scala类可以由其构造函数或其apply-methods访问
  • 当没有构造函数而不是apply-method
  • 当没有构造函数且没有apply方法时,有一个另一个类文件的命名方式与调用类相同,后者在末尾附加一个美元符号。在此课程中搜索MODULE$字段
  • 继承构造函数和apply-methods,因此如果在子类中找不到任何内容,请搜索超类

一些例子

方法

// 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方法。此外,它说这个课程是抽象的。因此,只能使用apply-method:

Option.apply(3);

部分

// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
  ...
  public scala.Some(java.lang.Object);
}

它有一个构造函数和一个apply-method(因为我们知道Option有一个,Some扩展了Option)。使用其中一个并开心:

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$();
}

呀!我们找到了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

列表

scala.collection.immutable.List是抽象的,因此我们必须使用scala.collection.immutable.List$。它有一个需要scala.collection.Seq的apply方法。所以要获得一个List,我们首先需要一个Seq。但是如果我们看看Seq,就没有应用方法。此外,当我们查看Seq的超类时,在scala.collection.Seq$我们只能找到一个期望Seq的apply-methods。那么,该怎么办?

我们必须看看scalac如何创建List或Seq的实例。首先创建一个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

}

构造函数很有趣。它告诉我们,创建了一个int数组(l.12),其中填充了1,2和3.(l.14-25)。之后,此数组将传递到scala.Predef$.wrapIntArray(第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 }));

这看起来很难看,但确实有效。如果你创建一个漂亮的库来包装对Scala库的访问,那么从Java中使用Scala就很容易了。

摘要

我知道还有一些规则如何将Scala代码编译为字节码。但我认为根据上述信息,应该可以自己找到这些规则。

答案 1 :(得分: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