10
votes

If I have the following Scala class:

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

Then I only need to define the id method in Scala to get a concrete class. But if I try to extend it in Java, the compiler says that all the concrete methods of Ordered are missing. So, does that mean that the Scala compiler is only putting the implementation of the concrete methods of Ordered in concrete Scala classes?

This seems very wasteful, because I could have dozens of concrete classes implementing MyOrdered, and they would all get a copy of the same code, when in fact it would be enough to just put it directly in the base class MyOrdered. Also, this makes it very difficult to create a Java-friendly Scala API. Is there any way to force the Scala compiler to put the method definitions where it should have done so anyway, apart from making the class concrete by using dummy method implementations?

Even funnier is declaring a concrete method final in a Scala trait. In that case, it is still not implemented in an abstract Scala class that extends the trait, but it cannot be implemented in a Java class that extends the abstract Scala class because it is flagged as final. This is definitely a compiler bug. Final abstract methods make no sense, even if they are legal in the JVM, apparently.

2
Compiler bugs should be reported to issue tracker with minimal source code example showing the problem.Grzegorz Kossakowski
Yes, but I don't report bugs lightly, so I was hoping to get some people to agree with me on this first. I might be mistaken, after all.Sebastien Diot
I'm happy to help you verify it but I don't understand the problem you are describing. Small Scala code examples + description expected behaviour would help a lot. Feel free to edit your question. When it comes to extending traits that have some methods implemented from Java the best answer would be: don't do that. It's going to be a problem sooner or later. The reason is that Java has nothing corresponding that Scala could map onto so it does various tricks that are confusing from Java point of view.Grzegorz Kossakowski
I think the problem with the final trait method is an issue from the "presentation compiler" of Eclipse rather then Scala. And anyway since extending a trait-with-code in Java is "not supported", how the "final" is treated is in essence irrelevant.Sebastien Diot

2 Answers

11
votes

Scala 2.9.1.RC1

Let me introduce you to our friend :javap in the REPL, which can be useful for diagnosing errors. First, we define the class,

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

And then ask to see the JVM bytecode,

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

 ...

We see that scalac actually generates methods in MyOrdered corresponding to those concrete ones in trait Ordered. For example, the > method gets translated to $greater and basically just calls scala/math/Ordered$class.$greater. If we like, we can now look up the bytecode for concrete trait definitions,

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
...

Finally, let's test your hypothesis that a subclass M of MyOrdered gets a full copy of all the methods

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 **
....

Nope, it looks like there's no code duplication here.

To conclude,

  • Scalac does some magic with traits with concrete methods, so don't try to inherit from them in Java. Abstract classes should be OK.

  • The JVM doesn't natively support symbolic method names, Scala singleton objects, nor traits with concrete methods, so the Scala compiler needs to do some translation, and uses the reserved symbol $.

If you're still having problems with Java interop, hopefully :javap will help you in diagnosing the specific problem.

0
votes

Note: that latest commit for Scal 2.12.x (August 2016) might optimize things a bit in compiler/scala/tools/nsc/backend/jvm:

SD-192 Change scheme for trait super accessors

Rather than putting the code of a trait method body into a static method, leave it in the default method.
The static method (needed as the target of the super calls) now uses invokespecial to exactly call that method.