如果我有以下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中是合法的,显然也是如此。
答案 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
来精确调用该方法。