Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll explore the concept of traits in Groovy. They were introduced in the Groovy 2.3 release.

2. What are Traits?

Traits are reusable components representing a set of methods or behaviors that we can use to extend the functionality of multiple classes.

For this reason, they're considered as interfaces, carrying both default implementations and state. All traits are defined using the trait keyword.

3. Methods

Declaring a method in a trait is similar to declaring any regular method in a class. However, we cannot declare protected or package-private methods in a trait.

Let's see how public and private methods are implemented.

3.1. Public Methods

To start, we'll explore how public methods are implemented in a trait.

Let's create a trait named UserTrait and a public sayHello method:

trait UserTrait {
    String sayHello() {
        return "Hello!"
    }
}

After that, we'll create an Employee class, which implements UserTrait:

class Employee implements UserTrait {}

Now, let's create a test to verify that an Employee instance can access the sayHello method of the UserTrait:

def 'Should return msg string when using Employee.sayHello method provided by User trait' () {
    when:
        def msg = employee.sayHello()
    then:
        msg
        msg instanceof String
        assert msg == "Hello!"
}

3.2. Private Methods

We can also create a private method in a trait and refer to it in another public method.

Let's see the code implementation in the UserTrait:

private String greetingMessage() {
    return 'Hello, from a private method!'
}
    
String greet() {
    def msg = greetingMessage()
    println msg
    return msg
}

Note that if we access the private method in the implementation class, it will throw a MissingMethodException:

def 'Should return MissingMethodException when using Employee.greetingMessage method' () {
    when:
        def exception
        try {
            employee.greetingMessage()
        } catch(Exception e) {
            exception = e
        }
        
    then:
        exception
        exception instanceof groovy.lang.MissingMethodException
        assert exception.message == "No signature of method: com.baeldung.traits.Employee.greetingMessage()"
          + " is applicable for argument types: () values: []"
}

In a trait, a private method may be essential for any implementation that should not be overridden by any class, though required by other public methods.

3.3. Abstract Methods

A trait can also contain abstract methods that can then be implemented in another class:

trait UserTrait {
    abstract String name()
    
    String showName() {
       return "Hello, ${name()}!"
    }
}
class Employee implements UserTrait {
    String name() {
        return 'Bob'
    }
}

3.4. Overriding Default Methods

Usually, a trait contains default implementations of its public methods, but we can override them in the implementation class:

trait SpeakingTrait {
    String speak() {
        return "Speaking!!"
    }
}
class Dog implements SpeakingTrait {
    String speak() {
        return "Bow Bow!!"
    }
}

Traits do not support protected and private scopes.

4. this Keyword

The behavior of the this keyword is similar to that in Java. We can consider the trait as a super class.

For instance, we'll create a method which returns this in a trait:

trait UserTrait {
    def self() {
        return this 
    }
}

5. Interfaces

A trait can also implement interfaces, just like regular classes do.

Let's create an interface and implement it in a trait:

interface Human {
    String lastName()
}
trait UserTrait implements Human {
    String showLastName() {
        return "Hello, ${lastName()}!"
    }
}

Now, let's implement the abstract method of the interface in the implementation class:

class Employee implements UserTrait {
    String lastName() {
        return "Marley"
    }
}

6. Properties

We can add properties to a trait just like we would in any regular class:

trait UserTrait implements Human { 
    String email
    String address
}

7. Extending Traits

Similar to a regular Groovy class, a trait may extend another trait using the extends keyword:

trait WheelTrait {
    int noOfWheels
}

trait VehicleTrait extends WheelTrait {
    String showWheels() {
        return "Num of Wheels $noOfWheels" 
    } 
}

class Car implements VehicleTrait {}

We can also extend multiple traits with the implements clause:

trait AddressTrait {                                      
    String residentialAddress
}

trait EmailTrait {                                    
    String email
}

trait Person implements AddressTrait, EmailTrait {}

8. Multiple Inheritance Conflicts

When a class implements two or more traits that have methods with the same signature, we need to know how to resolve the conflicts. Let's look at how Groovy resolves such conflicts by default, as well as a way that we can override the default resolution.

8.1. Default Conflict Resolution

By default, the method from the last declared trait in the implements clause will be picked up.

Therefore, traits help us to implement multiple inheritances without encountering the Diamond Problem.

First, let's create two traits with a method having the same signature:

trait WalkingTrait {
    String basicAbility() {
        return "Walking!!"
    }
}

trait SpeakingTrait {
    String basicAbility() {
        return "Speaking!!"
    }
}

Next, let's write a class that implements both traits:

class Dog implements WalkingTrait, SpeakingTrait {}

Because SpeakingTrait is declared last, its basicAbility method implementation would be picked up by default in the Dog class.

8.2. Explicit Conflict Resolution

Now, if we don't want to simply take the default conflict resolution provided by the language, we can override it by explicitly choosing which method to call using the trait.super.method reference.

For instance, let's add another method with the same signature to our two traits:

String speakAndWalk() {
    return "Walk and speak!!"
}
String speakAndWalk() {
    return "Speak and walk!!"
}

Now, let's override the default resolution of multiple inheritance conflicts in our Dog class using the super keyword:

class Dog implements WalkingTrait, SpeakingTrait {
    String speakAndWalk() {
        WalkingTrait.super.speakAndWalk()
    }
}

9. Implementing Traits at Runtime

To implement a trait dynamically, we can use the as keyword to coerce an object to a trait at runtime.

For instance, let’s create an AnimalTrait with the basicBehavior method:

trait AnimalTrait {
    String basicBehavior() {
        return "Animalistic!!"
    }
}

To implement several traits at once, we can use the withTraits method instead of the as keyword:

def dog = new Dog()
def dogWithTrait = dog.withTraits SpeakingTrait, WalkingTrait, AnimalTrait

10. Conclusion

In this article, we've seen how to create traits in Groovy and explored some of their useful features.

A trait is a really effective way to add common implementations and functionalities throughout our classes. In addition, it allows us to minimize redundant code and makes code maintenance easier.

As usual, the code implementations and unit tests for this article are available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!