Traits are reusable components, basically set of methods or fields that can be implemented by class.
Agenda:
-> Traits
-> Why use Traits?
-> How to Use Traits
-> Extending Traits
-> Multiple Inheritance Conflicts and Resolutions
-> Runtime Implementation of Traits
-> Chaining
-> Differences from Java 8 default methods
-> Limitations
2. Agenda
➔ Traits
➔ Why use Traits?
➔ How to Use Traits
➔ Extending Traits
➔ Multiple Inheritance Conflicts
and Resolutions
➔ Runtime Implementation of
Traits
➔ Chaining
➔ Differences from Java 8 default
methods
➔ Limitations
3. Groovy 2.3 introduced traits as a new language construct.
Traits are reusable components, basically set of methods or fields that can be implemented by class.
Traits are a structural construct of the language which allow:
★ composition of behaviors
★ runtime implementation of interfaces
★ behavior overriding
★ compatibility with static type checking/compilation
Traits
4. Why use Traits ?
We all must have heard of problems of multiple inheritance when working
with java and also familiar with the well known situation of what is known
as 'Diamond Problem'. It says that if there are two classes B and C which
are inherited from A, and class D is inherited from both B and C. If there is a
method in A that B and/or C has overridden, and D does not override it,
then which version of the method does D inherit: that of B, or that of C ??
The situation is like we don’t know from which parent class a particular
feature is inherited from if more than one parent class implements the
feature.
So, traits allows the composition of behavior without going into the
“Diamond Inheritance Problem” allowing us to decide which behavior
prevails upon conflict.
Class A
Class B Class C
Class D
5. Then it can be used like a normal interface using the implements keyword:
How to use Traits
trait FlyingAbility {
String fly() { "I'm flying!" }
}
Declaration of a trait
Declaration of a method inside a trait
class Bird implements FlyingAbility {}
def b = new Bird()
assert b.fly() == "I'm flying!"
Here:
Adds the trait FlyingAbility to
the Bird class capabilities
instantiate a new Bird
the Bird class automatically
gets the behavior of the
FlyingAbility trait
They can be seen as interfaces carrying both default implementations and state.
A trait is defined using the trait keyword.
6. Extending Traits
❏ Simple Inheritance
Traits may extend another trait, in which case we must use the
extends keyword:
Here:
the Named trait defines a single
name property
the Polite trait extends the Named
trait
Polite adds a new method which has
access to the name property of the
super-trait
the name property is visible from
the Person class implementing Polite
as is the introduce method
trait Named {
String name
}
trait Polite extends Named {
String introduce() { "Hello, I am $name" }
}
class Person implements Polite {}
def p = new Person(name: 'Ali')
assert p.introduce() == 'Hello, I am Ali'
7. (Continues…)
❏ Multiple Inheritance
Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in the
implements clause:
Here:
WithId trait defines the id
property
WithName trait defines the
name property
Identified is a trait which
inherits both WithId and
WithName
trait WithId {
Long id
}
trait WithName {
String name
}
trait Identified implements WithId, WithName {}
8. Multiple Inheritance Conflicts and
Resolutions
❏ Default conflict resolution
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:
Here:
trait A defines a method named
exec returning a String
trait B defines the very same
method
class C implements both traits
trait A {
String exec() { 'A' }
}
trait B {
String exec() { 'B' }
}
class C implements A,B {}
9. (Continues…)
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:
def c = new C()
assert c.exec() == 'B'
10. (Continues…)
❏ User conflict resolution
In case this behavior is not the one we want, we 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:
Here:
explicit call of exec from the
trait A
calls the version from A instead
of using the default resolution,
which would be the one from B
class C implements A,B {
String exec() { A.super.exec() }
}
def c = new C()
assert c.exec() == 'A'
11. Runtime Implementation of Traits
❏ Implementing a Trait at Runtime
Groovy also supports implementing traits dynamically at runtime. It allows to "decorate" an existing
object using a trait. As an example, let’s start with this trait and the following class:
Here:
the Extra trait defines an extra
method
the Something class does not
implement the Extra trait
Something only defines a
method doSomething
trait Extra {
String extra() { "I'm an extra method" }
}
class Something {
String doSomething() { 'Something' }
}
12. (Continues…)
Then if we do:
def s = new Something()
s.extra()
the call to extra would fail because Something is not implementing Extra.
13. (Continues…)
It is possible to do it at runtime with the following syntax:
def s = new Something() as Extra
s.extra()
s.doSomething()
Here:
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
14. (Continues…)
❏ Implementing Multiple Traits at Once
If we need to implement several traits at once, we can use
the withTraits method instead of the as keyword:
trait A { void methodFromA() {} }
trait B { void methodFromB() {} }
class C {}
def c = new C()
c.methodFromA()
c.methodFromB()
def d = c.withTraits A, B
d.methodFromA()
d.methodFromB()
Here:
call to methodFromA will fail because C
doesn’t implement A
call to methodFromB will fail because C
doesn’t implement B
withTrait will wrap c into something which
implements A and B
methodFromA will now pass because d
implements A
methodFromB will now pass because d also
implements B
15. Chaining
Groovy supports the concept of stackable traits. The idea is to delegate from one trait to the other if the
current trait is not capable of handling a message.
18. Differences from Java 8 Default Methods
In Java 8, interfaces can have default implementations of methods. If a class implements an interface and
does not provide an implementation for a default method, then the implementation from the interface is
chosen.
Traits behave the same but with a major difference: the implementation from the trait is always used if
the class declares the trait in its interface list and that it doesn’t provide an implementation.
This feature can be used to compose behaviors in an very precise way, in case we want to override the
behavior of an already implemented method.
19. (Continues…)
Consider two classes that extends class A and implements trait T
So even if we have someMethod() already implemented in the super class, but the classes B and C
declares the trait in its interface list, the behavior will be borrowed from the trait implementation.
20. Limitations
❏ Compatibility with AST transformations
Traits are not officially compatible with AST transformations. Some of them, like @CompileStatic will be
applied on the trait itself (not on implementing classes), while others will apply on both the implementing
class and the trait. There is absolutely no guarantee that an AST transformation will run on a trait as it
does on a regular class, so use it at your own risk!
21. (Continues...)
❏ Prefix & Postfix Operations
Within traits, prefix and postfix operations are not allowed if they update a field of the trait:
trait Counting {
int x
void inc() {
x++
}
void dec() {
--x
}
}
class Counter implements Counting {}
def c = new Counter()
c.inc()
Here:
x is defined within the trait, postfix
increment is not allowed
x is defined within the trait, prefix
decrement is not allowed
Note:
A workaround is to use the +=
operator instead.
22. Some topics for Further Reading
➔ Meaning of this in Traits
➔ Overriding Default Methods
➔ Inheritance of State Gotchas, etc.