DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • What To Know Before Implementing IIoT
  • Share Delta Lake Tables Between Databricks and Snowflake
  • Compliance Automated Standard Solution (COMPASS), Part 10: How OSCAL Mapping Paves the Way for Continuous Compliance Scalability
  • GenAI Implementation Isn't Magic — It’s a Lifecycle

Trending

  • Part 4: Multi-Agent Orchestration — Pairing Goose With Quarkus Flow State Machines
  • MQTT, Kafka, NATS, or Zenoh? Choosing Edge-to-Cloud Messaging by Traffic Pattern
  • Architecture Review Boards for Generative AI: A Framework for Governance, Risk Management, and Responsible Adoption
  • New York City Taxi Trips: Putting Neo4j and DuckDB to Work

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Coding
  3. Languages
  4. Scala Traits Implementation and Interoperability. Part I: Basics

Scala Traits Implementation and Interoperability. Part I: Basics

By 
Tomasz Nurkiewicz
·
Apr. 11, 13 · Interview
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
8.4K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

 Traits in Scala are similar to interfaces, but much more powerful. They allow implementations of some of the methods, fields, stacking, etc. But have you ever wondered how are they implemented on top of JVM? How is it possible to extend multiple traits and where the implementations go to? In this article, based on my StackOverflow answer, I will give several trait examples and explain how scalac implements them, what are the drawbacks and what you get for free. Often we will look at compiled classes and decompile them to Java. It's not essential knowledge, but I hope you'll enjoy it. All examples are compiled against Scala 2.10.1.

Simple trait with no method implementations

The following trait:
trait Solver {
    def answer(question: String): Int
}
compiles down to the most boring Java interface:
public interface Solver {
    int answer(String);
}
There is really nothing special in Scala traits. That also means that if you ship Solver.class as part of your library, users can safely implement such interface from Java code. As far as java/javac is concerned, this is an ordinary Java compiled interface.

Traits having some methods implemented

OK, but what if trait actually has some method bodies?
trait Solver {
 
    def answer(s: String): Int
 
    def ultimateAnswer = answer("Answer to the Ultimate Question of Life, the Universe, and Everything")
 
}
Here, ultimateAnswer method actually has some implementation (the fact that it calls abstract answer() method is irrelevant) while answer() remains unimplemented. What will scalac produce?
public interface Solver {
    int answer(java.lang.String);
    int ultimateAnswer();
}
Well, it's still an interface and the implementation is gone. If the implementation is gone, what happens when we extend such a trait?
class DummySolver extends Solver {
 
    override def answer(s: String) = 42
 
}
We need to implement answer() but ultimateAnswer is already available via Solver. Anxious to see how it looks under the hood? DummySolver.class:
public class DummySolver implements Solver {
 
    public DummySolver() {
        Solver$class.$init$(this);
    }
 
    public int ultimateAnswer() {
        return Solver$class.ultimateAnswer(this);
    }
 
    public int answer(String s) {
        return 42;
    }
 
}
That... is... weird... Apparently we missed one new class file, Solver$class.class. Yes, the class is named Solver$class, this is valid even in Java. This is not Solver.class expression which returns Class[Solver], it's apparently an ordinary Java class named Solver$class with a bunch of static methods. Here is how it looks like:
public abstract class Solver$class {
 
    public static int ultimateAnswer(Solver $this) {
        return $this.answer("Answer to the Ultimate Question of Life, the Universe, and Everything");
    }
 
    public static void $init$(Solver solver) {}
}
Do you see the trick? Scala created a helper Solver$class and methods that are implemented inside trait are actually placed in that hidden class. BTW this is not a companion object, it's just a helper class invisible to you. But the real trick is $this parameter, invoked as Solver$class.ultimateAnswer(this). Since we are technically in another object, we must somehow get a handle of a real Solver instance. It is like OOP but done manually.

So we learned that method implementations from traits are extracted to a special helper class. This class is referenced every time we call such a method. This way we don't copy method body over and over into every single class extending given trait.

Extending multiple traits with implementations

Imagine extending multiple traits, each implementing distinct set of methods:
trait Foo {
    def foo = "Foo"
}
 
trait Bar {
    def bar = "Bar"
}
 
class Buzz extends Foo with Bar
By analogy you probably know already how Foo.class and Bar.class look like:
public interface Foo {
    String foo();
}
 
public interface Bar {
    String bar();
}

Implementations are hidden in mysterious ...$class.class files just as before:
public abstract class Foo$class {
    public static String foo(Foo) {
        return "Foo";
    }
 
    public static void $init$(Foo) {}
}
 
public abstract class Bar$class {
    public static String bar(Bar) {
        return "Bar";
    }
 
    public static void $init$(Bar) {}
}

Notice that static methods in Foo$class accept instances of Foo while Bar$class require reference to Bar. This works because Buzz implements both Foo and Bar:
public class Buzz implements Foo, Bar {
 
    public Buzz() {
        Foo.class.$init$(this);
        Bar.class.$init$(this);
    }
 
    public String bar() {
        return Bar$class.bar(this);
    }
 
    public String foo() {
        return Foo$class.foo(this);
    }
 
}

Both foo() and bar() pass this reference, but this is fine.

Traits with fields

Fields are another interesting feature of traits. This time let's use a real-world example from Spring Data project. As you know interfaces can require certain methods to be implemented. However they can't force you to provide certain fields (or provide fields on their own). This limitation becomes painful when working with Auditable<U, ID> interface extending Persistable<ID>. While these interfaces merely exist to make sure certain fields are present on your @Entity class, namely createdBy, createdDate, lastModifiedBy, lastModifiedDate and id, this cannot be expressed cleanly. Instead you have to implement the following methods in every single entity extending Auditable:
       U getCreatedBy();
    void setCreatedBy(final U createdBy);
DateTime getCreatedDate();
    void setCreatedDate(final DateTime creationDate);
       U getLastModifiedBy();
    void setLastModifiedBy(final U lastModifiedBy);
DateTime getLastModifiedDate();
    void setLastModifiedDate(final DateTime lastModifiedDate);
 
      ID getId();
 boolean isNew();
Moreover, every single class must of course define fields highlighted above. Doesn't feel DRY at all. Luckily traits can help us a lot. In trait we can define what fields should be created in every class extending this trait:
trait IAmAuditable[ID <: java.io.Serializable] extends Auditable[User, ID] {
 
    var createdBy: User = _
 
    def getCreatedBy = createdBy
 
    def setCreatedBy(createdBy: User) {
        this.createdBy = createdBy
    }
 
    var createdDate: DateTime = _
 
    def getCreatedDate = createdDate
 
    def setCreatedDate(creationDate: DateTime) {
        this.createdDate = creationDate
    }
 
    var lastModifiedBy: User = _
 
    def getLastModifiedBy = lastModifiedBy
 
    def setLastModifiedBy(lastModifiedBy: User) {
        this.lastModifiedBy = lastModifiedBy
    }
 
    var lastModifiedDate: DateTime = _
 
    def getLastModifiedDate = lastModifiedDate
 
    def setLastModifiedDate(lastModifiedDate: DateTime) {
        this.lastModifiedDate = lastModifiedDate
    }
 
    var id: ID = _
 
    def getId = id
 
    def isNew = id == null
}

But wait, Scala has built-in support for POJO-style getters/setters! So we can shorten this to:
class IAmAuditable[ID <: java.io.Serializable] extends Auditable[User, ID] {
 
    @BeanProperty var createdBy: User = _
 
    @BeanProperty var createdDate: DateTime = _
 
    @BeanProperty var lastModifiedBy: User = _
 
    @BeanProperty var lastModifiedDate: DateTime = _
 
    @BeanProperty var id: ID = _
 
    def isNew = id == null
}
Compiler-generated getters and setters implement interface automatically. From now on, every class willing to provide auditing capabilities can extend this trait:
@Entity
class Person extends IAmAuditable[String] {
 
    //...
 
}
All the fields, getters and setters are there. But how is it implemented? Let's look at the generated Person.class:
public class Person implements IAmAuditable<java.lang.String> {
    private User createdBy;
    private DateTime createdDate;
    private User lastModifiedBy;
    private DateTime lastModifiedDate;
    private java.io.Serializable id;
 
    public User createdBy() //...
    public void createdBy_$eq(User) //...
    public User getCreatedBy() //...
    public void setCreatedBy(User) //...
 
    public DateTime createdDate() //...
    public void createdDate_$eq(DateTime) //...
    public DateTime getCreatedDate() //...
    public void setCreatedDate(DateTime) //...
 
    public boolean isNew();
 
    //...
}
There is actually much more, but you get the idea. So fields aren't refactored into a separate class, but that was to be expected. Instead fields are copied into every single class extending this particular trait. In Java we could have used abstract base class for that, but it's nice to reserve inheritance for real is-a relationships and do not use it for dummy field holders. In Scala trait is such a holder, grouping common fields that we can reuse across several classes.

What about Java interoperability? Well, IAmAuditable is compiled down to Java interface, thus it doesn't have fields at all. If you implement it from Java, you gain nothing special.

We covered basic use cases for traits in Scala and their implementation. In the next article we will explore how mixins and stackable modifications work.


Trait (computer programming) Implementation Scala (programming language) Interoperability

Published at DZone with permission of Tomasz Nurkiewicz. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • What To Know Before Implementing IIoT
  • Share Delta Lake Tables Between Databricks and Snowflake
  • Compliance Automated Standard Solution (COMPASS), Part 10: How OSCAL Mapping Paves the Way for Continuous Compliance Scalability
  • GenAI Implementation Isn't Magic — It’s a Lifecycle

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook