99. The class-trait relationship

When a trait is implemented by a class, the relation can be seen as the trait is “folded” into the implementing class. We saw this when we called the SportingEvents constructor and could see the Running trait’s properties. Because of this relationship, traits can refer to this to access instance members.

The code below reveals that a trait’s class is that of the implementer:

Uncover the mystery
trait MyTrait {
    Class whoIsThis() {
        this.class
    }
}

class MyClass implements MyTrait {}

assert new MyClass().whoIsThis() == MyClass

In the next example you can see this being used internal to the trait (this.distance = distance) and the class (${this.name}) but also from the class into the trait (${this.distance}):

Traits and this, again
trait Running {
    Integer distance

    void setDistance(distance) {
        if (distance > 10_000) {
            throw new IllegalArgumentException('Surely you can\'t run that far')
        }
        this.distance = distance
    }
}

class SportingEvent implements Running {
    String name

    def getAdvert() {
        "${this.name} is a ${this.distance}m event"
    }
}

SportingEvent groovySprint = new SportingEvent(name: 'The Groovy 500', distance:\
 500)
println groovySprint.advert

Self types

That last example could have been rewritten such that the getAdvert() method is declared in the trait (rather than the class):

Traits and this
trait Running {
    Integer distance

    void setDistance(distance) {
        if (distance > 10_000) {
            throw new IllegalArgumentException('Surely you can\'t run that far')
        }
        this.distance = distance
    }

    def getAdvert() {
        "${this.name} is a ${this.distance}m event"
    }
}

class SportingEvent implements Running {
    String name
}

SportingEvent groovySprint = new SportingEvent(name: 'The Groovy 500', distance:\
 500)
println groovySprint.advert

This works fine as SportingEvent has a name property but there’s nothing enforcing this and you’re exposed to the risk of a groovy.lang.MissingPropertyException being raised at runtime if the method/property/field can’t be found.

The @groovy.transform.SelfType annotation is used if a trait needs to be tied to a specific implementing class. The example below demonstrates the Running trait annotated with @SelfType(SportingEvent), indicating that the trait should only by implemented by SportingEvent (or one of its subclasses):

The @SelfType annotation
import groovy.transform.SelfType

@SelfType(SportingEvent)
trait Running {
    Integer distance

    void setDistance(distance) {
        if (distance > 10_000) {
            throw new IllegalArgumentException('Surely you can\'t run that far')
        }
        this.distance = distance
    }

    def getAdvert() {
        "${this.name} is a ${this.distance}m event"
    }
}

class SportingEvent implements Running {
    String name
}

By setting @SelfType(SportingEvent) we can ensure that Groovy will refuse to compile the following attempt:

class Imposter implements Running {}

Just be mindful with this capability - you want to make sure that you aren’t coupling your classes and traits too much. Thankfully, @SelfType can also be passed an interface, allowing for a more broadly implemented trait.