Scala编译器如何处理具体的特征方法?

时间:2011-08-14 13:44:20

标签: java scala traits

如果我有以下Scala类:

abstract class MyOrdered extends Ordered[MyOrdered] {
    def id: Int
    def compare(that : MyOrdered) : Int =
        if (that==null) 1 else (id-that.id)
}

然后我只需要在Scala中定义id方法来获得具体的类。但是如果我尝试在 Java 中扩展它,编译器会说Ordered的所有具体方法都缺失了。那么,这是否意味着Scala编译器只在具体的Scala类中实现了Ordered的具体方法?

这看起来非常浪费,因为我可以有几十个实现MyOrdered的具体类,并且它们都会得到相同代码的副本,实际上将它直接放在基类MyOrdered中就足够了。此外,这使得创建Java友好的Scala API变得非常困难。有没有办法强制Scala编译器将方法定义放在应该这样做的地方,除了通过使用虚方法实现使类具体化?

甚至更有趣的是在Scala特征中声明一个具体的方法final。在这种情况下,仍然没有在扩展特征的抽象Scala类中实现,但是它不能在扩展抽象Scala类的Java类中实现,因为它被标记为final。这绝对是一个编译器错误。最终的抽象方法毫无意义,即使它们在JVM中是合法的,显然也是如此。

2 个答案:

答案 0 :(得分:11)

Scala 2.9.1.RC1

让我向您介绍REPL中的朋友:javap,它可用于诊断错误。首先,我们定义类

scala> abstract class MyOrdered extends Ordered[MyOrdered] {
     |     def id: Int
     |     def compare(that : MyOrdered) : Int =
     |         if (that==null) 1 else (id-that.id)
     | }
defined class MyOrdered

然后要求查看JVM字节码,

scala> :javap -v MyOrdered
Compiled from "<console>"
public abstract class MyOrdered extends java.lang.Object implements scala.math.Ordered,scala.ScalaObject

...
** I'm skipping lots of things here: $less, $lessEq, ... **
...

public boolean $greater(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokestatic    #19; //Method scala/math/Ordered$class.$greater:(Lscala/math/Ordered;Ljava/lang/Object;)Z
   5:   ireturn
  LineNumberTable: 
   line 7: 0

...

public abstract int id();

public int compare(MyOrdered);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_1
   1:   ifnonnull   8
   4:   iconst_1
   5:   goto    17
   8:   aload_0
   9:   invokevirtual   #38; //Method id:()I
   12:  aload_1
   13:  invokevirtual   #38; //Method id:()I
   16:  isub
   17:  ireturn
  LineNumberTable: 
   line 10: 0

 ...

我们看到scalac实际上MyOrdered中生成方法,与特征Ordered中的具体方法相对应。例如,>方法转换为$greater,基本上只调用scala/math/Ordered$class.$greater。如果我们愿意,我们现在可以查找具体特征定义的字节码,

scala> :javap -v scala.math.Ordered$class
Compiled from "Ordered.scala"
public abstract class scala.math.Ordered$class extends java.lang.Object
...
public static boolean $greater(scala.math.Ordered, java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   invokeinterface #12,  2; //InterfaceMethod scala/math/Ordered.compare:(Ljava/lang/Object;)I
   7:   iconst_0
   8:   if_icmple   15
   11:  iconst_1
   12:  goto    16
   15:  iconst_0
   16:  ireturn
  LineNumberTable: 
   line 46: 0
...

最后,让我们测试你的假设,即M的子类MyOrdered获得所有方法的完整副本

scala> class M extends MyOrdered { def id = 2 }
defined class M

scala> :javap -v M
Compiled from "<console>"
public class M extends MyOrdered implements scala.ScalaObject
....
** No extra methods besides id **
....

不,看起来这里没有代码重复。

总结,

  • Scalac使用具体方法对特征进行了一些魔术,因此不要尝试在Java中继承它们。抽象类应该没问题。

  • JVM本身不支持符号方法名称,Scala单例对象,也不支持具体方法的特征,因此Scala编译器需要进行一些转换,并使用保留符号$。

如果您仍然遇到Java互操作问题,希望:javap可以帮助您诊断特定问题。

答案 1 :(得分:0)

注意:latest commit for Scal 2.12.x(2016年8月)可能会优化compiler/scala/tools/nsc/backend/jvm中的内容:

  

SD-192特征超级访问者的改变方案

     

不是将特征方法体的代码放入静态方法,   保留默认方法。
  静态方法(需要作为超级调用的目标)现在使用invokespecial来精确调用该方法。