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

如果我有以下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中是合法的,显然也是如此。

Scala 2.9.1.RC1

让我向您介绍我们的朋友:javap 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 "" 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 ... 

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

 scala> class M extends MyOrdered { def id = 2 } defined class M scala> :javap -v M Compiled from "" public class M extends MyOrdered implements scala.ScalaObject .... ** No extra methods besides id ** .... 

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

总之,

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

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

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

注意: Scal 2.12.x (2016年8月)的最新提交可能会在compiler/scala/tools/nsc/backend/jvm稍微优化一下:

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

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