What def, val, and var fields in Scala traits look like after they’re compiled (including the classes that extend them)

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.

Back to top

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.

Back to top

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 abstract val 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
};
Back to top

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
Back to top

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
Back to top

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 private id; this is a different implementation than the “concrete val” example (which makes sense, because this is a var example)
  • Note that the Bar constructor makes two method calls; conversely, this is all it did in the “abstract var” example:
public Bar() {
    id = 1;
}
Back to top

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 field t in class T results in an abstract method, as shown in my original def example
  • The class A implements T, but does not reference t
  • The class B implements t as a private field t and a getter method t(), which is also the same as my first def example
  • Putting an abstract class in the middle essentially had no effect on the relationship between the final class B and the trait T
Back to top

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 trait T1
  • 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
Back to top

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.

Back to top