Scala traits that extend classes are compiled for a JVM target如何编译?

huangapple go评论135阅读模式
英文:

How are scala traits that extend classes compiled for a JVM target?

问题

我从这个问题中了解到,对于像下面这样的trait,Scala会生成以下类似于Java代码的结构:

trait A {
  def a = { ... }
}
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互操作性产生任何影响?

英文:

I know from this question that Scala generates for a trait such as

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

a structure that would look similar to the following Java code

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

However, in Scala it is possible for a trait to extend a class:

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

How is this translated in a Java equivalent, where interfaces cannot inherit from classes?
Is an additional interface generated for B?
Does this Scala feature have any impact on Java interoperability?

答案1

得分: 6

好的,以下是翻译好的部分:

你可以只是编译以下代码
class B {
  def b = { ??? }
}
trait A extends B {
  def a = { ??? }
}

并检查字节码:

> javap -c -l -p B
从“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
...

> javap -c -l -p A
从“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
...

正如你所看到的,`scalac` 使 `A` 只是一个带有默认方法的接口(自 Java 8 开始可用)。你可能会想知道,如果你想在 `A` 中调用一些 `B` 的方法,因为我们在字节码中没有看到 `A extends B`:

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

现在 `A` 发生了改变:

> javap -c -l -p A
从“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
...

我们可以看到该代码使用 `checkcast` 进行将 `this` 强制转换为 `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
从“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
...

正如您所看到的,这在很大程度上取决于上下文,字节码的最终结果 - 编译器会在最后处理好事情,但仅当它知道您如何使用它。

因此,如果您希望与 Java 进行互操作性,请坚持使用简单的 `trait` 和 `class` 作为共同的接口,让 Scala 实例化更复杂的实现。从 Java 中自己实现这一点可能会在许多意想不到的情况下对您产生影响。
英文:

Well, you can just compile

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

and check the bytecode:

> 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;
}

As you see scalac make A just an interface with default methods (they are available since Java 8). You might wonder, what would happen if you wanted to call some B method in A since we don't see that A extends B in bytecode:

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

now A changed to:

> 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;
}

As we can see the code uses checkcasts to cast this to B and then calls 'B's method - this means that scalac will have to make sure that then you instantiate A it will be something that also extends B! So let's check what will happen when we do this:

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

and the C is

> 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;
}

As you can see this is very context-dependent how things end up with bytecode - the compiler will juggle things around to make it fit things in the end, but only if it will know how you use it.

Therefore if you want interoperability with Java, stick to simple traits and classes as your common interface, and let Scala instantiate more complex implementations. Doing this yourself from Java might bite you in many unexpected ways.

huangapple
  • 本文由 发表于 2020年8月27日 18:15:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/63613833.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定