96. Trait properties and fields

Traits can also declare properties:

A trait with a property
trait Running {
    Integer distance

    void startEvent() {
        println 'Ready, set, go!'
    }
}

class SportingEvent implements Running {
    String name
}

SportingEvent stawellGift = new SportingEvent(name: 'The Stawell Gift', distance\
: 120)

println "Welcome to $stawellGift.name - a ${stawellGift.distance}m race"
stawellGift.startEvent()

As you can see in the example above, the Running trait’s distance property essentially becomes a property of the SportingEvent class. As for classes, getters and setters are generated for properties but you can supply your own if you need additional functionality.

Fields within traits are somewhat different to those in classes. This can get a bit tricky so let’s lay out the example code first:

A trait with fields
trait Running {

    Integer distance

    public String raceType = 'foot race event'

    private Integer maxCompetitors = 10

    void startEvent() {
        println 'Ready, set, go!'
    }

}

class SportingEvent implements Running {
    String name

    String describeEvent() {
        "This is a ${Running__raceType} for ${Running__maxCompetitors} competito\
rs"
    }
}

SportingEvent dash = new SportingEvent()

assert dash.Running__raceType == 'foot race event'
assert dash.Running__maxCompetitors == 10

dash.Running__maxCompetitors = 99
assert dash.Running__maxCompetitors == 99

println dash.describeEvent()

In the Running trait above you can see:

  • One property (as before): Integer distance
  • A public field: public String raceType = 'foot race event'
  • A private field: private Integer maxCompetitors = 10

Unlike properties, we cannot refer to a field as though it was a member of the SportingEvent class. In order to access the public and private fields I need to call dash.Running__raceType and dash.Running__maxCompetitors respectively. This notation uses the trait’s fully qualified name followed by two underscores then the field: <PackageName>_<TraitName>__<fieldName> and is needed both in the implementing class and external code.

The fully qualified name format is a bit odd but, as we’ve looked at packages earlier it should be possible to clarify this:

  • For a trait that is in the default package (i.e. not explictly put in a package), we just need <TraitName>__<fieldName> - as seen in Running__raceType
  • For a trait in a package, each package level is separated by an underscore (_):
    • If Running was declared in the events package: events_Running__raceType
    • If Running was declared in the events.track package: events_track_Running__raceType
    • and so on…