如何为JVM目标编译扩展类的scala特性?

问题描述

我从this question得知Scala为trait生成了诸如

trait A {
  def a = { ... }
}

类似于以下Java代码的结构

public interface A {
  public void a();
}
public class A$class {
  public static void a(A self) { ... }
}

但是,在Scala中,trait可以扩展class

class B {
  def b = { ??? }
}
trait A extends B {
  def a = { ??? }
}

在接口不能从类继承的Java等效语言中,如何翻译? 是否为B生成了其他接口? Scala功能对Java互操作性有影响吗?

解决方法

好吧,你可以编译

class B {
  def b = { ??? }
}
trait A extends B {
  def a = { ??? }
}

并检查字节码:

> javap -c -l -p B
Compiled from "B.scala"
public class B {
  public scala.runtime.Nothing$ b();
    Code:
       0: getstatic     #16                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: invokevirtual #19                 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
       6: areturn
    LineNumberTable:
      line 2: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0  this   LB;

  public B();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 4: 0
      line 1: 4
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LB;
}
> javap -c -l -p A
Compiled from "B.scala"
public interface A {
  public static scala.runtime.Nothing$ a$(A);
    Code:
       0: aload_0
       1: invokespecial #15                 // InterfaceMethod a:()Lscala/runtime/Nothing$;
       4: areturn
    LineNumberTable:
      line 5: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0 $this   LA;

  public scala.runtime.Nothing$ a();
    Code:
       0: getstatic     #22                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: invokevirtual #25                 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
       6: areturn
    LineNumberTable:
      line 5: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0  this   LA;

  public static void $init$(A);
    Code:
       0: return
    LineNumberTable:
      line 4: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0 $this   LA;
}

您看到scalac使得A只是具有默认方法的接口(自Java 8开始可用)。您可能想知道,如果您想在B中调用某个A方法会发生什么,因为在字节码中看不到A extends B

trait A extends B {
  def a = { b; ??? }
}

现在A更改为:

> javap -c -l -p A
Compiled from "B.scala"
public interface A {
  public static scala.runtime.Nothing$ a$(A);
    Code:
       0: aload_0
       1: invokespecial #15                 // InterfaceMethod a:()Lscala/runtime/Nothing$;
       4: areturn
    LineNumberTable:
      line 5: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0 $this   LA;

  public scala.runtime.Nothing$ a();
    Code:
       0: aload_0
       1: checkcast     #18                 // class B
       4: invokevirtual #21                 // Method B.b:()Lscala/runtime/Nothing$;
       7: athrow
    LineNumberTable:
      line 5: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       8     0  this   LA;

  public static void $init$(A);
    Code:
       0: return
    LineNumberTable:
      line 4: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0 $this   LA;
}

我们可以看到,代码使用checkcastthis强制转换为B,然后调用'B的方法-这意味着scalac必须确保然后您实例化A,它将扩展B!因此,让我们检查一下这样做会发生什么:

class B {
  def b = { ??? }
}
trait A extends B {
  def a = { b; ??? }
}
class C extends A

C是

> javap -c -l -p C
Compiled from "B.scala"
public class C extends B implements A {
  public scala.runtime.Nothing$ a();
    Code:
       0: aload_0
       1: invokestatic  #16                 // InterfaceMethod A.a$:(LA;)Lscala/runtime/Nothing$;
       4: areturn
    LineNumberTable:
      line 7: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LC;

  public C();
    Code:
       0: aload_0
       1: invokespecial #22                 // Method B."<init>":()V
       4: aload_0
       5: invokestatic  #26                 // InterfaceMethod A.$init$:(LA;)V
       8: return
    LineNumberTable:
      line 7: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       9     0  this   LC;
}

正如您所看到的,这与上下文如何以字节码结尾是非常相关的-编译器将弄乱一切以使其最终适应,但前提是它知道您将如何使用它。

因此,如果要与Java互操作,请坚持使用简单的traitclass作为通用接口,并让Scala实例化更复杂的实现。用Java自己做这件事可能会以许多意想不到的方式咬你。