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扩展它,那么编译器会说缺少所有具体的Orderered方法.那么这是否意味着Scala编译器只是在具体的Scala类中执行具体的具体方法?
这似乎是非常浪费的,因为我可以有几十个实现MyOrdered的具体类,并且它们都将获得相同代码的副本,实际上只需将它直接放在基类MyOrdered中即可.此外,这使得很难创建一个Java友好的Scala API.有没有办法强制Scala编译器将方法定义放在应该这样做的地方,除了使用虚拟方法实现使类具体化?
即使有趣的是在Scala特征中声明具体的方法最终.在这种情况下,它仍然不能在扩展trait的抽象Scala类中实现,但它不能在扩展抽象Scala类的Java类中实现,因为它被标记为final.这绝对是一个编译器bug.显然,最终的抽象方法没有任何意义,即使它们在JVM中是合法的.
解决方法
让我向您介绍一下我们的朋友: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,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中生成的,与trait Ordered中具体的方法相对应.例如,>方法被转换为$更大,基本上只调用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,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 "<console>" public class M extends MyOrdered implements scala.ScalaObject .... ** No extra methods besides id ** ....
不,看来这里没有代码重复.
总而言之,
> Scalac使用具体的方法做一些具有特征的魔法,所以不要试图用Java继承它们.抽象类应该可以.
> JVM本身并不支持符号方法名称,Scala单例对象,也不具有具体方法的特征,因此Scala编译器需要进行一些翻译,并使用保留符号$.
如果您仍然遇到Java互操作问题,希望可以:javap将帮助您诊断具体问题.