I generally have a pretty good feel for how Scala traits work, and how they can be used for different needs. As one example, a few years ago I learned that it’s best to define abstract fields in traits using def
. But there are still a few things I wonder about.
Today I had a few free moments and I decided to look at what happens under the covers when you use def
, val
, and var
fields in traits, and then mix-in or extend those traits with classes. So I created some examples, compiled them with scalac -Xprint:all
, and then decompiled them with JAD to see what everything looks like under the covers.
I was initially going to write a summary here, but if you want to know how things work under the hood, I think it helps to work through the examples, so for today I’ll leave that as an exercise for the reader.
As a final note before jumping in, all of these tests were run with Scala 2.12.8 and Java 8.
def
field in trait
As a first example, this is how I defined a field as a def
in a trait, and then implemented it in a class:
trait Foo {
def id: Int
}
class Bar extends Foo {
val id = 1
}
Next, this is what the final part of the output looks like when I compile that code with scalac -Xprint:all
:
abstract trait Foo extends Object {
def id(): Int
};
class Bar extends Object with Foo {
private[this] val id: Int = _;
<stable> <accessor> def id(): Int = Bar.this.id;
def <init>(): Bar = {
Bar.super.<init>();
Bar.this.id = 1;
()
}
}
And this is what I found when I decompiled the resulting class files with JAD:
public interface Foo {
public abstract int id();
}
public class Bar implements Foo {
private final int id = 1;
public int id() {
return id;
}
public Bar() {}
}
Results:
- A
def
field in a trait results in an interface that has an abstract method with the same name and return type as the field - The implementing class has a private, final backing field for the trait field, along with a corresponding “getter” method for that field
The part where the implementing class ends up with a private field corresponding to the def
field was a surprise when I learned about it a few years ago, but it makes sense when you think the process through, i.e., how the code has to work on the JVM.
When I first started this process I thought it would be helpful to show the output from
scalac -Xprint:all
, but to keep things simple here, I’ll only show the JAD output.
val
field in trait (abstract)
Next, here’s the source code for my abstract val
example:
trait Foo {
val id: Int //abstract
}
class Bar extends Foo {
val id = 1
}
Here’s the resulting JAD output:
public interface Foo {
public abstract int id();
}
public class Bar implements Foo {
private final int id = 1;
public int id() {
return id;
}
public Bar() {}
}
Results:
- An abstract
val
field in a trait creates an interface with an abstract method with the same name and return type - The implementing class has a private backing field for trait field, along with a corresponding “getter” method for that field
- At the class level, for this simple example, a
def
field and an abstractval
field seem to result in the same bytecode
I should note that there’s a slight difference in the output seen by scalac -Xprint:all
for the two approaches. The final output from scalac
for my def
example looks like this:
abstract trait Foo extends Object {
def id(): Int
};
while the same final output for the val
approach looks like this:
abstract trait Foo extends Object {
<stable> <accessor> def id(): Int
};
val
field in trait (concrete)
Here’s the source code for my concrete val
field example:
trait Foo {
val id = 0 //concrete
}
class Bar extends Foo {
override val id = 1
}
Here’s the resulting JAD output:
public interface Foo {
public abstract void $Foo$_setter_$id_$eq(int i);
public abstract int id();
public static void $init$(Foo $this) {
$this.$Foo$_setter_$id_$eq(0);
}
}
public class Bar implements Foo {
private final int id = 1;
public void $Foo$_setter_$id_$eq(int i) {}
public int id() {
return id;
}
public Bar() {
Foo.$init$(this);
}
}
Results:
- Because the field must be a concrete field in the trait, the resulting interface code is quite a bit more complex, including an initialization method
- It was a bit of a surprise for me to see what appear to be public “setter” methods in the resulting code, although the setter in the class doesn’t have a method body, and its
id
field is marked final
var
field in trait (abstract)
Here’s the source code for my abstract var
example:
trait Foo {
var id: Int //abstract
}
class Bar extends Foo {
var id = 1 //works
}
Here’s the resulting JAD output:
public interface Foo {
public abstract int id();
public abstract void id_$eq(int i);
}
public class Bar implements Foo {
private int id;
public int id() {
return id;
}
public void id_$eq(int x$1) {
id = x$1;
}
public Bar() {
id = 1;
}
}
Results:
var
means “get and set,” so both getter and setter methods are generated- As you would expect after the previous examples, the trait doesn’t have an
id
field, and it has two abstract methods, a setter and a getter - The class looks a little like a JavaBean, with a private field and getter/setter methods
var
field in trait (concrete)
Here’s the source code for my concrete var
field example:
trait Foo {
var id = 0
}
class Bar extends Foo {
id = 1
}
Here’s the resulting JAD output:
public interface Foo {
public abstract int id();
public abstract void id_$eq(int i);
public static void $init$(Foo $this) {
$this.id_$eq(0);
}
}
public class Bar implements Foo {
private int id;
public int id() {
return id;
}
public void id_$eq(int x$1) {
id = x$1;
}
public Bar() {
Foo.$init$(this);
id_$eq(1);
}
}
Results:
- As usual, there’s no field in the interface
- There is an “init” method in the interface to set the field to
0
initially - The init method calls
id_$eq
, which updates the privateid
; this is a different implementation than the “concreteval
” example (which makes sense, because this is avar
example) - Note that the
Bar
constructor makes two method calls; conversely, this is all it did in the “abstractvar
” example:
public Bar() {
id = 1;
}
An abstract class in the middle
To look at something a little more complicated, I created a trait T
with a field t
defined as a def
. Then I extend that with an abstract class A
, while also giving A
a concrete field a
. Then I create a concrete class B
by extending A
:
trait T {
def t: Int
}
abstract class A extends T {
val a = 1
// class is abstract b/c it doesn’t implement `t`
}
class B extends A {
val b = 2
override val t = 3
}
Here’s the resulting JAD output:
public interface T {
public abstract int t();
}
public abstract class A implements T {
private final int a = 1;
public int a() {
return a;
}
public A() {}
}
public class B extends A {
private final int b = 2;
private final int t = 3;
public int b() {
return b;
}
public int t() {
return t;
}
public B() {}
}
Results:
- The
def
fieldt
in classT
results in an abstract method, as shown in my originaldef
example - The class
A
implementsT
, but does not referencet
- The class
B
implementst
as a private fieldt
and a getter methodt()
, which is also the same as my firstdef
example - Putting an abstract class in the middle essentially had no effect on the relationship between the final class
B
and the traitT
A trait in the middle
As a final example, let’s see what happens if one trait extends another trait, they both have def
fields, and a class extends the second trait:
trait T1 {
def t1: Int
}
trait T2 extends T1 {
def t2: Int
}
class C extends T2 {
val t1 = 1
val t2 = 2
}
If you’ve read through all of this so far, you probably have a decent feel for what the generated source code should look like. So before looking at the following code, I encourage you to work through this problem in your head and decide what you think the decompiled source code should look like for T1
, T2
, and the class C
.
.
.
.
.
.
.
Here’s the decompiled source code:
public interface T1 {
public abstract int t1();
}
public interface T2 extends T1 {
public abstract int t2();
}
public class C implements T2 {
private final int t1 = 1;
private final int t2 = 2;
public int t1() {
return t1;
}
public int t2() {
return t2;
}
public C() {}
}
Results:
- As before,
def
fields in traits lead to abstract methods in the generated code - As in the previous example, the trait in the middle knows nothing about the
t1()
method when it extends the traitT1
- As in previous examples, the final class has private fields corresponding to the fields defined in the trait(s) it extends, with public getter methods for those fields
Summary
As I wrote in the beginning, I didn’t want to include a summary here because I think it’s best to work through the examples. Therefore, I won’t add a summary here, but if you want to add your own summary in the Comments section below, feel free to do so.