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:
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}):
this, againtrait 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):
thistrait 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):
@SelfType annotationimport 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.