Movatterモバイル変換


[0]ホーム

URL:


Object orientation

version 5.0.2
Table of Contents

This chapter covers the object-oriented aspects of the Groovy programming language.

1. Types

1.1. Primitive types

Groovy supports the same primitive types as defined by theJava Language Specification:

  • integral types:byte (8 bit),short (16 bit),int (32 bit) andlong (64 bit)

  • floating-point types:float (32 bit) anddouble (64 bit)

  • theboolean type (one oftrue orfalse)

  • thechar type (16 bit, usable as a numeric type, representing a UTF-16 code)

Also like Java, Groovy uses the respective wrapper classes when objects corresponding to anyof the primitive types are required:

Table 1. primitive wrappers
Primitive typeWrapper class

boolean

Boolean

char

Character

short

Short

int

Integer

long

Long

float

Float

double

Double

Automatic boxing and unboxing occur when, for instance, calling a method requiringthe wrapper class and passing it a primitive variable as the parameter, or vice-versa.This is similar to Java but Groovy takes the idea further.

In most scenarios, you can treat a primitive just like it was the full object wrapper equivalent.For instance, you can call.toString() or.equals(other) on a primitive.Groovy autowraps and unwraps between references and primitives as needed.

Here’s an example usingint which is declared as a static field in a class (discussed shortly):

class Foo {    static int i}assert Foo.class.getDeclaredField('i').type == int.class(1)assert Foo.i.class != int.class && Foo.i.class == Integer.class(2)
1Primitive type is respected in the bytecode
2Looking at the field at runtime shows it has been autowrapped

Now you may be concerned that this means every time you use a mathematical operator on a reference to a primitivethat you’ll incur the cost of unboxing and reboxing the primitive. But this is not the case, as Groovy will compileyour operators into theirmethod equivalents and uses those instead.Additionally, Groovy will automatically unbox to a primitive when calling a Java method that takes a primitiveparameter and automatically box primitive method return values from Java. However, be aware there are somedifferences from Java’s method resolution.

1.2. Reference Types

Apart from primitives, everything else is an object and has an associated class defining its type.We’ll discuss classes, and class-related or class-like things like interfaces, traits and records shortly.

We might declare two variables, of type String and List, as follows:

String movie = 'The Matrix'List actors = ['Keanu Reeves', 'Hugo Weaving']

1.3. Generics

Groovy carries across the same concepts with regard to generics as Java.When defining classes and methods, it is possible to use a type parameter and createa generic class, interface, method or constructor.

Usage of generic classes and methods, regardless of whether they are defined in Javaor Groovy, may involve supplying a type argument.

We might declare a variable, of type"list of string", as follows:

List<String> roles = ['Trinity', 'Morpheus']

Java employs type erasure for backwards compatibility with earlier versions of Java.Dynamic Groovy can be thought of as more aggressively applying type erasure.In general, less generics type information will be checked at compile time.Groovy’s static nature employs similar checks to Java with regard to generics information.

2. Classes

Groovy classes are very similar to Java classes, and are compatible with Java ones at JVM level.They may have methods, fields and properties (think JavaBeans properties but with less boilerplate).Classes and class members can have the same modifiers (public, protected, private, static, etc.) as in Javawith some minor differences at the source level which are explained shortly.

The key differences between Groovy classes and their Java counterparts are:

  • Classes or methods with no visibility modifier are automatically public (a special annotation can be used to achieve package private visibility).

  • Fields with no visibility modifier are turned into properties automatically, which results in less verbose code,since explicit getter and setter methods aren’t needed. More on this aspect will be covered in thefields and properties section.

  • Classes do not need to have the same base name as their source file definitions but it is highly recommended in most scenarios (see also the next point about scripts).

  • One source file may contain one or more classes (but if a file contains any code not in a class, it is considered a script). Scripts are just classes with somespecial conventions and will have the same name as their source file (so don’t include a class definition within a script having the same name as the script source file).

The following code presents an example class.

class Person {(1)    String name(2)    Integer age    def increaseAge(Integer years) {(3)        this.age += years    }}
1class beginning, with the namePerson
2string field and property namedname
3method definition

2.1. Normal class

Normal classes refer to classes which are top level and concrete. This means they can be instantiated without restrictions from any other classes or scripts. This way, they can only be public (even though thepublic keyword may be suppressed). Classes are instantiated by calling their constructors, using thenew keyword, as in the following snippet.

def p = new Person()

2.2. Inner class

Inner classes are defined within another classes. The enclosing class can use the inner class as usual. On the other side, an inner class can access members of its enclosing class, even if they are private. Classes other than the enclosing class are not allowed to access inner classes. Here is an example:

class Outer {    private String privateStr    def callInnerMethod() {        new Inner().methodA()(1)    }    class Inner {(2)        def methodA() {            println "${privateStr}."(3)        }    }}
1the inner class is instantiated and its method gets called
2inner class definition, inside its enclosing class
3even being private, a field of the enclosing class is accessed by the inner class

There are some reasons for using inner classes:

  • They increase encapsulation by hiding the inner class from other classes, which do not need to know about it. This also leads to cleaner packages and workspaces.

  • They provide a good organization, by grouping classes that are used by only one class.

  • They lead to more maintainable codes, since inner classes are near the classes that use them.

It is common for an inner class to be an implementation of some interface whose method(s) are needed by the outer class.The code below illustrates this typical usage pattern, here being used with threads.

class Outer2 {    private String privateStr = 'some string'    def startThread() {       new Thread(new Inner2()).start()    }    class Inner2 implements Runnable {        void run() {            println "${privateStr}."        }    }}

Note that the classInner2 is defined only to provide an implementation of the methodrun to classOuter2.Anonymous inner classes help to eliminate verbosity in this case.That topic is covered shortly.

Groovy 3+ also supports Java syntax for non-static inner class instantiation, for example:

class Computer {    class Cpu {        int coreNumber        Cpu(int coreNumber) {            this.coreNumber = coreNumber        }    }}assert 4 == new Computer().new Cpu(4).coreNumber

2.2.1. Anonymous inner class

The earlier example of an inner class (Inner2) can be simplified with an anonymous inner class.The same functionality can be achieved with the following code:

class Outer3 {    private String privateStr = 'some string'    def startThread() {        new Thread(new Runnable() {(1)            void run() {                println "${privateStr}."            }        }).start()(2)    }}
1comparing with the last example of previous section, thenew Inner2() was replaced bynew Runnable() along with all its implementation
2the methodstart is invoked normally

Thus, there was no need to define a new class to be used just once.

2.2.2. Abstract class

Abstract classes represent generic concepts, thus, they cannot be instantiated, being created to be subclassed.Their members include fields/properties and abstract or concrete methods.Abstract methods do not have implementation, and must be implemented by concrete subclasses.

abstract class Abstract {(1)    String name    abstract def abstractMethod()(2)    def concreteMethod() {        println 'concrete'    }}
1abstract classes must be declared withabstract keyword
2abstract methods must also be declared withabstract keyword

Abstract classes are commonly compared to interfaces.There are at least two important differences of choosing one or another.First, while abstract classes may contain fields/properties and concrete methods, interfaces may contain only abstract methods (method signatures).Moreover, one class can implement several interfaces, whereas it can extend just one class, abstract or not.

2.3. Inheritance

Inheritance in Groovy resembles inheritance in Java.It provides a mechanism for a child class (or subclass) to reusecode or properties from a parent (or super class).Classes related through inheritance form an inheritance hierarchy.Common behavior and members are pushed up the hierarchy to reduce duplication.Specializations occur in child classes.

Different forms of inheritance are supported:

  • implementation inheritance where code (methods, fields or properties) from asuperclass or fromone or moretraits is reused by a child class

  • contract inheritance where a class promises to provide particular abstract methods defined in asuperclass,or defined in one or moretraits orinterfaces.

2.4. Superclasses

Parent classes share visible fields, properties or methods with child classes.A child class may have at most one parent class.Theextends keyword is used immediately prior to giving the superclass type.

2.5. Interfaces

An interface defines a contract that a class needs to conform to.Typically, an interface defines zero or more abstract method definitions,but does not define the method’s implementation.

Here is aGreeter interface defining onegreet method:

interface Greeter {(1)    void greet(String name)(2)}
1an interface needs to be declared using theinterface keyword
2the abstract method signature for thegreet method

Such method signatures are public by default.It is an error to useprotected or package-private methods in interfaces:

interface Greeter {    protected void greet(String name)(1)}
1Usingprotected is a compile-time error

A classimplements an interface if it defines the interface in itsimplements list or if any of its superclassesdoes:

class SystemGreeter implements Greeter {(1)    void greet(String name) {(2)        println "Hello $name"    }}def greeter = new SystemGreeter()assert greeter instanceof Greeter(3)
1TheSystemGreeter declares theGreeter interface using theimplements keyword
2Then implements the requiredgreet method
3Any instance ofSystemGreeter is also an instance of theGreeter interface

An interface can extend another interface:

interface ExtendedGreeter extends Greeter {(1)    void sayBye(String name)}
1theExtendedGreeter interface extends theGreeter interface using theextends keyword

It is worth noting that for a class to be an instance of an interface, it has to be explicit. For example, the followingclass defines thegreet method as it is declared in theGreeter interface, but does not declareGreeter in itsinterfaces:

class DefaultGreeter {    void greet(String name) { println "Hello" }}greeter = new DefaultGreeter()assert !(greeter instanceof Greeter)

In other words, Groovy does not define structural typing. It is however possible to make an instance of an objectimplement an interface at runtime, using theas coercion operator:

greeter = new DefaultGreeter()(1)coerced = greeter as Greeter(2)assert coerced instanceof Greeter(3)
1create an instance ofDefaultGreeter that does not implement the interface
2coerce the instance into aGreeter at runtime
3the coerced instance implements theGreeter interface

You can see that there are two distinct objects: one is the source object, aDefaultGreeter instance, which does notimplement the interface. The other is an instance ofGreeter that delegates to the coerced object.

Groovytraits are close to interfaces, but support otherimportant features described elsewhere in this manual. If you need morepower than offered by interfaces, consider using traits.

While interfaces typically contain abstract method definitions,non-abstract methods are also possible.Variants allowed aredefault,static, orprivate:

  • Default methods provide a mechanism to evolve API functionality.You can add new functionality to existing interfaces but maintainbinary compatibility with code written for older versions of those interfaces.Shared functionality for default methods may be placed in private methods.

  • Static methods provide a mechanism to associate methods directlyan interface class without it impacting the OO contracts or being overridden.You could use them for writing factory or utility methods withoutcreating an additional utility class.

3. Class members

3.1. Constructors

Constructors are special methods used to initialize an object with a specific state. As with normal methods,it is possible for a class to declare more than one constructor, so long as each constructor has a uniquetype signature. If an object doesn’t require any parameters during construction, it may use ano-arg constructor.If no constructors are supplied, an empty no-arg constructor will be provided by the Groovy compiler.

Groovy supports two invocation styles:

  • positional parameters are used in a similar to how you would use Java constructors

  • named parameters allow you to specify parameter names when invoking the constructor.

3.1.1. Positional parameters

To create an object by using positional parameters, the respective class needs to declare one or moreconstructors. In the case of multiple constructors, each must have a unique type signature. The constructors can alsobe added to the class using thegroovy.transform.TupleConstructor annotation.

Typically, once at least one constructor is declared, the class can only be instantiated by having one of itsconstructors called. It is worth noting that, in this case, you can’t normally create the class with named parameters.Groovy does support named parameters so long as the class contains a no-arg constructor or provides a constructor whichtakes aMap argument as the first (and potentially only) argument - see the next section for details.

There are three forms of using a declared constructor. The first one is the normal Java way, with thenew keyword.The others rely on coercion of lists into the desired types. In this case, it is possible to coerce with theaskeyword and by statically typing the variable.

class PersonConstructor {    String name    Integer age    PersonConstructor(name, age) {(1)        this.name = name        this.age = age    }}def person1 = new PersonConstructor('Marie', 1)(2)def person2 = ['Marie', 2] as PersonConstructor(3)PersonConstructor person3 = ['Marie', 3](4)
1Constructor declaration
2Constructor invocation, classic Java way
3Constructor usage, using coercion withas keyword
4Constructor usage, using coercion in assignment

3.1.2. Named parameters

If no (or a no-arg) constructor is declared, it is possible to create objects by passing parameters in the form of amap (property/value pairs). This can be in handy in cases where one wants to allow several combinations of parameters.Otherwise, by using traditional positional parameters it would be necessary to declare all possible constructors.Having a constructor where the first (and perhaps only) argument is aMap argument is also supported - such aconstructor may also be added using thegroovy.transform.MapConstructor annotation.

class PersonWOConstructor {(1)    String name    Integer age}def person4 = new PersonWOConstructor()(2)def person5 = new PersonWOConstructor(name: 'Marie')(3)def person6 = new PersonWOConstructor(age: 1)(4)def person7 = new PersonWOConstructor(name: 'Marie', age: 2)(5)
1No constructor declared
2No parameters given in the instantiation
3name parameter given in the instantiation
4age parameter given in the instantiation
5name andage parameters given in the instantiation

It is important to highlight, however, that this approach gives more power to the constructor caller,while imposing an increased responsibility on the caller to get the names and value types correct.Thus, if greater control is desired, declaring constructors using positional parameters might be preferred.

Notes:

  • While the example above supplied no constructor, you can also supply a no-arg constructoror a constructor where the first argument is aMap, most typically it’s the only argument.

  • When no (or a no-arg) constructor is declared, Groovy replaces the named constructor call by a callto the no-arg constructor followed by calls to the setter for each supplied named property.

  • When the first argument is a Map, Groovy combines all named parameters into a Map (regardless of ordering)and supplies the map as the first parameter. This can be a good approach if your properties are declared asfinal (since they will be set in the constructor rather than after the fact with setters).

  • You can support both named and positional constructionby supply both positional constructors as well as a no-arg or Map constructor.

  • You can support hybrid construction by having a constructor where the first argumentis a Map but there are also additional positional parameters. Use this style with caution.

3.2. Methods

Groovy methods are quite similar to other languages. Some peculiarities will be shown in the next subsections.

3.2.1. Method definition

A method is defined with a return type or with thedef keyword, to make the return type untyped. A method can also receive any number of arguments, which may not have their types explicitly declared. Java modifiers can be used normally, and if no visibility modifier is provided, the method is public.

Methods in Groovy always return some value. If noreturn statement is provided, the value evaluated in the last line executed will be returned. For instance, note that none of the following methods uses thereturn keyword.

def someMethod() { 'method called' }(1)String anotherMethod() { 'another method called' }(2)def thirdMethod(param1) { "$param1 passed" }(3)static String fourthMethod(String param1) { "$param1 passed" }(4)
1Method with no return type declared and no parameter
2Method with explicit return type and no parameter
3Method with a parameter with no type defined
4Static method with a String parameter

3.2.2. Named parameters

Like constructors, normal methods can also be called with named parameters.To support this notation, a convention is used where the first argument to the method is aMap.In the method body, the parameter values can be accessed as in normal maps (map.key).If the method has just a single Map argument, all supplied parameters must be named.

def foo(Map args) { "${args.name}: ${args.age}" }foo(name: 'Marie', age: 1)
Mixing named and positional parameters

Named parameters can be mixed with positional parameters.The same convention applies, in this case, in addition to theMap argument as the first argument,the method in question will have additional positional arguments as needed.Supplied positional parameters when calling the method must be in order.The named parameters can be in any position. They are grouped into the map and supplied asthe first parameter automatically.

def foo(Map args, Integer number) { "${args.name}: ${args.age}, and the number is ${number}" }foo(name: 'Marie', age: 1, 23)(1)foo(23, name: 'Marie', age: 1)(2)
1Method call with additionalnumber argument ofInteger type
2Method call with changed order of arguments

If we don’t have the Map as the first argument, then a Map must be supplied for that argument instead of named parameters.Failure to do so will lead togroovy.lang.MissingMethodException:

def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }foo(name: 'Marie', age: 1, 23)(1)
1Method call throwsgroovy.lang.MissingMethodException: No signature of method: foo() is applicable for argument types: (LinkedHashMap, Integer) values: [[name:Marie, age:1], 23], because the named argumentMap parameter is not defined as the first argument

Above exception can be avoided if we replace named arguments with an explicitMap argument:

def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }foo(23, [name: 'Marie', age: 1])(1)
1ExplicitMap argument in place of named arguments makes invocation valid
Although Groovy allows you to mix named and positional parameters, it can lead to unnecessary confusion.Mix named and positional arguments with caution.

3.2.3. Default arguments

Default arguments make parameters optional.If the argument is not supplied, the method assumes a default value.

def foo(String par1, Integer par2 = 1) { [name: par1, age: par2] }assert foo('Marie').age == 1

Parameters are dropped from the right, however mandatory parameters are never dropped.

def baz(a = 'a', int b, c = 'c', boolean d, e = 'e') { "$a $b $c $d $e" }assert baz(42, true) == 'a 42 c true e'assert baz('A', 42, true) == 'A 42 c true e'assert baz('A', 42, 'C', true) == 'A 42 C true e'assert baz('A', 42, 'C', true, 'E') == 'A 42 C true E'

The same rule applies to constructors as well as methods.If using@TupleConstructor, additional configuration options apply.

3.2.4. Varargs

Groovy supports methods with a variable number of arguments. They are defined like this:def foo(p1, …​, pn, T…​ args).Herefoo supportsn arguments by default, but also an unspecified number of further arguments exceedingn.

def foo(Object... args) { args.length }assert foo() == 0assert foo(1) == 1assert foo(1, 2) == 2

This example defines a methodfoo, that can take any number of arguments, including no arguments at all.args.length will return the number of arguments given. Groovy allowsT[] as an alternative notation toT…​.That means any method with an array as last parameter is seen by Groovy as a method that can take a variable number of arguments.

def foo(Object[] args) { args.length }assert foo() == 0assert foo(1) == 1assert foo(1, 2) == 2

If a method with varargs is called withnull as the vararg parameter, then the argument will benull and not an array of length one withnull as the only element.

def foo(Object... args) { args }assert foo(null) == null

If a varargs method is called with an array as an argument, then the argument will be that array instead of an array of length one containing the given array as the only element.

def foo(Object... args) { args }Integer[] ints = [1, 2]assert foo(ints) == [1, 2]

Another important point are varargs in combination with method overloading. In case of method overloading Groovy will select the most specific method.For example if a methodfoo takes a varargs argument of typeT and another methodfoo also takes one argument of typeT, the second method is preferred.

def foo(Object... args) { 1 }def foo(Object x) { 2 }assert foo() == 1assert foo(1) == 2assert foo(1, 2) == 1

3.2.5. Method selection algorithm

Dynamic Groovy supportsmultiple dispatch (aka multimethods).When calling a method, the actual method invoked is determineddynamically based on the run-time type of methods arguments.First the method name and number of arguments will be considered (including allowance for varargs),and then the type of each argument.Consider the following method definitions:

def method(Object o1, Object o2) { 'o/o' }def method(Integer i, String  s) { 'i/s' }def method(String  s, Integer i) { 's/i' }

Perhaps as expected, callingmethod withString andInteger parameters,invokes our third method definition.

assert method('foo', 42) == 's/i'

Of more interest here is when the types are not known at compile time.Perhaps the arguments are declared to be of typeObject (a list of such objects in our case).Java would determine that themethod(Object, Object) variant would be selected in allcases (unless casts were used) but as can be seen in the following example, Groovy uses the runtime typeand will invoke each of our methods once (and normally, no casting is needed):

List<List<Object>> pairs = [['foo', 1], [2, 'bar'], [3, 4]]assert pairs.collect { a, b -> method(a, b) } == ['s/i', 'i/s', 'o/o']

For each of the first two of our three method invocations an exact match of argument types was found.For the third invocation, an exact match ofmethod(Integer, Integer) wasn’t found butmethod(Object, Object)is still valid and will be selected.

Method selection then is about finding theclosest fit from valid method candidates which have compatibleparameter types.So,method(Object, Object) is also valid for the first two invocations but is not as close a matchas the variants where types exactly match.To determine the closest fit, the runtime has a notion of thedistance an actual argumenttype is away from the declared parameter type and tries to minimise the total distance across all parameters.

The following table illustrates some factors which affect the distance calculation.

AspectExample

Directly implemented interfaces match more closely than ones from further up the inheritance hierarchy.

Given these interface and method definitions:

interface I1 {}interface I2 extends I1 {}interface I3 {}class Clazz implements I3, I2 {}def method(I1 i1) { 'I1' }def method(I3 i3) { 'I3' }

The directly implemented interface will match:

assert method(new Clazz()) == 'I3'

An Object array is preferred over an Object.

def method(Object[] arg) { 'array' }def method(Object arg) { 'object' }assert method([] as Object[]) == 'array'

Non-vararg variants are favored over vararg variants.

def method(String s, Object... vargs) { 'vararg' }def method(String s) { 'non-vararg' }assert method('foo') == 'non-vararg'

If two vararg variants are applicable, the one which uses the minimum number of vararg arguments is preferred.

def method(String s, Object... vargs) { 'two vargs' }def method(String s, Integer i, Object... vargs) { 'one varg' }assert method('foo', 35, new Date()) == 'one varg'

Interfaces are preferred over super classes.

interface I {}class Base {}class Child extends Base implements I {}def method(Base b) { 'superclass' }def method(I i) { 'interface' }assert method(new Child()) == 'interface'

For a primitive argument type, a declared parameter type which is the same or slightly larger is preferred.

def method(Long l) { 'Long' }def method(Short s) { 'Short' }def method(BigInteger bi) { 'BigInteger' }assert method(35) == 'Long'

In the case where two variants have exactly the same distance, this is deemed ambiguous and will cause a runtime exception:

def method(Date d, Object o) { 'd/o' }def method(Object o, String s) { 'o/s' }def ex = shouldFail {    println method(new Date(), 'baz')}assert ex.message.contains('Ambiguous method overloading')

Casting can be used to select the desired method:

assert method(new Date(), (Object)'baz') == 'd/o'assert method((Object)new Date(), 'baz') == 'o/s'

3.2.6. Exception declaration

Groovy automatically allows you to treat checked exceptions like unchecked exceptions.This means that you don’t need to declare any checked exceptions that a method may throwas shown in the following example which can throw aFileNotFoundException if the file isn’t found:

def badRead() {    new File('doesNotExist.txt').text}shouldFail(FileNotFoundException) {    badRead()}

Nor will you be required to surround the call to thebadRead method in the previous example within a try/catchblock - though you are free to do so if you wish.

If you wish to declare any exceptions that your code might throw (checked or otherwise) you are free to do so.Adding exceptions won’t change how the code is used from any other Groovy code but can be seen as documentationfor the human reader of your code. The exceptions will become part of the method declaration in the bytecode,so if your code might be called from Java, it might be useful to include them.Using an explicit checked exception declaration is illustrated in the following example:

def badRead() throws FileNotFoundException {    new File('doesNotExist.txt').text}shouldFail(FileNotFoundException) {    badRead()}

3.3. Fields and Properties

3.3.1. Fields

A field is a member of a class, interface or trait which stores data.A field defined in a Groovy source file has:

  • a mandatoryaccess modifier (public,protected, orprivate)

  • one or more optionalmodifiers (static,final,synchronized)

  • an optionaltype

  • a mandatoryname

class Data {    private int id(1)    protected String description(2)    public static final boolean DEBUG = false(3)}
1aprivate field namedid, of typeint
2aprotected field nameddescription, of typeString
3apublic static final field namedDEBUG of typeboolean

A field may be initialized directly at declaration:

class Data {    private String id = IDGenerator.next()(1)    // ...}
1the private fieldid is initialized withIDGenerator.next()

It is possible to omit the type declaration of a field. This is however considered a bad practice and in general itis a good idea to use strong typing for fields:

class BadPractice {    private mapping(1)}class GoodPractice {    private Map<String,String> mapping(2)}
1the fieldmapping doesn’t declare a type
2the fieldmapping has a strong type

The difference between the two is important if you want to use optional type checking later.It is also important as a way to document the class design.However, in some cases like scripting or if you want to rely on duck typing it may be usefulto omit the type.

3.3.2. Properties

A property is an externally visible feature of a class. Rather than just using a public field to representsuch features (which provides a more limited abstraction and would restrict refactoring possibilities),the typical approach in Java is to follow the conventions outlined in theJavaBeans Specification, i.e. represent the property using acombination of a private backing field and getters/setters. Groovy follows these same conventionsbut provides a simpler way to define the property. You can define a property with:

  • anabsent access modifier (nopublic,protected orprivate)

  • one or more optionalmodifiers (static,final,synchronized)

  • an optionaltype

  • a mandatoryname

Groovy will then generate the getters/setters appropriately. For example:

class Person {    String name(1)    int age(2)}
1creates a backingprivate String name field, agetName and asetName method
2creates a backingprivate int age field, agetAge and asetAge method

If a property is declaredfinal, no setter is generated:

class Person {    final String name(1)    final int age(2)    Person(String name, int age) {        this.name = name(3)        this.age = age(4)    }}
1defines a read-only property of typeString
2defines a read-only property of typeint
3assigns thename parameter to thename field
4assigns theage parameter to theage field

Properties are accessed by name and will call the getter or setter transparently, unless the code is in the classwhich defines the property:

class Person {    String name    void name(String name) {        this.name = "Wonder $name"(1)    }    String title() {        this.name(2)    }}def p = new Person()p.name = 'Diana'(3)assert p.name == 'Diana'(4)p.name('Woman')(5)assert p.title() == 'Wonder Woman'(6)
1this.name will directly access the field because the property is accessed from within the class that defines it
2similarly a read access is done directly on thename field
3write access to the property is done outside of thePerson class so it will implicitly callsetName
4read access to the property is done outside of thePerson class so it will implicitly callgetName
5this will call thename method onPerson which performs a direct access to the field
6this will call thetitle method onPerson which performs a direct read access to the field

It is worth noting that this behavior of accessing the backing field directly is done in order to prevent a stackoverflow when using the property access syntax within a class that defines the property.

It is possible to list the properties of a class thanks to the metaproperties field of an instance:

class Person {    String name    int age}def p = new Person()assert p.properties.keySet().containsAll(['name','age'])

By convention, Groovy will recognize properties even if there is no backing fieldprovided there are getters or settersthat follow the Java Beans specification. For example:

class PseudoProperties {    // a pseudo property "name"    void setName(String name) {}    String getName() {}    // a pseudo read-only property "age"    int getAge() { 42 }    // a pseudo write-only property "groovy"    void setGroovy(boolean groovy) {  }}def p = new PseudoProperties()p.name = 'Foo'(1)assert p.age == 42(2)p.groovy = true(3)
1writingp.name is allowed because there is a pseudo-propertyname
2readingp.age is allowed because there is a pseudo-readonly propertyage
3writingp.groovy is allowed because there is a pseudo-write-only propertygroovy

This syntactic sugar is at the core of many DSLs written in Groovy.

Property naming conventions

It is generally recommended that the first two letters of a property name arelowercase and for multi-word properties that camel case is used.In those cases, generated getters and setters will have a name formed by capitalizing theproperty name and adding aget orset prefix (or optionally "is" for a boolean getter).So,getLength would be a getter for alength property andsetFirstName a setter for afirstName property.isEmpty might be the getter method name for a property namedempty.

Property names starting with a capital letter would have getters/setters with just the prefix added.So, the propertyFoo is allowed even though it isn’t following the recommended naming conventions.For this property, the accessor methods would besetFoo andgetFoo.A consequence of this is that you aren’t allowed to have both afoo and aFoo property,since they would have the same named accessor methods.

The JavaBeans specification makes a special case for properties which typically might be acronyms.If the first two letters of a property name are uppercase, no capitalization is performed(or more importantly, no decapitalization is done if generating the property name from the accessor method name).So,getURL would be the getter for aURL property.

Because of the special "acronym handling" property naming logic in the JavaBeans specification, theconversion to and from a property name are non-symmetrical. This leads to some strange edge cases.Groovy adopts a naming convention that avoids one ambiguity that might seem a little strange butwas popular at the time of Groovy’s design and has remained (so far) for historical reasons.Groovy looks at the second letter of a property name. If that is a capital, the property is deemed to beone of the acronym style properties and no capitalization is done, otherwise normal capitalization is done.Although wenever recommend it, it does allow you to have what might seem like "duplicate named" properties,e.g. you can haveaProp andAProp, orpNAME andPNAME. The getters would begetaProp andgetAProp,andgetpNAME andgetPNAME respectively.

Modifiers on a property

We have already seen that properties are defined by omitting the visibility modifier.In general, any other modifiers, e.g.transient would be copied across to the field.Two special cases are worth noting:

  • final, which we saw earlier is for read-only properties, is copied onto the backing field but also causes no setter to be defined

  • static is copied onto the backing field but also causes the accessor methods to be static

If you wish to have a modifier likefinal also carried over to the accessor methods,you can write your properties long hand or consider using asplit property definition.

Annotations on a property

Annotations, including those associated with AST transforms,are copied on to the backing field for the property.This allows AST transforms which are applicable to fields tobe applied to properties, e.g.:

class Animal {    int lowerCount = 0    @Lazy String name = { lower().toUpperCase() }()    String lower() { lowerCount++; 'sloth' }}def a = new Animal()assert a.lowerCount == 0(1)assert a.name == 'SLOTH'(2)assert a.lowerCount == 1(3)
1Confirms no eager initialization
2Normal property access
3Confirms initialization upon property access
Split property definition with an explicit backing field

Groovy’s property syntax is a convenient shorthand when your class designfollows certain conventions which align with common JavaBean practice.If your class doesn’t exactly fit these conventions,you can certainly write the getter, setter and backing field long hand like you would in Java.However, Groovy does provide a split definition capability which still providesa shortened syntax while allowing slight adjustments to the conventions.For a split definition, you write a field and a property with the same name and type.Only one of the field or property may have an initial value.

For split properties, annotations on the field remain on the backing field for the property.Annotations on the property part of the definition are copied onto the getter and setter methods.

This mechanism allows a number of common variations that property users may wishto use if the standard property definition doesn’t exactly fit their needs.For example, if the backing field should beprotected rather thanprivate:

class HasPropertyWithProtectedField {    protected String name(1)    String name(2)}
1Protected backing field for name property instead of normal private one
2Declare name property

Or, the same example but with a package-private backing field:

class HasPropertyWithPackagePrivateField {    String name(1)    @PackageScope String name(2)}
1Declare name property
2Package-private backing field for name property instead of normal private one

As a final example, we may wish to apply method-related AST transforms,or in general, any annotation to the setters/getters,e.g. to have the accessors be synchronized:

class HasPropertyWithSynchronizedAccessorMethods {    private String name(1)    @Synchronized String name(2)}
1Backing field for name property
2Declare name property with annotation for setter/getter
Explicit accessor methods

The automatic generation of accessor methods doesn’t occur if thereis an explicit definition of the getter or setter in the class.This allows you to modify the normal behavior of such a getter or setter if needed.Inherited accessor methods aren’t normally considered but if an inheritedaccessor method is marked final, that will also cause no generation of anadditional accessor method to honor thefinal requirement of no subclassing of such methods.

4. Annotations

4.1. Annotation definition

An annotation is a kind of special interface dedicated at annotating elements of the code. An annotation is a type whichsuperinterface is thejava.lang.annotation.Annotation interface. Annotations are declared in a verysimilar way to interfaces, using the@interface keyword:

@interface SomeAnnotation {}

An annotation may define members in the form of methods without bodies and an optional default value. The possiblemember types are limited to:

For example:

@interface SomeAnnotation {    String value()(1)}@interface SomeAnnotation {    String value() default 'something'(2)}@interface SomeAnnotation {    int step()(3)}@interface SomeAnnotation {    Class appliesTo()(4)}@interface SomeAnnotation {}@interface SomeAnnotations {    SomeAnnotation[] value()(5)}enum DayOfWeek { mon, tue, wed, thu, fri, sat, sun }@interface Scheduled {    DayOfWeek dayOfWeek()(6)}
1an annotation defining avalue member of typeString
2an annotation defining avalue member of typeString with a default value ofsomething
3an annotation defining astep member of type the primitive typeint
4an annotation defining aappliesTo member of typeClass
5an annotation defining avalue member which type is an array of another annotation type
6an annotation defining adayOfWeek member which type is the enumeration typeDayOfWeek

Unlike in the Java language, in Groovy, an annotation can be used to alter the semantics of the language. It is especiallytrue of AST transformations which will generate code based on annotations.

4.1.1. Annotation placement

An annotation can be applied on various elements of the code:

@SomeAnnotation(1)void someMethod() {    // ...}@SomeAnnotation(2)class SomeClass {}@SomeAnnotation String someVar(3)
1@SomeAnnotation applies to thesomeMethod method
2@SomeAnnotation applies to theSomeClass class
3@SomeAnnotation applies to thesomeVar variable

In order to limit the scope where an annotation can be applied, it is necessary to declare it on the annotationdefinition, using thejava.lang.annotation.Target annotation. For example, here is how you woulddeclare that an annotation can be applied to a class or a method:

import java.lang.annotation.ElementTypeimport java.lang.annotation.Target@Target([ElementType.METHOD, ElementType.TYPE])(1)@interface SomeAnnotation {}(2)
1the@Target annotation is meant to annotate an annotation with a scope.
2@SomeAnnotation will therefore only be allowed onTYPE orMETHOD

The list of possible targets is available in thejava.lang.annotation.ElementType.

Groovy does not support thejava.lang.annotation.ElementType#TYPE_PARAMETER andjava.lang.annotation.ElementType#TYPE_PARAMETER element types which were introduced in Java 8.

4.1.2. Annotation member values

When an annotation is used, it is required to set at least all members that do not have a default value. For example:

@interface Page {    int statusCode()}@Page(statusCode=404)void notFound() {    // ...}

However it is possible to omitvalue= in the declaration of the value of an annotation if the membervalue is theonly one being set:

@interface Page {    String value()    int statusCode() default 200}@Page(value='/home')(1)void home() {    // ...}@Page('/users')(2)void userList() {    // ...}@Page(value='error',statusCode=404)(3)void notFound() {    // ...}
1we can omit thestatusCode because it has a default value, butvalue needs to be set
2sincevalue is the only mandatory member without a default, we can omitvalue=
3if bothvalue andstatusCode need to be set, it is required to usevalue= for the defaultvalue member

4.1.3. Retention policy

The visibility of an annotation depends on its retention policy. The retention policy of an annotation is set usingthejava.lang.annotation.Retention annotation:

import java.lang.annotation.Retentionimport java.lang.annotation.RetentionPolicy@Retention(RetentionPolicy.SOURCE)(1)@interface SomeAnnotation {}(2)
1the@Retention annotation annotates the@SomeAnnotation annotation
2so@SomeAnnotation will have aSOURCE retention

The list of possible retention targets and description is available in thejava.lang.annotation.RetentionPolicy enumeration. Thechoice usually depends on whether you want an annotation to be visible atcompile time or runtime.

4.1.4. Closure annotation parameters

An interesting feature of annotations in Groovy is that you can use a closure as an annotation value. Thereforeannotations may be used with a wide variety of expressions and still have IDE support. For example, imagine aframework where you want to execute some methods based on environmental constraints like the JDK version or the OS.One could write the following code:

class Tasks {    Set result = []    void alwaysExecuted() {        result << 1    }    @OnlyIf({ jdk>=6 })    void supportedOnlyInJDK6() {        result << 'JDK 6'    }    @OnlyIf({ jdk>=7 && windows })    void requiresJDK7AndWindows() {        result << 'JDK 7 Windows'    }}

For the@OnlyIf annotation to accept aClosure as an argument, you only have to declare thevalue as aClass:

@Retention(RetentionPolicy.RUNTIME)@interface OnlyIf {    Class value()(1)}

To complete the example, let’s write a sample runner that would use that information:

class Runner {    static <T> T run(Class<T> taskClass) {        def tasks = taskClass.newInstance()(1)        def params = [jdk: 6, windows: false](2)        tasks.class.declaredMethods.each { m ->(3)            if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) {(4)                def onlyIf = m.getAnnotation(OnlyIf)(5)                if (onlyIf) {                    Closure cl = onlyIf.value().newInstance(tasks,tasks)(6)                    cl.delegate = params(7)                    if (cl()) {(8)                        m.invoke(tasks)(9)                    }                } else {                    m.invoke(tasks)(10)                }            }        }        tasks(11)    }}
1create a new instance of the class passed as an argument (the task class)
2emulate an environment which is JDK 6 and not Windows
3iterate on all declared methods of the task class
4if the method is public and takes no arguments
5try to find the@OnlyIf annotation
6if it is found get thevalue and create a newClosure out of it
7set thedelegate of the closure to our environment variable
8call the closure, which is the annotation closure. It will return aboolean
9if it istrue, call the method
10if the method is not annotated with@OnlyIf, execute the method anyway
11after that, return the task object

Then the runner can be used this way:

def tasks = Runner.run(Tasks)assert tasks.result == [1, 'JDK 6'] as Set

4.2. Meta-annotations

4.2.1. Declaring meta-annotations

Meta-annotations, also known as annotation aliases are annotations thatare replaced at compile time by other annotations (one meta-annotationis an alias for one or more annotations). Meta-annotations can be used toreduce the size of code involving multiple annotations.

Let’s start with a simple example. Imagine you have the @Serviceand @Transactional annotations and that you want to annotate a classwith both:

@Service@Transactionalclass MyTransactionalService {}

Given the multiplication of annotations that you could add to the same class, a meta-annotationcould help by reducing the two annotations with a single one having the very same semantics. For example,we might want to write this instead:

@TransactionalService(1)class MyTransactionalService {}
1@TransactionalService is a meta-annotation

A meta-annotation is declared as a regular annotation but annotated with@AnnotationCollector and thelist of annotations it is collecting. In our case, the@TransactionalService annotation can be written:

import groovy.transform.AnnotationCollector@Service(1)@Transactional(2)@AnnotationCollector(3)@interface TransactionalService {}
1annotate the meta-annotation with@Service
2annotate the meta-annotation with@Transactional
3annotate the meta-annotation with@AnnotationCollector

4.2.2. Behavior of meta-annotations

Groovy supports bothprecompiled andsource formmeta-annotations. This means that your meta-annotation may beprecompiled, or you can have it in the same source tree as the one youare currently compiling.

INFO: Meta-annotations are a Groovy-only feature. There isno chance for you to annotate a Java class with a meta-annotation andhope it will do the same as in Groovy. Likewise, you cannot write ameta-annotation in Java: both the meta-annotation definition and usagehave to be Groovy code. But you can happily collect Java annotationsand Groovy annotations within your meta-annotation.

When the Groovy compiler encounters a class annotated with ameta-annotation, it replaces it with the collected annotations. So,in our previous example, it willreplace @TransactionalService with @Transactional and @Service:

def annotations = MyTransactionalService.annotations*.annotationType()assert (Service in annotations)assert (Transactional in annotations)

The conversion from a meta-annotation to the collected annotations is performed during thesemantic analysis compilation phase. 

In addition to replacing the alias with the collected annotations, a meta-annotation is capable ofprocessing them, including arguments.

4.2.3. Meta-annotation parameters

Meta-annotations can collect annotations which have parameters. To illustrate this,we will imagine two annotations, each of them accepting one argument:

@Timeout(after=3600)@Dangerous(type='explosive')

And suppose that you want to create a meta-annotation named @Explosive:

@Timeout(after=3600)@Dangerous(type='explosive')@AnnotationCollectorpublic @interface Explosive {}

By default, when the annotations are replaced, they will get theannotation parameter valuesas they were defined in the alias. More interesting,the meta-annotation supports overriding specific values:

@Explosive(after=0)(1)class Bomb {}
1theafter value provided as a parameter to @Explosive overrides the one defined in the@Timeout annotation

If two annotations define the same parameter name, the default processorwill copy the annotation value to all annotations that accept this parameter:

@Retention(RetentionPolicy.RUNTIME)public @interface Foo {   String value()(1)}@Retention(RetentionPolicy.RUNTIME)public @interface Bar {    String value()(2)}@Foo@Bar@AnnotationCollectorpublic @interface FooBar {}(3)@Foo('a')@Bar('b')class Bob {}(4)assert Bob.getAnnotation(Foo).value() == 'a'(5)println Bob.getAnnotation(Bar).value() == 'b'(6)@FooBar('a')class Joe {}(7)assert Joe.getAnnotation(Foo).value() == 'a'(8)println Joe.getAnnotation(Bar).value() == 'a'(9)
1the@Foo annotation defines thevalue member of typeString
2the@Bar annotation also defines thevalue member of typeString
3the@FooBar meta-annotation aggregates@Foo and@Bar
4classBob is annotated with@Foo and@Bar
5the value of the@Foo annotation onBob isa
6while the value of the@Bar annotation onBob isb
7classJoe is annotated with@FooBar
8then the value of the@Foo annotation onJoe isa
9and the value of the@Bar annotation onJoe is alsoa

In the second case, the meta-annotation value was copied inboth@Foo and@Bar annotations.

It is a compile time error if the collected annotations define the same memberswith incompatible types. For example if on the previous example@Foo defined a value oftypeString but@Bar defined a value of typeint.

It is however possible to customize the behavior of meta-annotations and describe how collectedannotations are expanded. We’ll look at how to do that shortly but first there is an advancedprocessing option to cover.

4.2.4. Handling duplicate annotations in meta-annotations

The@AnnotationCollector annotation supports amode parameter which can be used toalter how the default processor handles annotation replacement in the presence ofduplicate annotations.

INFO: Custom processors (discussed next) may or may not support this parameter.

As an example, suppose you create a meta-annotation containing the@ToString annotationand then place your meta-annotation on a class that already has an explicit@ToStringannotation. Should this be an error? Should both annotations be applied? Does one takepriority over the other? There is no correct answer. In some scenarios it might bequite appropriate for any of these answers to be correct. So, rather than trying topreempt one correct way to handle the duplicate annotation issue, Groovy lets youwrite your own custom meta-annotation processors (covered next) and lets you writewhatever checking logic you like within AST transforms - which are a frequent target foraggregating. Having said that, by simply setting themode, a number of commonlyexpected scenarios are handled automatically for you within any extra coding.The behavior of themode parameter is determined by theAnnotationCollectorModeenum value chosen and is summarized in the following table.

Mode

Description

DUPLICATE

Annotations from the annotation collection will always be inserted. After all transforms have been run, it will be an error if multiple annotations (excluding those with SOURCE retention) exist.

PREFER_COLLECTOR

Annotations from the collector will be added and any existing annotations with the same name will be removed.

PREFER_COLLECTOR_MERGED

Annotations from the collector will be added and any existing annotations with the same name will be removed but any new parameters found within existing annotations will be merged into the added annotation.

PREFER_EXPLICIT

Annotations from the collector will be ignored if any existing annotations with the same name are found.

PREFER_EXPLICIT_MERGED

Annotations from the collector will be ignored if any existing annotations with the same name are found but any new parameters on the collector annotation will be added to existing annotations.

4.2.5. Custom meta-annotation processors

A custom annotation processor will let you choose how to expand ameta-annotation into collected annotations. The behaviour of the meta-annotation is,in this case, totally up to you. To do this, you must:

To illustrate this, we are going to explore how the meta-annotation@CompileDynamic is implemented.

@CompileDynamic is a meta-annotation that expands itselfto @CompileStatic(TypeCheckingMode.SKIP). The problem is that thedefault meta annotation processor doesn’t support enums and theannotation valueTypeCheckingMode.SKIP is one.

The naive implementation here would not work:

@CompileStatic(TypeCheckingMode.SKIP)@AnnotationCollectorpublic @interface CompileDynamic {}

Instead, we will define it like this:

@AnnotationCollector(processor = "org.codehaus.groovy.transform.CompileDynamicProcessor")public @interface CompileDynamic {}

The first thing you may notice is that our interface is no longerannotated with @CompileStatic. The reason for this is that we rely ontheprocessor parameter instead, that references a class whichwill generate the annotation.

Here is how the custom processor is implemented:

CompileDynamicProcessor.groovy
@CompileStatic(1)class CompileDynamicProcessor extends AnnotationCollectorTransform {(2)    private static final ClassNode CS_NODE = ClassHelper.make(CompileStatic)(3)    private static final ClassNode TC_NODE = ClassHelper.make(TypeCheckingMode)(4)    List<AnnotationNode> visit(AnnotationNode collector,(5)                               AnnotationNode aliasAnnotationUsage,(6)                               AnnotatedNode aliasAnnotated,(7)                               SourceUnit source) {(8)        def node = new AnnotationNode(CS_NODE)(9)        def enumRef = new PropertyExpression(            new ClassExpression(TC_NODE), "SKIP")(10)        node.addMember("value", enumRef)(11)        Collections.singletonList(node)(12)    }}
1our custom processor is written in Groovy, and for better compilation performance, we use static compilation
2the custom processor has to extendorg.codehaus.groovy.transform.AnnotationCollectorTransform
3create a class node representing the@CompileStatic annotation type
4create a class node representing theTypeCheckingMode enum type
5collector is the@AnnotationCollector node found in the meta-annotation. Usually unused.
6aliasAnnotationUsage is the meta-annotation being expanded, here it is@CompileDynamic
7aliasAnnotated is the node being annotated with the meta-annotation
8sourceUnit is theSourceUnit being compiled
9we create a new annotation node for@CompileStatic
10we create an expression equivalent toTypeCheckingMode.SKIP
11we add that expression to the annotation node, which is now@CompileStatic(TypeCheckingMode.SKIP)
12return the generated annotation

In the example, thevisit method is the only method which has to be overridden. It is meant to return a list ofannotation nodes that will be added to the node annotated with the meta-annotation. In this example, we return asingle one corresponding to@CompileStatic(TypeCheckingMode.SKIP).

5. Traits

Traits are a structural construct of the language which allows:

  • composition of behaviors

  • runtime implementation of interfaces

  • behavior overriding

  • compatibility with static type checking/compilation

They can be seen asinterfaces carrying bothdefault implementations andstate. A trait is defined using thetrait keyword:

trait FlyingAbility {(1)        String fly() { "I'm flying!" }(2)}
1declaration of a trait
2declaration of a method inside a trait

Then it can be used like a normal interface using theimplements keyword:

class Bird implements FlyingAbility {}(1)def b = new Bird()(2)assert b.fly() == "I'm flying!"(3)
1Adds the traitFlyingAbility to theBird class capabilities
2instantiate a newBird
3theBird class automatically gets the behavior of theFlyingAbility trait

Traits allow a wide range of capabilities, from simple composition to testing, which are described thoroughly in this section.

5.1. Methods

5.1.1. Public methods

Declaring a method in a trait can be done like any regular method in a class:

trait FlyingAbility {(1)        String fly() { "I'm flying!" }(2)}
1declaration of a trait
2declaration of a method inside a trait

5.1.2. Abstract methods

In addition, traits may declareabstract methods too, which therefore need to be implemented in the class implementing the trait:

trait Greetable {    abstract String name()(1)    String greeting() { "Hello, ${name()}!" }(2)}
1implementing class will have to declare thename method
2can be mixed with a concrete method

Then the trait can be used like this:

class Person implements Greetable {(1)    String name() { 'Bob' }(2)}def p = new Person()assert p.greeting() == 'Hello, Bob!'(3)
1implement the traitGreetable
2sincename was abstract, it is required to implement it
3thengreeting can be called

5.1.3. Private methods

Traits may also define private methods. Those methods will not appear in the trait contract interface:

trait Greeter {    private String greetingMessage() {(1)        'Hello from a private method!'    }    String greet() {        def m = greetingMessage()(2)        println m        m    }}class GreetingMachine implements Greeter {}(3)def g = new GreetingMachine()assert g.greet() == "Hello from a private method!"(4)try {    assert g.greetingMessage()(5)} catch (MissingMethodException e) {    println "greetingMessage is private in trait"}
1define a private methodgreetingMessage in the trait
2the publicgreet message callsgreetingMessage by default
3create a class implementing the trait
4greet can be called
5but notgreetingMessage
Traits only supportpublic andprivate methods. Neitherprotected norpackage private scopes aresupported.

5.1.4. Final methods

If we have a class implementing a trait, conceptually implementations from the trait methodsare "inherited" into the class. But, in reality, there is no base class containing suchimplementations. Rather, they are woven directly into the class. A final modifier on a methodjust indicates what the modifier will be for the woven method. While it would likely beconsidered bad style to inherit and override or multiply inherit methods with the samesignature but a mix of final and non-final variants, Groovy doesn’t prohibit this scenario.Normal method selection applies and the modifier used will be determined from the resulting method.You might consider creating a base class which implements the desired trait(s) if youwant trait implementation methods that can’t be overridden.

5.2. The meaning of this

this represents the implementing instance. Think of a trait as a superclass. This means that when you write:

trait Introspector {    def whoAmI() { this }}class Foo implements Introspector {}def foo = new Foo()

then calling:

foo.whoAmI()

will return the same instance:

assert foo.whoAmI().is(foo)

5.3. Interfaces

Traits may implement interfaces, in which case the interfaces are declared using theimplements keyword:

interface Named {(1)    String name()}trait Greetable implements Named {(2)    String greeting() { "Hello, ${name()}!" }}class Person implements Greetable {(3)    String name() { 'Bob' }(4)}def p = new Person()assert p.greeting() == 'Hello, Bob!'(5)assert p instanceof Named(6)assert p instanceof Greetable(7)
1declaration of a normal interface
2addNamed to the list of implemented interfaces
3declare a class that implements theGreetable trait
4implement the missingname method
5thegreeting implementation comes from the trait
6make surePerson implements theNamed interface
7make surePerson implements theGreetable trait

5.4. Properties

A trait may define properties, like in the following example:

trait Named {    String name(1)}class Person implements Named {}(2)def p = new Person(name: 'Bob')(3)assert p.name == 'Bob'(4)assert p.getName() == 'Bob'(5)
1declare a propertyname inside a trait
2declare a class which implements the trait
3the property is automatically made visible
4it can be accessed using the regular property accessor
5or using the regular getter syntax

5.5. Fields

5.5.1. Private fields

Since traits allow the use of private methods, it can also be interesting to use private fields to store state. Traitswill let you do that:

trait Counter {    private int count = 0(1)    int count() { count += 1; count }(2)}class Foo implements Counter {}(3)def f = new Foo()assert f.count() == 1(4)assert f.count() == 2
1declare a private fieldcount inside a trait
2declare a public methodcount that increments the counter and returns it
3declare a class that implements theCounter trait
4thecount method can use the private field to keep state
This is a major difference withJava 8 virtual extension methods. While virtual extension methodsdo not carry state, traits can. Moreover, traits in Groovy are supported starting with Java 6, because their implementation does not rely on virtual extension methods. Thismeans that even if a trait can be seen from a Java class as a regular interface, that interface willnot have default methods, only abstract ones.

5.5.2. Public fields

Public fields work the same way as private fields, but in order to avoid thediamond problem,field names are remapped in the implementing class:

trait Named {    public String name(1)}class Person implements Named {}(2)def p = new Person()(3)p.Named__name = 'Bob'(4)
1declare a publicfield inside the trait
2declare a class implementing the trait
3create an instance of that class
4the 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 isString, the name of the package ismy.package, the name of the trait isFoo and the name of the field isbar,in the implementing class, the public field will appear as:

String my_package_Foo__bar
While traits support public fields, it is not recommended to use them and considered as a bad practice.

5.6. Composition of behaviors

Traits can be used to implement multiple inheritance in a controlled way. For example, we can have the following traits:

trait FlyingAbility {(1)        String fly() { "I'm flying!" }(2)}trait SpeakingAbility {    String speak() { "I'm speaking!" }}

And a class implementing both traits:

class Duck implements FlyingAbility, SpeakingAbility {}(1)def d = new Duck()(2)assert d.fly() == "I'm flying!"(3)assert d.speak() == "I'm speaking!"(4)
1theDuck class implements bothFlyingAbility andSpeakingAbility
2creates a new instance ofDuck
3we can call the methodfly fromFlyingAbility
4but also the methodspeak fromSpeakingAbility

Traits encourage the reuse of capabilities among objects, and the creation of new classes by the composition of existing behavior.

5.7. Overriding default methods

Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, wecan slightly change the example above, by having a duck which quacks:

class Duck implements FlyingAbility, SpeakingAbility {    String quack() { "Quack!" }(1)    String speak() { quack() }(2)}def d = new Duck()assert d.fly() == "I'm flying!"(3)assert d.quack() == "Quack!"(4)assert d.speak() == "Quack!"(5)
1define a method specific toDuck, namedquack
2override the default implementation ofspeak so that we usequack instead
3the duck is still flying, from the default implementation
4quack comes from theDuck class
5speak no longer uses the default implementation fromSpeakingAbility

5.8. Extending traits

5.8.1. Simple inheritance

Traits may extend another trait, in which case you must use theextends keyword:

trait Named {    String name(1)}trait Polite extends Named {(2)    String introduce() { "Hello, I am $name" }(3)}class Person implements Polite {}def p = new Person(name: 'Alice')(4)assert p.introduce() == 'Hello, I am Alice'(5)
1theNamed trait defines a singlename property
2thePolite traitextends theNamed trait
3Polite adds a new method which has access to thename property of the super-trait
4thename property is visible from thePerson class implementingPolite
5as is theintroduce method

5.8.2. Multiple inheritance

Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in theimplementsclause:

trait WithId {(1)    Long id}trait WithName {(2)    String name}trait Identified implements WithId, WithName {}(3)
1WithId trait defines theid property
2WithName trait defines thename property
3Identified is a trait which inherits bothWithId andWithName

5.9. Duck typing and traits

5.9.1. Dynamic code

Traits can call any dynamic code, like a normal Groovy class. This means that you can, in the body of a method, callmethods 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:

trait SpeakingDuck {    String speak() { quack() }(1)}class Duck implements SpeakingDuck {    String methodMissing(String name, args) {        "${name.capitalize()}!"(2)    }}def d = new Duck()assert d.speak() == 'Quack!'(3)
1theSpeakingDuck expects thequack method to be defined
2theDuck class does implement the method usingmethodMissing
3calling thespeak method triggers a call toquack which is handled bymethodMissing

5.9.2. Dynamic methods in a trait

It is also possible for a trait to implement MOP methods likemethodMissing orpropertyMissing, in which case implementing classeswill inherit the behavior from the trait, like in this example:

trait DynamicObject {(1)    private Map props = [:]    def methodMissing(String name, args) {        name.toUpperCase()    }    def propertyMissing(String name) {        props.get(name)    }    void setProperty(String name, Object value) {        props.put(name, value)    }}class Dynamic implements DynamicObject {    String existingProperty = 'ok'(2)    String existingMethod() { 'ok' }(3)}def d = new Dynamic()assert d.existingProperty == 'ok'(4)assert d.foo == null(5)d.foo = 'bar'(6)assert d.foo == 'bar'(7)assert d.existingMethod() == 'ok'(8)assert d.someMethod() == 'SOMEMETHOD'(9)
1create a trait implementing several MOP methods
2theDynamic class defines a property
3theDynamic class defines a method
4calling an existing property will call the method fromDynamic
5calling a non-existing property will call the method from the trait
6will callsetProperty defined on the trait
7will callgetProperty defined on the trait
8calling an existing method onDynamic
9but calling a non-existing method thanks to the traitmethodMissing

5.10. Multiple inheritance conflicts

5.10.1. Default conflict resolution

It is possible for a class to implement multiple traits. If some trait defines a method with the same signature as amethod in another trait, we have a conflict:

trait A {    String exec() { 'A' }(1)}trait B {    String exec() { 'B' }(2)}class C implements A,B {}(3)
1traitA defines a method namedexec returning aString
2traitB defines the very same method
3classC implements both traits

In this case, the default behavior is that the method from thelast declared trait in theimplements clause wins.Here,B is declared afterA so the method fromB will be picked up:

def c = new C()assert c.exec() == 'B'

5.10.2. User conflict resolution

In case this behavior is not the one you want, you can explicitly choose which method to call using theTrait.super.foo syntax.In the example above, we can ensure the method from trait A is invoked by writing this:

class C implements A,B {    String exec() { A.super.exec() }(1)}def c = new C()assert c.exec() == 'A'(2)
1explicit call ofexec from the traitA
2calls the version fromA instead of using the default resolution, which would be the one fromB

5.11. Runtime implementation of traits

5.11.1. Implementing a trait at runtime

Groovy also supports implementing traits dynamically at runtime. It allows you to "decorate" an existing object using atrait. As an example, let’s start with this trait and the following class:

trait Extra {    String extra() { "I'm an extra method" }(1)}class Something {(2)    String doSomething() { 'Something' }(3)}
1theExtra trait defines anextra method
2theSomething class doesnot implement theExtra trait
3Something only defines a methoddoSomething

Then if we do:

def s = new Something()s.extra()

the call to extra would fail becauseSomething is not implementingExtra. It is possible to do it at runtime withthe following syntax:

def s = new Something() as Extra(1)s.extra()(2)s.doSomething()(3)
1use of theas keyword to coerce an object to a traitat runtime
2thenextra can be called on the object
3anddoSomething is still callable
When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteedthat the coerced object will implement both the traitand the interfaces that the original object implements, butthe result willnot be an instance of the original class.

5.11.2. Implementing multiple traits at once

Should you need to implement several traits at once, you can use thewithTraits method instead of theas keyword:

trait A { void methodFromA() {} }trait B { void methodFromB() {} }class C {}def c = new C()c.methodFromA()(1)c.methodFromB()(2)def d = c.withTraits A, B(3)d.methodFromA()(4)d.methodFromB()(5)
1call tomethodFromA will fail becauseC doesn’t implementA
2call tomethodFromB will fail becauseC doesn’t implementB
3withTraits will wrapc into something which implementsA andB
4methodFromA will now pass becaused implementsA
5methodFromB will now pass becaused also implementsB
When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteedthat the coerced object will implement both the traitsand the interfaces that the original object implements, butthe result willnot be an instance of the original class.

5.12. Chaining behavior

Groovy supports the concept ofstackable traits. The idea is to delegate from one trait to the other if the current traitis not capable of handling a message. To illustrate this, let’s imagine a message handler interface like this:

interface MessageHandler {    void on(String message, Map payload)}

Then you can compose a message handler by applying small behaviors. For example, let’s define a default handler in theform of a trait:

trait DefaultHandler implements MessageHandler {    void on(String message, Map payload) {        println "Received $message with payload $payload"    }}

Then any class can inherit the behavior of the default handler by implementing the trait:

class SimpleHandler implements DefaultHandler {}

Now what if you want to log all messages, in addition to the default handler? One option is to write this:

class SimpleHandlerWithLogging implements DefaultHandler {    void on(String message, Map payload) {(1)        println "Seeing $message with payload $payload"(2)        DefaultHandler.super.on(message, payload)(3)    }}
1explicitly implement theon method
2perform logging
3continue by delegating to theDefaultHandler trait

This works but this approach has drawbacks:

  1. the logging logic is bound to a "concrete" handler

  2. we have an explicit reference toDefaultHandler in theon method, meaning that if we happen to change the trait that our class implements, code will be broken

As an alternative, we can write another trait which responsibility is limited to logging:

trait LoggingHandler implements MessageHandler {(1)    void on(String message, Map payload) {        println "Seeing $message with payload $payload"(2)        super.on(message, payload)(3)    }}
1the logging handler is itself a handler
2prints the message it receives
3thensuper makes it delegate the call to the next trait in the chain

Then our class can be rewritten as this:

class HandlerWithLogger implements DefaultHandler, LoggingHandler {}def loggingHandler = new HandlerWithLogger()loggingHandler.on('test logging', [:])

which will print:

Seeing test logging with payload [:]Received test logging with payload [:]

As the priority rules imply thatLoggerHandler wins because it is declared last, then a call toon will usethe implementation fromLoggingHandler. But the latter has a call tosuper, which means the next trait in thechain. Here, the next trait isDefaultHandler soboth will be called:

The interest of this approach becomes more evident if we add a third handler, which is responsible for handling messagesthat start withsay:

trait SayHandler implements MessageHandler {    void on(String message, Map payload) {        if (message.startsWith("say")) {(1)            println "I say ${message - 'say'}!"        } else {            super.on(message, payload)(2)        }    }}
1a handler specific precondition
2if the precondition is not met, pass the message to the next handler in the chain

Then our final handler looks like this:

class Handler implements DefaultHandler, SayHandler, LoggingHandler {}def h = new Handler()h.on('foo', [:])h.on('sayHello', [:])

Which means:

  • messages will first go through the logging handler

  • the logging handler callssuper which will delegate to the next handler, which is theSayHandler

  • if the message starts withsay, then the handler consumes the message

  • if not, thesay handler delegates to the next handler in the chain

This approach is very powerful because it allows you to write handlers that do not know each other and yet let youcombine them in the order you want. For example, if we execute the code, it will print:

Seeing foo with payload [:]Received foo with payload [:]Seeing sayHello with payload [:]I say Hello!

but if we move the logging handler to be the second one in the chain, the output is different:

class AlternateHandler implements DefaultHandler, LoggingHandler, SayHandler {}h = new AlternateHandler()h.on('foo', [:])h.on('sayHello', [:])

prints:

Seeing foo with payload [:]Received foo with payload [:]I say Hello!

The reason is that now, since theSayHandler consumes the message without callingsuper, the logging handler isnot called anymore.

5.12.1. Semantics of super inside a trait

If a class implements multiple traits and a call to an unqualifiedsuper is found, then:

  1. if the class implements another trait, the call delegates to the next trait in the chain

  2. if there isn’t any trait left in the chain,super refers to the super class of the implementing class (this)

For example, it is possible to decorate final classes thanks to this behavior:

trait Filtering {(1)    StringBuilder append(String str) {(2)        def subst = str.replace('o','')(3)        super.append(subst)(4)    }    String toString() { super.toString() }(5)}def sb = new StringBuilder().withTraits Filtering(6)sb.append('Groovy')assert sb.toString() == 'Grvy'(7)
1define a trait namedFiltering, supposed to be applied on aStringBuilder at runtime
2redefine theappend method
3remove all 'o’s from the string
4then delegate tosuper
5in casetoString is called, delegate tosuper.toString
6runtime implementation of theFiltering trait on aStringBuilder instance
7the string which has been appended no longer contains the lettero

In this example, whensuper.append is encountered, there is no other trait implemented by the target object, so themethod which is called is the originalappend method, that is to say the one fromStringBuilder. The same trickis used fortoString, so that the string representation of the proxy object which is generated delegates to thetoString of theStringBuilder instance.

5.13. Advanced features

5.13.1. SAM type coercion

If a trait defines a single abstract method, it is candidate for SAM (Single Abstract Method) type coercion. For example,imagine the following trait:

trait Greeter {    String greet() { "Hello $name" }(1)    abstract String getName()(2)}
1thegreet method is not abstract and calls the abstract methodgetName
2getName is an abstract method

SincegetName is thesingle abstract method in theGreeter trait, you can write:

Greeter greeter = { 'Alice' }(1)
1the closure "becomes" the implementation of thegetName single abstract method

or even:

void greet(Greeter g) { println g.greet() }(1)greet { 'Alice' }(2)
1the greet method accepts the SAM type Greeter as parameter
2we can call it directly with a closure

5.13.2. Differences with Java 8 default methods

In Java 8, interfaces can have default implementations of methods. If a class implements an interface and does not providean implementation for a default method, then the implementation from the interface is chosen. Traits behave the same butwith a major difference: the implementation from the trait isalways used if the class declares the trait in its interfacelistand that it doesn’t provide an implementationeven if a super class does.

This feature can be used to compose behaviors in a very precise way, in case you want to override the behavior of analready implemented method.

To illustrate the concept, let’s start with this simple example:

import groovy.test.GroovyTestCaseimport groovy.transform.CompileStaticimport org.codehaus.groovy.control.CompilerConfigurationimport org.codehaus.groovy.control.customizers.ASTTransformationCustomizerimport org.codehaus.groovy.control.customizers.ImportCustomizerclass SomeTest extends GroovyTestCase {    def config    def shell    void setup() {        config = new CompilerConfiguration()        shell = new GroovyShell(config)    }    void testSomething() {        assert shell.evaluate('1+1') == 2    }    void otherTest() { /* ... */ }}

In this example, we create a simple test case which uses two properties (config andshell) and uses those inmultiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration.One option is to create a subclass ofSomeTest:

class AnotherTest extends SomeTest {    void setup() {        config = new CompilerConfiguration()        config.addCompilationCustomizers( ... )        shell = new GroovyShell(config)    }}

It works, but what if you have actually multiple test classes, and that you want to test the new configuration for allthose test classes? Then you would have to create a distinct subclass for each test class:

class YetAnotherTest extends SomeTest {    void setup() {        config = new CompilerConfiguration()        config.addCompilationCustomizers( ... )        shell = new GroovyShell(config)    }}

Then what you see is that thesetup method of both tests is the same. The idea, then, is to create a trait:

trait MyTestSupport {    void setup() {        config = new CompilerConfiguration()        config.addCompilationCustomizers( new ASTTransformationCustomizer(CompileStatic) )        shell = new GroovyShell(config)    }}

Then use it in the subclasses:

class AnotherTest extends SomeTest implements MyTestSupport {}class YetAnotherTest extends SomeTest2 implements MyTestSupport {}...

It would allow us to dramatically reduce the boilerplate code, and reduces the risk of forgetting to change the setupcode in case we decide to change it. Even ifsetup is already implemented in the super class, since the test class declaresthe trait in its interface list, the behavior will be borrowed from the trait implementation!

This feature is in particular useful when you don’t have access to the super class source code. It can be used tomock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keepthe overridden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course,is to override the method inevery place you would have used the new code.

It’s worth noting that if you use runtime traits, the methods from the trait arealways preferred to those of the proxiedobject:
class Person {    String name(1)}trait Bob {    String getName() { 'Bob' }(2)}def p = new Person(name: 'Alice')assert p.name == 'Alice'(3)def p2 = p as Bob(4)assert p2.name == 'Bob'(5)
1thePerson class defines aname property which results in agetName method
2Bob is a trait which definesgetName as returningBob
3the default object will returnAlice
4p2 coercesp intoBob at runtime
5getName returnsBob becausegetName is taken from thetrait
Again, don’t forget that dynamic trait coercion returns a distinct object which only implements the originalinterfaces, as well as the traits.

5.14. Differences with mixins

There are several conceptual differences with mixins, as they are available in Groovy. Note that we are talking aboutruntime mixins, not the @Mixin annotation which is deprecated in favour of traits.

First of all, methods defined in a trait are visible in bytecode:

  • internally, the trait is represented as an interface (without default or static methods) and several helper classes

  • this means that an object implementing a trait effectively implements aninterface

  • 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:

class A { String methodFromA() { 'A' } }(1)class B { String methodFromB() { 'B' } }(2)A.metaClass.mixin B(3)def o = new A()assert o.methodFromA() == 'A'(4)assert o.methodFromB() == 'B'(5)assert o instanceof A(6)assert !(o instanceof B)(7)
1classA definesmethodFromA
2classB definesmethodFromB
3mixin B into A
4we can callmethodFromA
5we can also callmethodFromB
6the object is an instance ofA
7but it’snot an instanceofB

The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instancesarenot modified, so if you mixin some class into another, there isn’t a third class generated, and methods which respond toA will continue responding to A even if mixed in.

5.15. Static methods, properties and fields

The following instructions are subject to caution. Static member support is work in progress and still experimental. Theinformation below is valid for 5.0.2 only.

It is possible to define static methods in a trait, but it comes with numerous limitations:

  • Traits with static methods cannot be compiled statically or type checked. All static methods,properties and field are accessed dynamically (it’s a limitation from the JVM).

  • Static methods do not appear within the generated interfaces for each trait.

  • The trait is interpreted as atemplate for the implementing class, which means that eachimplementing class will get its own static methods, properties and fields. So a static memberdeclared on a trait doesn’t belong to theTrait, but to its implementing class.

  • You should typically not mix static and instance methods of the same signature. The normalrules for applying traits apply (including multiple inheritance conflict resolution). If themethod chosen is static but some implemented trait has an instance variant, a compilation errorwill occur. If the method chosen is the instance variant, the static variant will be ignored(the behavior is similar to static methods in Java interfaces for this case).

Let’s start with a simple example:

trait TestHelper {    public static boolean CALLED = false(1)    static void init() {(2)        CALLED = true(3)    }}class Foo implements TestHelper {}Foo.init()(4)assert Foo.TestHelper__CALLED(5)
1the static field is declared in the trait
2a static method is also declared in the trait
3the static field is updatedwithin the trait
4a static methodinit is made available to the implementing class
5the static field isremapped to avoid the diamond issue

As usual, it is not recommended to use public fields. Anyway, should you want this, you must understand that the following code would fail:

Foo.CALLED = true

because there isno static fieldCALLED defined on the trait itself. Likewise, if you have two distinct implementing classes, each one gets a distinct static field:

class Bar implements TestHelper {}(1)class Baz implements TestHelper {}(2)Bar.init()(3)assert Bar.TestHelper__CALLED(4)assert !Baz.TestHelper__CALLED(5)
1classBar implements the trait
2classBaz also implements the trait
3init is only called onBar
4the static fieldCALLED onBar is updated
5but the static fieldCALLED onBaz is not, because it isdistinct

5.16. Inheritance of state gotchas

We have seen that traits are stateful. It is possible for a trait to define fields or properties, but when a class implements a trait, it gets those fields/properties ona per-trait basis. So consider the following example:

trait IntCouple {    int x = 1    int y = 2    int sum() { x+y }}

The trait defines two properties,x andy, as well as asum method. Now let’s create a class which implements the trait:

class BaseElem implements IntCouple {    int f() { sum() }}def base = new BaseElem()assert base.f() == 3

The result of callingf is3, becausef delegates tosum in the trait, which has state. But what if we write this instead?

class Elem implements IntCouple {    int x = 3(1)    int y = 4(2)    int f() { sum() }(3)}def elem = new Elem()
1Override propertyx
2Override propertyy
3Callsum from trait

If you callelem.f(), what is the expected output? Actually it is:

assert elem.f() == 3

The reason is that thesum method accesses thefields of the trait. So it is using thex andy values definedin the trait. If you want to use the values from the implementing class, then you need to dereference fields by usinggetters and setters, like in this last example:

trait IntCouple {    int x = 1    int y = 2    int sum() { getX()+getY() }}class Elem implements IntCouple {    int x = 3    int y = 4    int f() { sum() }}def elem = new Elem()assert elem.f() == 7

5.17. Self types

5.17.1. Type constraints on traits

Sometimes you will want to write a trait that can only be applied to some type. For example, you may want to apply atrait on a class that extends another class which is beyond your control, and still be able to call those methods.To illustrate this, let’s start with this example:

class CommunicationService {    static void sendMessage(String from, String to, String message) {(1)        println "$from sent [$message] to $to"    }}class Device { String id }(2)trait Communicating {    void sendMessage(Device to, String message) {        CommunicationService.sendMessage(id, to.id, message)(3)    }}class MyDevice extends Device implements Communicating {}(4)def bob = new MyDevice(id:'Bob')def alice = new MyDevice(id:'Alice')bob.sendMessage(alice,'secret')(5)
1AService class, beyond your control (in a library, …​) defines asendMessage method
2ADevice class, beyond your control (in a library, …​)
3Defines a communicating trait for devices that can call the service
4DefinesMyDevice as a communicating device
5The method from the trait is called, andid is resolved

It is clear, here, that theCommunicating trait can only apply toDevice. However, there’s no explicitcontract to indicate that, because traits cannot extend classes. However, the code compiles and runs perfectlyfine, becauseid in the trait method will be resolved dynamically. The problem is that there is nothing thatprevents the trait from being applied to any class which isnot aDevice. Any class which has anid wouldwork, while any class that does not have anid property would cause a runtime error.

The problem is even more complex if you want to enable type checking or apply@CompileStatic on the trait: becausethe trait knows nothing about itself being aDevice, the type checker will complain saying that it does not findtheid property.

One possibility is to explicitly add agetId method in the trait, but it would not solve all issues. What if a methodrequiresthis as a parameter, and actually requires it to be aDevice?

class SecurityService {    static void check(Device d) { if (d.id==null) throw new SecurityException() }}

If you want to be able to callthis in the trait, then you will explicitly need to castthis into aDevice. This canquickly become unreadable with explicit casts tothis everywhere.

5.17.2. The @SelfType annotation

In order to make this contract explicit, and to make the type checker aware of thetype of itself, Groovy providesa@SelfType annotation that will:

  • let you declare the types that a class that implements this trait must inherit or implement

  • throw a compile-time error if those type constraints are not satisfied

So in our previous example, we can fix the trait using the@groovy.transform.SelfType annotation:

@SelfType(Device)@CompileStatictrait Communicating {    void sendMessage(Device to, String message) {        SecurityService.check(this)        CommunicationService.sendMessage(id, to.id, message)    }}

Now if you try to implement this trait on a class that isnot a device, a compile-time error will occur:

class MyDevice implements Communicating {} // forgot to extend Device

The error will be:

class 'MyDevice' implements trait 'Communicating' but does not extend self type class 'Device'

In conclusion, self types are a powerful way of declaring constraints on traits without having to declare the contractdirectly in the trait or having to use casts everywhere, maintaining separation of concerns as tight as it should be.

5.17.3. Differences with Sealed annotation (incubating)

Both@Sealed and@SelfType restrict classes which use a trait but in orthogonal ways.Consider the following example:

interface HasHeight { double getHeight() }interface HasArea { double getArea() }@SelfType([HasHeight, HasArea])(1)@Sealed(permittedSubclasses=[UnitCylinder,UnitCube])(2)trait HasVolume {    double getVolume() { height * area }}final class UnitCube implements HasVolume, HasHeight, HasArea {    // for the purposes of this example: h=1, w=1, l=1    double height = 1d    double area = 1d}final class UnitCylinder implements HasVolume, HasHeight, HasArea {    // for the purposes of this example: h=1, diameter=1    // radius=diameter/2, area=PI * r^2    double height = 1d    double area = Math.PI * 0.5d**2}assert new UnitCube().volume == 1dassert new UnitCylinder().volume == 0.7853981633974483d
1All usages of theHasVolume trait must implement or extend bothHasHeight andHasArea
2OnlyUnitCube orUnitCylinder can use the trait

For the degenerate case where a single class implements a trait, e.g.:

final class Foo implements FooTrait {}

Then, either:

@SelfType(Foo)trait FooTrait {}

or:

@Sealed(permittedSubclasses='Foo')(1)trait FooTrait {}
1Or just@Sealed ifFoo andFooTrait are in the same source file

could express this constraint. Generally, the former of these is preferred.

5.18. Limitations

5.18.1. Compatibility with AST transformations

Traits are not officially compatible with AST transformations. Some of them, like@CompileStatic will be appliedon 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 itat your own risk!

5.18.2. Prefix and 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++(1)    }    void dec() {        --x(2)    }}class Counter implements Counting {}def c = new Counter()c.inc()
1x is defined within the trait, postfix increment is not allowed
2x is defined within the trait, prefix decrement is not allowed

A workaround is to use the+= operator instead.

6. Record classes (incubating)

Record classes, orrecords for short, are a special kind of classuseful for modelling plain data aggregates.They provide a compact syntax with less ceremony than normal classes.Groovy already has AST transforms such as@Immutable and@Canonicalwhich already dramatically reduce ceremony but records have beenintroduced in Java and record classes in Groovy are designed to alignwith Java record classes.

For example, suppose we want to create aMessage recordrepresenting an email message. For the purposes of this example,let’s simplify such a message to contain just afrom email address,ato email address, and a messagebody. We can define sucha record as follows:

record Message(String from, String to, String body) { }

We’d use the record class in the same way as a normal class, as shown below:

def msg = new Message('me@myhost.com', 'you@yourhost.net', 'Hello!')assert msg.toString() == 'Message[from=me@myhost.com, to=you@yourhost.net, body=Hello!]'

The reduced ceremony saves us from defining explicit fields, getters andtoString,equals andhashCode methods. In fact, it’s a shorthandfor the following rough equivalent:

final class Message extends Record {    private final String from    private final String to    private final String body    private static final long serialVersionUID = 0    /* constructor(s) */    final String toString() { /*...*/ }    final boolean equals(Object other) { /*...*/ }    final int hashCode() { /*...*/ }    String from() { from }    // other getters ...}

Note the special naming convention for record getters. They are the same name as the field(rather than the often common JavaBean convention of capitalized with a "get" prefix).Rather than referring to a record’s fields or properties, the termcomponentis typically used for records. So ourMessage record hasfrom,to, andbody components.

Like in Java, you can override the normally implicitly supplied methodsby writing your own:

record Point3D(int x, int y, int z) {    String toString() {        "Point3D[coords=$x,$y,$z]"    }}assert new Point3D(10, 20, 30).toString() == 'Point3D[coords=10,20,30]'

You can also use generics with records in the normal way. For example, consider the followingCoord record definition:

record Coord<T extends Number>(T v1, T v2){    double distFromOrigin() { Math.sqrt(v1()**2 + v2()**2 as double) }}

It can be used as follows:

def r1 = new Coord<Integer>(3, 4)assert r1.distFromOrigin() == 5def r2 = new Coord<Double>(6d, 2.5d)assert r2.distFromOrigin() == 6.5d

6.1. Special record features

6.1.1. Compact constructor

Records have an implicit constructor. This can be overridden in the normal wayby providing your own constructor - you need to make sure you set all the fieldsif you do this.However, for succinctness, a compact constructor syntax can be used wherethe parameter declaration part of a normal constructor is elided.For this special case, the normal implicit constructor is still providedbut is augmented by the supplied statements in the compact constructor definition:

public record Warning(String message) {    public Warning {        Objects.requireNonNull(message)        message = message.toUpperCase()    }}def w = new Warning('Help')assert w.message() == 'HELP'

6.1.2. Serializability

Groovynative records follow thespecial conventionsfor serializability which apply to Java records.Groovyrecord-like classes (discussed below) follow normal Java class serializability conventions.

6.2. Groovy enhancements

6.2.1. Argument defaults

Groovy supports default values for constructor arguments.This capability is also available for records as shown in the following record definitionwhich has default values fory andcolor:

record ColoredPoint(int x, int y = 0, String color = 'white') {}

Arguments when left off (dropping one or more arguments from the right) are replacedwith their defaults values as shown in the following example:

assert new ColoredPoint(5, 5, 'black').toString() == 'ColoredPoint[x=5, y=5, color=black]'assert new ColoredPoint(5, 5).toString() == 'ColoredPoint[x=5, y=5, color=white]'assert new ColoredPoint(5).toString() == 'ColoredPoint[x=5, y=0, color=white]'

This processing follows normal Groovy conventions for default arguments for constructors, essentially automatically providing the constructors with the following signatures:

ColoredPoint(int, int, String)ColoredPoint(int, int)ColoredPoint(int)

Named arguments may also be used (default values also apply here):

assert new ColoredPoint(x: 5).toString() == 'ColoredPoint[x=5, y=0, color=white]'assert new ColoredPoint(x: 0, y: 5).toString() == 'ColoredPoint[x=0, y=5, color=white]'

You can disable default argument processing as shown here:

@TupleConstructor(defaultsMode=DefaultsMode.OFF)record ColoredPoint2(int x, int y, String color) {}assert new ColoredPoint2(4, 5, 'red').toString() == 'ColoredPoint2[x=4, y=5, color=red]'

This will produce a single constructor as per the default with Java.It will be an error if you drop off arguments in this scenario.

You can force all properties to have a default value as shown here:

@TupleConstructor(defaultsMode=DefaultsMode.ON)record ColoredPoint3(int x, int y = 0, String color = 'white') {}assert new ColoredPoint3(y: 5).toString() == 'ColoredPoint3[x=0, y=5, color=white]'

Any property/field without an explicit initial value will be given the default value for the argument’s type (null, or zero/false for primitives).

Diving deeper

We previously described aMessage record and displayed it’s rough equivalent.Groovy in fact steps through an intermediate stage where therecord keywordis replaced by theclass keyword and an accompanying@RecordType annotation:

@RecordTypeclass Message {    String from    String to    String body}

Then@RecordType itself is processed as ameta-annotation (annotation collector)and expanded into its constituent sub-annotations such as@TupleConstructor,@POJO,@RecordBase, and others. This is in some sense an implementation detail which can often be ignored.However, if you wish to customise or configure the record implementation,you may wish to drop back to the@RecordType style or augment your record classwith one of the constituent sub-annotations.

6.2.2. DeclarativetoString customization

As per Java, you can customize a record’stoString method by writing your own.If you prefer a more declarative style, you can alternatively use Groovy’s@ToString transformto override the default recordtoString.As an example, you can a three-dimensional point record as follows:

package threedimport groovy.transform.ToString@ToString(ignoreNulls=true, cache=true, includeNames=true,          leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=')record Point(Integer x, Integer y, Integer z=null) { }assert new Point(10, 20).toString() == 'threed.Point[x=10, y=20]'

We customise thetoString by including the package name (excluded by default for records)and by caching thetoString value since it won’t change for this immutable record.We are also ignoring null values (the default value forz in our definition).

We can have a similar definition for a two-dimensional point:

package twodimport groovy.transform.ToString@ToString(ignoreNulls=true, cache=true, includeNames=true,          leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=')record Point(Integer x, Integer y) { }assert new Point(10, 20).toString() == 'twod.Point[x=10, y=20]'

We can see here that without the package name it would have the same toString as our previous example.

6.2.3. Obtaining a list of the record component values

We can obtain the component values from a record as a list like so:

record Point(int x, int y, String color) { }def p = new Point(100, 200, 'green')def (x, y, c) = p.toList()assert x == 100assert y == 200assert c == 'green'

You can use@RecordOptions(toList=false) to disable this feature.

6.2.4. Obtaining a map of the record component values

We can obtain the component values from a record as a map like so:

record Point(int x, int y, String color) { }def p = new Point(100, 200, 'green')assert p.toMap() == [x: 100, y: 200, color: 'green']

You can use@RecordOptions(toMap=false) to disable this feature.

6.2.5. Obtaining the number of components in a record

We can obtain the number of components in a record like so:

record Point(int x, int y, String color) { }def p = new Point(100, 200, 'green')assert p.size() == 3

You can use@RecordOptions(size=false) to disable this feature.

6.2.6. Obtaining the nth component from a record

We can use Groovy’s normal positional indexing to obtain a particular component in a record like so:

record Point(int x, int y, String color) { }def p = new Point(100, 200, 'green')assert p[1] == 200

You can use@RecordOptions(getAt=false) to disable this feature.

6.3. Optional Groovy features

6.3.1. Copying

It can be useful to make a copy of a record with some components changed.This can be done using an optionalcopyWith method which takes named arguments.Record components are set from the supplied arguments.For components not mentioned, a (shallow) copy of the original record component is used.Here is how you might usecopyWith for theFruit record:

@RecordOptions(copyWith=true)record Fruit(String name, double price) {}def apple = new Fruit('Apple', 11.6)assert 'Apple' == apple.name()assert 11.6 == apple.price()def orange = apple.copyWith(name: 'Orange')assert orange.toString() == 'Fruit[name=Orange, price=11.6]'

ThecopyWith functionality can be disabled by setting theRecordOptions#copyWith annotation attribute tofalse.

6.3.2. Deep immutability

As with Java, records by default offer shallow immutability.Groovy’s@Immutable transform performs defensive copying for a range of mutabledata types. Records can make use of this defensive copying to gain deep immutability as follows:

@ImmutablePropertiesrecord Shopping(List items) {}def items = ['bread', 'milk']def shop = new Shopping(items)items << 'chocolate'assert shop.items() == ['bread', 'milk']

These examples illustrate the principal behindGroovy’s record feature offering three levels of convenience:

  • Using therecord keyword for maximum succinctness

  • Supporting low-ceremony customization using declarative annotations

  • Allowing normal method implementations when full control is required

6.3.3. Obtaining the components of a record as a typed tuple

You can obtain the components of a record as a typed tuple:

import groovy.transform.*@RecordOptions(components=true)record Point(int x, int y, String color) { }@CompileStaticdef method() {    def p1 = new Point(100, 200, 'green')    def (int x1, int y1, String c1) = p1.components()    assert x1 == 100    assert y1 == 200    assert c1 == 'green'    def p2 = new Point(10, 20, 'blue')    def (x2, y2, c2) = p2.components()    assert x2 * 10 == 100    assert y2 ** 2 == 400    assert c2.toUpperCase() == 'BLUE'    def p3 = new Point(1, 2, 'red')    assert p3.components() instanceof Tuple3}method()

Groovy has a limited number ofTupleN classes.If you have a large number of components in your record, you might not be able to use this feature.

6.4. Other differences to Java

Groovy supports creatingrecord-like classes as well as native records.Record-like classes don’t extend Java’sRecord class and such classeswon’t be seen by Java as records but will otherwise have similar properties.

The@RecordOptions annotation (part of@RecordType) supports amode annotation attributewhich can take one of three values (withAUTO being the default):

NATIVE

Produces a class similar to what Java would do. Produces an error when compiling on JDKs earlier than JDK16.

EMULATE

Produces a record-like class for all JDK versions.

AUTO

Produces a native record for JDK16+ and emulates the record otherwise.

Whether you use therecord keyword or the@RecordType annotationis independent of the mode.

7. Sealed hierarchies (incubating)

Sealed classes, interfaces and traits restrict which subclasses can extend/implement them.Prior to sealed classes, class hierarchy designers had two main options:

  • Make a class final to allow no extension.

  • Make the class public and non-final to allow extension by anyone.

Sealed classes provide a middle-ground compared to these all or nothing choices.

Sealed classes are also more flexible than other tricks previously usedto try to achieve a middle-ground. For example, for class hierarchies,access modifiers like protected and package-private give some ability to restrict inheritancehierarchies but often at the expense of flexible use of those hierarchies.

Sealed hierarchies provide full inheritance within a known hierarchy of classes, interfacesand traits but disable or only provide controlled inheritance outside the hierarchy.

As an example, suppose we want to create a shape hierarchy containingonly circles and squares. We also want a shape interface tobe able to refer to instances in our hierarchy.We can create the hierarchy as follows:

sealed interface ShapeI permits Circle,Square { }final class Circle implements ShapeI { }final class Square implements ShapeI { }

Groovy also supports an alternative annotation syntax.We think the keyword style is nicer but you might choose the annotation style if your editor doesn’t yet have Groovy 4 support.

@Sealed(permittedSubclasses=[Circle,Square]) interface ShapeI { }final class Circle implements ShapeI { }final class Square implements ShapeI { }

We can have a reference of typeShapeI which, thanks to thepermits clause,can point to either aCircle orSquare and, since our classes arefinal,we know no additional classes will be added to our hierarchy in the future.At least not without changing thepermits clause and recompiling.

In general, we might want to have some parts of our class hierarchyimmediately locked down like we have here, where we marked thesubclasses asfinal but other times we might want to allow furthercontrolled inheritance.

sealed class Shape permits Circle,Polygon,Rectangle { }final class Circle extends Shape { }class Polygon extends Shape { }non-sealed class RegularPolygon extends Polygon { }final class Hexagon extends Polygon { }sealed class Rectangle extends Shape permits Square{ }final class Square extends Rectangle { }
<Click to see the alternate annotations syntax>
@Sealed(permittedSubclasses=[Circle,Polygon,Rectangle]) class Shape { }final class Circle extends Shape { }class Polygon extends Shape { }@NonSealed class RegularPolygon extends Polygon { }final class Hexagon extends Polygon { }@Sealed(permittedSubclasses=Square) class Rectangle extends Shape { }final class Square extends Rectangle { }

 
In this example, our permitted subclasses forShape areCircle,Polygon, andRectangle.Circle isfinal and hence that part of the hierarchy cannot be extended.Polygon is implicitly non-sealed andRegularPolygon is explicitly marked asnon-sealed.That means our hierarchy is open to any further extension by subclassing,as seen withPolygon → RegularPolygon andRegularPolygon → Hexagon.Rectangle is itself sealed which means that part of the hierarchy can be extendedbut only in a controlled way (onlySquare is permitted).

Sealed classes are useful for creating enum-like related classeswhich need to contain instance specific data. For instance, we might have the following enum:

enum Weather { Rainy, Cloudy, Sunny }def forecast = [Weather.Rainy, Weather.Sunny, Weather.Cloudy]assert forecast.toString() == '[Rainy, Sunny, Cloudy]'

but we now wish to also add weather specific instance data to weather forecasts.We can alter our abstraction as follows:

sealed abstract class Weather { }@Immutable(includeNames=true) class Rainy extends Weather { Integer expectedRainfall }@Immutable(includeNames=true) class Sunny extends Weather { Integer expectedTemp }@Immutable(includeNames=true) class Cloudy extends Weather { Integer expectedUV }def forecast = [new Rainy(12), new Sunny(35), new Cloudy(6)]assert forecast.toString() == '[Rainy(expectedRainfall:12), Sunny(expectedTemp:35), Cloudy(expectedUV:6)]'

Sealed hierarchies are also useful when specifying Algebraic or Abstract Data Types (ADTs) as shown in the following example:

import groovy.transform.*sealed interface Tree<T> {}@Singleton final class Empty implements Tree {    String toString() { 'Empty' }}@Canonical final class Node<T> implements Tree<T> {    T value    Tree<T> left, right}Tree<Integer> tree = new Node<>(42, new Node<>(0, Empty.instance, Empty.instance), Empty.instance)assert tree.toString() == 'Node(42, Node(0, Empty, Empty), Empty)'

Sealed hierarchies work well with records as shown in the following example:

sealed interface Expr {}record ConstExpr(int i) implements Expr {}record PlusExpr(Expr e1, Expr e2) implements Expr {}record MinusExpr(Expr e1, Expr e2) implements Expr {}record NegExpr(Expr e) implements Expr {}def threePlusNegOne = new PlusExpr(new ConstExpr(3), new NegExpr(new ConstExpr(1)))assert threePlusNegOne.toString() == 'PlusExpr[e1=ConstExpr[i=3], e2=NegExpr[e=ConstExpr[i=1]]]'

7.1. Differences to Java

  • Java provides no default modifier for subclasses of sealed classesand requires that one offinal,sealed ornon-sealed be specified.Groovy defaults tonon-sealed but you can still usenon-sealed/@NonSealed if you wish.We anticipate the style checking tool CodeNarc will eventually have a rule thatlooks for the presence ofnon-sealed so developers wanting that stricterstyle will be able to use CodeNarc and that rule if they want.

  • Currently, Groovy doesn’t check that all classes mentioned inpermittedSubclassesare available at compile-time and compiled along with the base sealed class.This may change in a future version of Groovy.

Groovy supports annotating classes as sealed as well as "native" sealed classes.

The@SealedOptions annotation supports amode annotation attributewhich can take one of three values (withAUTO being the default):

NATIVE

Produces a class similar to what Java would do.Produces an error when compiling on JDKs earlier than JDK17.

EMULATE

Indicates the class is sealed using the@Sealed annotation.This mechanism works with the Groovy compiler for JDK8+ but is not recognised by the Java compiler.

AUTO

Produces a native record for JDK17+ and emulates the record otherwise.

Whether you use thesealed keyword or the@Sealed annotationis independent of the mode.

Version 5.0.2
Last updated 2025-10-15 09:28:18 +1000

[8]ページ先頭

©2009-2025 Movatter.jp