Traits are a a structural construct of the language which allow:
-
composition of behaviors
-
runtime implementation of interfaces
-
behavior overriding
-
compatibility with static type checking/compilation
They can be seen as interfaces carrying both default implementations and state. A trait is defined using the
trait
keyword:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
declaration of a trait
-
declaration of a method inside a trait
Then it can be used like a normal interface using the implements
keyword:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
Adds the trait
FlyingAbility
to theBird
class capabilities -
instantiate a new
Bird
-
the
Bird
class automatically gets the behavior of theFlyingAbility
trait
Traits allow a wide range of capabilities, from simple composition to testing, which are described throughfully in this section.
Declaring a method in a trait can be done like any regular method in a class:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
declaration of a trait
-
declaration of a method inside a trait
In addition, traits may declare abstract methods too, which therefore need to be implemented in the class implementing the trait:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
implementing class will have to declare the
name
method -
can be mixed with a concrete method
Then the trait can be used like this:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
implement the trait
Greetable
-
since
name
was abstract, it is required to implement it -
then
greeting
can be called
Traits may also define private methods. Those methods will not appear in the trait contract interface:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
define a private method
greetingMessage
in the trait -
the public
greet
message callsgreetingMessage
by default -
create a class implementing the trait
-
greet
can be called -
but not
greetingMessage
Warning
|
Traits only support public and private methods. Neither protected nor package private scopes are
supported.
|
Traits may implement interfaces, in which case the interfaces are declared using the implements
keyword:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
declaration of a normal interface
-
add
Named
to the list of implemented interfaces -
declare a class that implements the
Greetable
trait -
implement the missing
greet
method -
the
greeting
implementation comes from the trait -
make sure
Person
implements theNamed
interface -
make sure
Person
implements theGreetable
trait
A trait may define properties, like in the following example:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
declare a property
name
inside a trait -
declare a class which implements the trait
-
the property is automatically made visible
-
it can be accessed using the regular property accessor
-
or using the regular getter syntax
Since traits allow the use of private methods, it can also be interesting to use private fields to store state. Traits will let you do that:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
Tip
|
This is a major difference with Java 8 virtual extension methods. While virtual extension methods do not carry state, traits can. Also interesting traits in Groovy are supported starting with Java 6, but their implementation do not rely on virtual extension methods. This means that even if a trait can be seen from a Java class as a regular interface, this interface will not have default methods, only abstract ones. |
Public fields work the same way as private fields, but in order to avoid the diamond problem, field names are remapped in the implementing class:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
declare a public field inside the trait
-
declare a class implementing the trait
-
create an instance of that class
-
the public field is available, but renamed
The name of the field depends on the fully qualified name of the trait. All dots (.
) in package are replaced with an underscore (_
), and the final name includes a double underscore.
So if the type of the field is String
, the name of the package is my.package
, the name of the trait is Foo
and the name of the field is bar
,
in the implementing class, the public field will appear as:
String my_package_Foo__bar
Warning
|
While traits support public fields, it is not recommanded to use them and considered as a bad practice. |
Traits can be used to implement multiple inheritance in a controlled way, avoiding the diamond issue. For example, we can have the following traits:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
And a class implementing both traits:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
Duck
class implements bothFlyingAbility
andSpeakingAbility
-
creates a new instance of
Duck
-
we can call the method
fly
fromFlyingAbility
-
but also the method
speak
fromSpeakingAbility
Traits encourage the reuse of capabilities among objects, and the creation of new classes by the composition of existing behavior.
Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, we can slightly change the example above, by having a duck which quacks:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
define a method specific to
Duck
, namedquack
-
override the default implementation of
speak
so that we usequack
instead -
the duck is still flying, from the default implementation
-
quack
comes from theDuck
class -
speak
no longer uses the default implementation fromSpeakingAbility
Traits may extend another trait, in which case you must use the extends
keyword:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
Named
trait defines a singlename
property -
the
Polite
trait extends theNamed
trait -
Polite
adds a new method which has access to thename
property of the super-trait -
the
name
property is visible from thePerson
class implementingPolite
-
as is the
introduce
method
Traits can call any dynamic code, like a normal Groovy class. This means that you can, in the body of a method, call methods which are supposed to exist in an implementing class, without having to explicitly declare them in an interface. This means that traits are fully compatible with duck typing:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
SpeakingDuck
expects thequack
method to be defined -
the
Duck
class does implement the method using methodMissing -
calling the
speak
method triggers a call toquack
which is handled bymethodMissing
It is also possible for a trait to implement MOP methods like methodMissing
or propertyMissing
, in which case implementing classes
will inherit the behavior from the trait, like in this example:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
create a trait implementing several MOP methods
-
the
Dynamic
class defines a property -
the
Dynamic
class defines a method -
calling an existing property will call the method from
Dynamic
-
calling an non-existing property will call the method from the trait
-
will call
setProperty
defined on the trait -
will call
getProperty
defined on the trait -
calling an existing method on
Dynamic
-
but calling a non existing method thanks to the trait
methodMissing
It is possible for a class to implement multiple traits. If some trait defines a method with the same signature as a method in another trait, we have a conflict:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
trait
A
defines a method namedexec
returning aString
-
trait
B
defines the very same method -
class
C
implements both traits
In this case, the default behavior is that methods from the last declared trait wins. Here, B
is declared after A
so the method from B
will be picked up:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
In case this behavior is not the one you want, you can explicitly choose which method to call using the Trait.super.foo
syntax.
In the example above, we can force to choose the method from trait A, by writing this:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
explicit call of
exec
from the traitA
-
calls the version from
A
instead of using the default resolution, which would be the one fromB
Groovy also supports implementing traits dynamically at runtime. It allows you to "decorate" an existing object using a trait. As an example, let’s start with this trait and the following class:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
Extra
trait defines anextra
method -
the
Something
class does not implement theExtra
trait -
Something
only defines a methoddoSomething
Then if we do:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
the call to extra would fail because Something
is not implementing Extra
. It is possible to do it at runtime with
the following syntax:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
use of the as keyword to coerce an object to a trait at runtime
-
then
extra
can be called on the object -
and
doSomething
is still callable
Important
|
When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteed that the coerced object will implement both the trait and the interfaces that the original object implements, but the result will not be an instance of the original class. |
Should you need to implement several traits at once, you can use the withTraits
method instead of the as
keyword:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
call to
methodFromA
will fail becauseC
doesn’t implementA
-
call to
methodFromB
will fail becauseC
doesn’t implementB
-
withTrait
will wrapc
into something which implementsA
andB
-
methodFromA
will now pass becaused
implementsA
-
methodFromB
will now pass becaused
also implementsB
Important
|
When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteed that the coerced object will implement both the traits and the interfaces that the original object implements, but the result will not be an instance of the original class. |
If a trait defines a single abstract method, it is candidate for SAM type coercion. For example, imagine the following trait:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
greet
method is not abstract and calls the abstract methodgetName
-
getName
is an abstract method
Since getName
is the single abstract method in the Greeter
trait, you can write:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the closure "becomes" the implementation of the
getName
single abstract method
or even:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the greet method accepts the SAM type Greeter as parameter
-
we can call it directly with a closure
The @ForceOverride
annotation can be used to compose behaviors in an even more precise way, in case you want to
override the behavior of an already implemented method.
To illustrate the concept, let’s start with this simple example:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
In this example, we create a simple test case which uses two properties (config and shell) and uses those in
multiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration.
One option is to create a subclass of SomeTest
:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
It works, but what if you have actually multiple test classes, and that you want to test the new configuration for all those test classes? Then you would have to create a distinct subclass for each test class:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
Then what you see is that the setup
method of both tests is the same. The idea, then, is to create a trait:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
Then use it in the subclasses:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
...
It would allow us to dramatically reduce the boilerplate code, and reduces the risk of forgetting to change the setup
code in case we decide to change it. The problem is that setup
is already implemented. Traits will only provide
default implementations. Since the setup
method is implemented in the super class, then even if we mark the class
as implementing the trait, the default method is not used.
That’s where @ForceOverride
comes in action. By annotating a trait method with @ForceOverride
, you are asking the
compiler to use the implementation from the trait even if the method is already implemented:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
@ForceOverride
is in particular useful when you don’t have access to the super class source code. It can be used to
mock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keep
the overriden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course,
is to override the method in every place you would have used the new code.
It’s worth noting that @ForceOverride
can also be used on runtime traits:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
the
Person
class defines aname
property which results in agetName
method -
BobNoOverride
is a trait which definesgetName
without@ForceOverride
-
BobForceOverride
is a trait which definesgetName
with@ForceOverride
-
the default object will return Alice
-
p1
coerces intoBobNoOverride
-
getName
still returns Alice becausegetName
is already implemented inPerson
-
p2
coerces intoBobForceOverride
-
getName
still returns Bob becausegetName
is annotated with@ForceOverride
Important
|
Again, don’t forget that dynamic trait coercion returns a distinct object which only implements the original interfaces, as well as the traits. |
There are several conceptual differences with mixins, as they are available in Groovy. Note that we are talking about
runtime mixins, not the @Mixin
annotation which is deprecated by traits.
First of all, methods defined in a trait are visible in bytecode:
-
internally, the trait is represented as an interface (without default methods) and several helper classes
-
this means that an object implementing a trait effectively implements an interface
-
those methods are visible from Java
-
they are compatible with type checking and static compilation
Methods added through a mixin are, on the contrary, only visible at runtime:
link:{projectdir}/src/spec/test/TraitsSpecificationTest.groovy[role=include]
-
class
A
definesmethodFromA
-
class
B
definesmethodFromB
-
mixin B into A
-
we can call
methodFromA
-
we can also call
methodFromB
-
the object is an instance of
A
-
but it’s not an instanceof
B
The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instances are not modified, so if you mixin some class into another, there isn’t a third class generated, and methods which respond to A will continue responding to A even if mixed in.