Using Scala Traits with Implemented Methods in Java

Using Scala traits with implemented methods in Java

Answer

From Java perspective Trait.scala is compiled into Trait interface. Hence implementing Trait in Java is interpreted as implementing an interface - which makes your error messages obvious. Short answer: you can't take advantage of trait implementations in Java, because this would enable multiple inheritance in Java (!)

How is it implemented in Scala?

Long answer: so how does it work in Scala? Looking at the generated bytecode/classes one can find the following code:

interface Trait {
void bar();
}

abstract class Trait$class {
public static void bar(Trait thiz) {/*trait implementation*/}
}

class Foo implements Trait {
public void bar() {
Trait$class.bar(this); //works because `this` implements Trait
}
}
  • Trait is an interface
  • abstract Trait$class (do not confuse with Trait.class) class is created transparently, which technically does not implement Trait interface. However it does have a static bar() method taking Trait instance as argument (sort of this)
  • Foo implements Trait interface
  • scalac automatically implements Trait methods by delegating to Trait$class. This essentially means calling Trait$class.bar(this).

Note that Trait$class is neither a member of Foo, nor does Foo extend it. It simply delegates to it by passing this.

Mixing in multiple traits

To continue the digression on how Scala works... That being said it is easy to imagine how mixing in multiple traits works underneath:

trait Trait1 {def ping(){}};
trait Trait2 {def pong(){}};
class Foo extends Trait1 with Trait2

translates to:

class Foo implements Trait1, Trait2 {
public void ping() {
Trait1$class.ping(this); //works because `this` implements Trait1
}

public void pong() {
Trait2$class.pong(this); //works because `this` implements Trait2
}
}

Multiple traits overriding same method

Now it's easy to imagine how mixing in multiple traits overriding same method:

trait Trait {def bar(){}};
trait Trait1 extends Trait {override def bar(){}};
trait Trait2 extends Trait {override def bar(){}};

Again Trait1 and Trait2 will become interfaces extending Trait. Now if Trait2 comes last when defining Foo:

class Foo extends Trait1 with Trait2

you'll get:

class Foo implements Trait1, Trait2 {
public void bar() {
Trait2$class.bar(this); //works because `this` implements Trait2
}
}

However switching Trait1 and Trait2 (making Trait1 to be last) will result in:

class Foo implements Trait2, Trait1 {
public void bar() {
Trait1$class.bar(this); //works because `this` implements Trait1
}
}

Stackable modifications

Now consider how traits as stackable modifications work. Imagine having a really useful class Foo:

class Foo {
def bar = "Foo"
}

which you want to enrich with some new functionality using traits:

trait Trait1 extends Foo {
abstract override def bar = super.bar + ", Trait1"
}

trait Trait2 extends Foo {
abstract override def bar = super.bar + ", Trait2"
}

Here is the new 'Foo' on steroids:

class FooOnSteroids extends Foo with Trait1 with Trait2

It translates to:

Trait1

interface Trait1 {
String Trait1$$super$bar();
String bar();
}
abstract class Trait1$class {
public static String bar(Trait1 thiz) {
// interface call Trait1$$super$bar() is possible
// since FooOnSteroids implements Trait1 (see below)
return thiz.Trait1$$super$bar() + ", Trait1";
}
}

Trait2

public interface Trait2 {
String Trait2$$super$bar();
String bar();
}
public abstract class Trait2$class {
public static String bar(Trait2 thiz) {
// interface call Trait2$$super$bar() is possible
// since FooOnSteroids implements Trait2 (see below)
return thiz.Trait2$$super$bar() + ", Trait2";
}
}

FooOnSteroids

class FooOnSteroids extends Foo implements Trait1, Trait2 {
public final String Trait1$$super$bar() {
// call superclass 'bar' method version
return Foo.bar();
}

public final String Trait2$$super$bar() {
return Trait1$class.bar(this);
}

public String bar() {
return Trait2$class.bar(this);
}
}

So the whole stack invocations are as follows:

  • 'bar' method on FooOnSteroids instance (entry point);
  • Trait2$class's 'bar' static method passing this as argument and returning a concatenation of 'Trait2$$super$bar()' method call and string ", Trait2";
  • 'Trait2$$super$bar()' on FooOnSteroids instance which calls ...
  • Trait1$class's 'bar' static method passing this as argument and returning a concatenation of 'Trait1$$super$bar()' method call and string ", Trait1";
  • 'Trait1$$super$bar' on FooOnSteroids instance which calls ...
  • original Foo's 'bar' method

And the result is "Foo, Trait1, Trait2".

Conclusion

If you've managed to read everything, an answer to the original question is in the first four lines...

Inherit Scala trait with fields in Java

Two problems might be arising.

1) java does not know what type to give fieldTypes due to the use of a placeholder type (underscore). This might lead to the generated fieldTypes_$eq method being made abstract (see here to see what java code is generated for a var)

2) scala traits cannot be extended in Java if they have any implementation details.

1 should be fixed by giving it an explicit type and 2 can be fixed by wrapping the trait in a class to extend in Java. (although 2 doesn't seem to be your problem, just something to be aware of)

How to call constructor of Scala trait in Java subclass?

That would become something like:

public interface A {

}

public abstract class A$class {
public static void $init$(A $this) {
Predef..MODULE$.println((Object)"A");
}
}

using a decompiler. Java speaking, that is:

public class AImpl implements A {}
public class User {
public User() {
A a = new AImpl();
A$class.$init$(a);
}
}

Scala: Creating a trait that forces classes to re-implement certain methods

This is close to it (and certainly an improvement over what I have),
but it does not do exactly what I wanted. If I have a "chain" of
classes, then it is enough for the top of the chain to implement the
methods.

Typeclass approach can help with that, for example,

trait Debuggable[T] {
def hashCode(v: T): Int
def equals(v: T, b: Any): Boolean
def toString(v: T): String
}

class Foo
class Bar
class Qux extends Foo

object Debuggable {
implicit val fooDebuggable: Debuggable[Foo] = new Debuggable[Foo] {
def hashCode(v: Foo) = 42
def equals(v: Foo, b: Any) = true
def toString(v: Foo) = "woohoo"
}
implicit val barDebuggable: Debuggable[Bar] = new Debuggable[Bar] {
def hashCode(v: Bar) = 24
def equals(v: Bar, b: Any) = false
def toString(v: Bar) = "boohoo"
}
}

import Debuggable._
def debug[T](v: T)(implicit ev: Debuggable[T]) = ???
debug(new Foo) // OK
debug(new Bar) // OK
debug(new Qux) // Error despite Qux <:< Foo

Trying to understand scala trait

Scala traits are more general than both Java interfaces and abstract classes.

You can use a trait as an interface, you can use it to store some implementation, you can use it simply to define a common super-type:

trait Message
case class Text(text: String) extends Message
case class Data(data: ByteString) extends Message

Multiple traits can be 'mixed in' a class:

class MyClass extends TraitA with TraitB with TraitC

where any conflict with identically named methods is resolved by the simple rule: the last trait takes precedence. This code:

trait TraitA { def print() { println("A") } }
trait TraitB { def print() { println("B") } }
trait TraitC { def print() { println("C") } }
new MyClass.print()

will print "C".

Scala traits can't be instantiated. You are creating an anonymous class in you example. If you add an abstract method to your trait it will not compile.

Unrelated note:
It is a good practice to write braces "()" in methods with side effects. Your method foo has a side effect: it prints something. So you should write "foo()".

How are Scala traits compiled into Java bytecode?

I'm not an expert, but here is my understanding:

Traits are compiled into an interface and corresponding class.

trait Foo {
def bar = { println("bar!") }
}

becomes the equivalent of...

public interface Foo {
public void bar();
}

public class Foo$class {
public static void bar(Foo self) { println("bar!"); }
}

Which leaves the question: How does the static bar method in Foo$class get called? This magic is done by the compiler in the class that the Foo trait is mixed into.

class Baz extends Foo

becomes something like...

public class Baz implements Foo {
public void bar() { Foo$class.bar(this); }
}

Class linearization just implements the appropriate version of the method (calling the static method in the Xxxx$class class) according to the linearization rules defined in the language specification.



Related Topics



Leave a reply



Submit