满身肌肉的遥控器 · Jupyter Notebook with ...· 昨天 · |
气宇轩昂的椅子 · 《蜘蛛侠3:英雄无归》精彩视效解析_新闻中心 ...· 3 月前 · |
有爱心的山寨机 · 限制片网站:选择与影响_三围小说网· 3 月前 · |
飘逸的冰淇淋 · 如何在Three.js中加载模型?· 9 月前 · |
无邪的小蝌蚪 · 唐三(《斗罗大陆》中的角色)_搜狗百科· 1 年前 · |
绅士的大白菜 · 廊坊市人民政府 - 手机版· 1 年前 · |
toString
customization
Also like Java, Groovy uses the respective wrapper classes when objects corresponding to any of the primitive types are required:
Table 1. primitive wrappersAutomatic boxing and unboxing occur when, for instance, calling a method requiring the 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 using
int
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)
Now you may be concerned that this means every time you use a mathematical operator on a reference to a primitive that you’ll incur the cost of unboxing and reboxing the primitive. But this is not the case, as Groovy will compile your operators into their method equivalents and uses those instead. Additionally, Groovy will automatically unbox to a primitive when calling a Java method that takes a primitive parameter and automatically box primitive method return values from Java. However, be aware there are some differences from Java’s method resolution.
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']
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 create a generic class, interface, method or constructor.
Usage of generic classes and methods, regardless of whether they are defined in Java or 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.
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 Java with 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 the fields 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 some special 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).
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 the
public
keyword may be suppressed). Classes are instantiated by calling their constructors, using the
new
keyword, as in the following snippet.
def p = new Person()
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)
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 class Inner2
is defined only to provide an implementation of the method run
to class Outer2
.
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)
comparing with the last example of previous section, the new Inner2()
was replaced by new Runnable()
along with all its implementation
the method start
is invoked normally
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'
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.
Inheritance in Groovy resembles inheritance in Java.
It provides a mechanism for a child class (or subclass) to reuse
code 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 a superclass or from
one or more traits is reused by a child class
contract inheritance where a class promises to provide particular abstract methods defined in a superclass,
or defined in one or more traits or interfaces.
2.4. Superclasses
Parent classes share visible fields, properties or methods with child classes.
A child class may have at most one parent class.
The extends
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.
An interface only defines a list of methods that need
to be implemented, but does not define the method’s implementation.
interface Greeter { (1)
void greet(String name) (2)
A class implements an interface if it defines the interface in its implements
list or if any of its superclasses
does:
class SystemGreeter implements Greeter { (1)
void greet(String name) { (2)
println "Hello $name"
def greeter = new SystemGreeter()
assert greeter instanceof Greeter (3)
It is worth noting that for a class to be an instance of an interface, it has to be explicit. For example, the following
class defines the greet
method as it is declared in the Greeter
interface, but does not declare Greeter
in its
interfaces:
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 object
implement an interface at runtime, using the as
coercion operator:
greeter = new DefaultGreeter() (1)
coerced = greeter as Greeter (2)
assert coerced instanceof Greeter (3)
You can see that there are two distinct objects: one is the source object, a DefaultGreeter
instance, which does not
implement the interface. The other is an instance of Greeter
that delegates to the coerced object.
Groovy interfaces do not support default implementation like Java 8 interfaces. If you are looking for something
similar (but not equal), traits are close to interfaces, but allow default implementation as well as other
important features described in this manual.
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 unique
type signature. If an object doesn’t require any parameters during construction, it may use a no-arg constructor.
If no constructors are supplied, an empty no-arg constructor will be provided by the Groovy compiler.
Groovy supports two invocation styles:
3.1.1. Positional parameters
To create an object by using positional parameters, the respective class needs to declare one or more
constructors. In the case of multiple constructors, each must have a unique type signature. The constructors can also
be added to the class using the groovy.transform.TupleConstructor annotation.
Typically, once at least one constructor is declared, the class can only be instantiated by having one of its
constructors 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 which
takes a Map
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 the new
keyword.
The others rely on coercion of lists into the desired types. In this case, it is possible to coerce with the as
keyword 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)
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 a
map (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 a Map
argument is also supported - such a
constructor may also be added using the groovy.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)
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 constructor
or a constructor where the first argument is a Map
, most typically it’s the only argument.
When no (or a no-arg) constructor is declared, Groovy replaces the named constructor call by a call
to 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 as
final
(since they will be set in the constructor rather than after the fact with setters).
You can support both named and positional construction
by 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 argument
is 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 the def
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 no return
statement is provided, the value evaluated in the last line executed will be returned. For instance, note that none of the following methods uses the return
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)
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 a Map
.
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 the Map
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 as
the 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)
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 to groovy.lang.MissingMethodException
:
def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(name: 'Marie', age: 1, 23) (1)
def foo(Integer number, Map args) { "${args.name}: ${args.age}, and the number is ${number}" }
foo(23, [name: 'Marie', age: 1]) (1)
Although Groovy allows you to mix named and positional parameters, it can lead to unnecessary confusion.
Mix named and positional arguments with caution.
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)
.
Here foo
supports n
arguments by default, but also an unspecified number of further arguments exceeding n
.
def foo(Object... args) { args.length }
assert foo() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
This example defines a method foo
, that can take any number of arguments, including no arguments at all.
args.length
will return the number of arguments given. Groovy allows T[]
as an alternative notation to T…
.
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() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
If a method with varargs is called with null
as the vararg parameter, then the argument will be null
and not an array of length one with null
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 method foo
takes a varargs argument of type T
and another method foo
also takes one argument of type T
, the second method is preferred.
def foo(Object... args) { 1 }
def foo(Object x) { 2 }
assert foo() == 1
assert foo(1) == 2
assert foo(1, 2) == 1
3.2.5. Method selection algorithm
Dynamic Groovy supports multiple dispatch (aka multimethods).
When calling a method, the actual method invoked is determined
dynamically 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, calling method
with String
and Integer
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 type Object
(a list of such objects in our case).
Java would determine that the method(Object, Object)
variant would be selected in all
cases (unless casts were used) but as can be seen in the following example, Groovy uses the runtime type
and 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 of method(Integer, Integer)
wasn’t found but method(Object, Object)
is still valid and will be selected.
Method selection then is about finding the closest fit from valid method candidates which have compatible
parameter types.
So, method(Object, Object)
is also valid for the first two invocations but is not as close a match
as the variants where types exactly match.
To determine the closest fit, the runtime has a notion of the distance an actual argument
type 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.
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'
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'
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'
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 throw
as shown in the following example which can throw a FileNotFoundException
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 the badRead
method in the previous example within a try/catch
block - 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 documentation
for 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()
private int id (1)
protected String description (2)
public static final boolean DEBUG = false (3)
It is possible to omit the type declaration of a field. This is however considered a bad practice and in general it
is a good idea to use strong typing for fields:
class BadPractice {
private mapping (1)
class GoodPractice {
private Map<String,String> mapping (2)
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 useful
to 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 represent
such features (which provides a more limited abstraction and would restrict refactoring possibilities),
the typical approach in Java is to follow the conventions outlined in the
JavaBeans Specification, i.e. represent the property using a
combination of a private backing field and getters/setters. Groovy follows these same conventions
but provides a simpler way to define the property. You can define a property with:
Properties are accessed by name and will call the getter or setter transparently, unless the code is in the class
which 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)
this.name
will directly access the field because the property is accessed from within the class that defines it
similarly a read access is done directly on the name
field
write access to the property is done outside of the Person
class so it will implicitly call setName
read access to the property is done outside of the Person
class so it will implicitly call getName
this will call the name
method on Person
which performs a direct access to the field
this will call the title
method on Person
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 stack
overflow 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 meta properties
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 field
provided there are getters or setters
that 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)
Property naming conventions
It is generally recommended that the first two letters of a property name are
lowercase and for multi-word properties that camel case is used.
In those cases, generated getters and setters will have a name formed by capitalizing the
property name and adding a get
or set
prefix (or optionally "is" for a boolean getter).
So, getLength
would be a getter for a length
property and setFirstName
a setter for a firstName
property.
isEmpty
might be the getter method name for a property named empty
.
Property names starting with a capital letter would have getters/setters with just the prefix added.
So, the property Foo
is allowed even though it isn’t following the recommended naming conventions.
For this property, the accessor methods would be setFoo
and getFoo
.
A consequence of this is that you aren’t allowed to have both a foo
and a Foo
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 a URL
property.
Because of the special "acronym handling" property naming logic in the JavaBeans specification, the
conversion 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 but
was 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 be
one of the acronym style properties and no capitalization is done, otherwise normal capitalization is done.
Although we never recommend it, it does allow you to have what might seem like "duplicate named" properties,
e.g. you can have aProp
and AProp
, or pNAME
and PNAME
. The getters would be getaProp
and getAProp
,
and getpNAME
and getPNAME
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
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 to
be 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)
Split property definition with an explicit backing field
Groovy’s property syntax is a convenient shorthand when your class design
follows 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 provides
a 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 wish
to use if the standard property definition doesn’t exactly fit their needs.
For example, if the backing field should be protected
rather than private
:
class HasPropertyWithProtectedField {
protected String name (1)
String name (2)
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)
Explicit accessor methods
The automatic generation of accessor methods doesn’t occur if there
is 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 inherited
accessor method is marked final, that will also cause no generation of an
additional accessor method to honor the final
requirement of no subclassing of such methods.
4.1. Annotation definition
An annotation is a kind of special interface dedicated at annotating elements of the code. An annotation is a type which
superinterface is the java.lang.annotation.Annotation interface. Annotations are declared in a very
similar 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 possible
member types are limited to:
enum DayOfWeek { mon, tue, wed, thu, fri, sat, sun }
@interface Scheduled {
DayOfWeek dayOfWeek() (6)
Unlike in the Java language, in Groovy, an annotation can be used to alter the semantics of the language. It is especially
true 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 var (3)
In order to limit the scope where an annotation can be applied, it is necessary to declare it on the annotation
definition, using the java.lang.annotation.Target annotation. For example, here is how you would
declare that an annotation can be applied to a class or a method:
import java.lang.annotation.ElementType
import java.lang.annotation.Target
@Target([ElementType.METHOD, ElementType.TYPE]) (1)
@interface SomeAnnotation {} (2)
However it is possible to omit value=
in the declaration of the value of an annotation if the member value
is the
only 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() {
// ...
4.1.3. Retention policy
The visibility of an annotation depends on its retention policy. The retention policy of an annotation is set using
the java.lang.annotation.Retention annotation:
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@Retention(RetentionPolicy.SOURCE) (1)
@interface SomeAnnotation {} (2)
The list of possible retention targets and description is available in the
java.lang.annotation.RetentionPolicy enumeration. The
choice usually depends on whether you want an annotation to be visible at
compile 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. Therefore
annotations may be used with a wide variety of expressions and still have IDE support. For example, imagine a
framework 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 a Closure
as an argument, you only have to declare the value
as a Class
:
@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)
4.2.1. Declaring meta-annotations
Meta-annotations, also known as annotation aliases are annotations that
are replaced at compile time by other annotations (one meta-annotation
is an alias for one or more annotations). Meta-annotations can be used to
reduce the size of code involving multiple annotations.
Let’s start with a simple example. Imagine you have the @Service
and @Transactional
annotations and that you want to annotate a class
with both:
@Service
@Transactional
class MyTransactionalService {}
Given the multiplication of annotations that you could add to the same class, a meta-annotation
could 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 {}
A meta-annotation is declared as a regular annotation but annotated with @AnnotationCollector
and the
list 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 {
Groovy supports both precompiled and source form
meta-annotations. This means that your meta-annotation may be
precompiled, or you can have it in the same source tree as the one you
are currently compiling.
INFO: Meta-annotations are a Groovy-only feature. There is
no chance for you to annotate a Java class with a meta-annotation and
hope it will do the same as in Groovy. Likewise, you cannot write a
meta-annotation in Java: both the meta-annotation definition and usage
have to be Groovy code. But you can happily collect Java annotations
and Groovy annotations within your meta-annotation.
When the Groovy compiler encounters a class annotated with a
meta-annotation, it replaces it with the collected annotations. So,
in our previous example, it will
replace @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 the
semantic analysis compilation phase.
In addition to replacing the alias with the collected annotations, a meta-annotation is capable of
processing 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')
@AnnotationCollector
public @interface Explosive {}
By default, when the annotations are replaced, they will get the
annotation parameter values as they were defined in the alias. More interesting,
the meta-annotation supports overriding specific values:
@Explosive(after=0) (1)
class Bomb {}
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)
It is a compile time error if the collected annotations define the same members
with incompatible types. For example if on the previous example @Foo
defined a value of
type String
but @Bar
defined a value of type int
.
It is however possible to customize the behavior of meta-annotations and describe how collected
annotations are expanded. We’ll look at how to do that shortly but first there is an advanced
processing option to cover.
4.2.4. Handling duplicate annotations in meta-annotations
The @AnnotationCollector
annotation supports a mode
parameter which can be used to
alter how the default processor handles annotation replacement in the presence of
duplicate 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
annotation
and then place your meta-annotation on a class that already has an explicit @ToString
annotation. Should this be an error? Should both annotations be applied? Does one take
priority over the other? There is no correct answer. In some scenarios it might be
quite appropriate for any of these answers to be correct. So, rather than trying to
preempt one correct way to handle the duplicate annotation issue, Groovy lets you
write your own custom meta-annotation processors (covered next) and lets you write
whatever checking logic you like within AST transforms - which are a frequent target for
aggregating. Having said that, by simply setting the mode
, a number of commonly
expected scenarios are handled automatically for you within any extra coding.
The behavior of the mode
parameter is determined by the AnnotationCollectorMode
enum value chosen and is summarized in the following table.
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 a
meta-annotation into collected annotations. The behaviour of the meta-annotation is,
in this case, totally up to you. To do this, you must:
create a meta-annotation processor, extending org.codehaus.groovy.transform.AnnotationCollectorTransform
declare the processor to be used in the meta-annotation declaration
To illustrate this, we are going to explore how the meta-annotation @CompileDynamic
is implemented.
@CompileDynamic
is a meta-annotation that expands itself
to @CompileStatic(TypeCheckingMode.SKIP)
. The problem is that the
default meta annotation processor doesn’t support enums and the
annotation value TypeCheckingMode.SKIP
is one.
The naive implementation here would not work:
@CompileStatic(TypeCheckingMode.SKIP)
@AnnotationCollector
public @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 longer
annotated with @CompileStatic
. The reason for this is that we rely on
the processor
parameter instead, that references a class which
will 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)
our custom processor is written in Groovy, and for better compilation performance, we use static compilation
the custom processor has to extend org.codehaus.groovy.transform.AnnotationCollectorTransform
create a class node representing the @CompileStatic
annotation type
create a class node representing the TypeCheckingMode
enum type
collector
is the @AnnotationCollector
node found in the meta-annotation. Usually unused.
aliasAnnotationUsage
is the meta-annotation being expanded, here it is @CompileDynamic
aliasAnnotated
is the node being annotated with the meta-annotation
sourceUnit
is the SourceUnit
being compiled
we create a new annotation node for @CompileStatic
we create an expression equivalent to TypeCheckingMode.SKIP
we add that expression to the annotation node, which is now @CompileStatic(TypeCheckingMode.SKIP)
return the generated annotation
In the example, the visit
method is the only method which has to be overridden. It is meant to return a list of
annotation nodes that will be added to the node annotated with the meta-annotation. In this example, we return a
single one corresponding to @CompileStatic(TypeCheckingMode.SKIP)
.
They can be seen as interfaces carrying both default implementations and state. A trait is defined using the
trait
keyword:
trait FlyingAbility { (1)
String fly() { "I'm flying!" } (2)
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)
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"
Traits only support public
and private
methods. Neither protected
nor package private
scopes are
supported.
5.1.4. Final methods
If we have a class implementing a trait, conceptually implementations from the trait methods
are "inherited" into the class. But, in reality, there is no base class containing such
implementations. Rather, they are woven directly into the class. A final modifier on a method
just indicates what the modifier will be for the woven method. While it would likely be
considered bad style to inherit and override or multiply inherit methods with the same
signature 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 you
want trait implementation methods that can’t be overridden.
def p = new Person()
assert p.greeting() == 'Hello, Bob!' (5)
assert p instanceof Named (6)
assert p instanceof Greetable (7)
class Person implements Named {} (2)
def p = new Person(name: 'Bob') (3)
assert p.name == 'Bob' (4)
assert p.getName() == 'Bob' (5)
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. Traits
will 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
This is a major difference with Java 8 virtual extension methods. While virtual extension methods
do 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. This
means that even if a trait can be seen from a Java class as a regular interface, that interface will not 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 the diamond 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)
The name of the field depends on the fully qualified name of the trait. All dots (.
) in package are replaced with an underscore (_
), and the final name includes a double underscore.
So if the type of the field is String
, the name of the package is my.package
, the name of the trait is Foo
and the name of the field is bar
,
in the implementing class, the public field will appear as:
String my_package_Foo__bar
5.7. Overriding default methods
Traits provide default implementations for methods, but it is possible to override them in the implementing class. For example, we
can slightly change the example above, by having a duck which quacks:
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)
5.8.2. Multiple inheritance
Alternatively, a trait may extend multiple traits. In that case, all super traits must be declared in the implements
clause:
trait WithId { (1)
Long id
trait WithName { (2)
String name
trait Identified implements WithId, WithName {} (3)
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, call
methods which are supposed to exist in an implementing class, without having to explicitly declare them in an interface.
This means that traits are fully compatible with duck typing:
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)
5.9.2. Dynamic methods in a trait
It is also possible for a trait to implement MOP methods like methodMissing
or propertyMissing
, in which case implementing classes
will inherit the behavior from the trait, like in this example:
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)
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 a
method 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)
In this case, the default behavior is that the method from the last declared trait in the implements
clause wins.
Here, B
is declared after A
so the method from B
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 the Trait.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)
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 a
trait. 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)
the call to extra would fail because Something
is not implementing Extra
. It is possible to do it at runtime with
the following syntax:
def s = new Something() as Extra (1)
s.extra() (2)
s.doSomething() (3)
When coercing an object to a trait, the result of the operation is not the same instance. It is guaranteed
that the coerced object will implement both the trait and the interfaces that the original object implements, but
the result will not be an instance of the original class.
When coercing an object to multiple traits, the result of the operation is not the same instance. It is guaranteed
that the coerced object will implement both the traits and the interfaces that the original object implements, but
the result will not be an instance of the original class.
5.12. Chaining behavior
Groovy supports the concept of stackable traits. The idea is to delegate from one trait to the other if the current trait
is not capable of handling a message. 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 the
form 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)
trait LoggingHandler implements MessageHandler { (1)
void on(String message, Map payload) {
println "Seeing $message with payload $payload" (2)
super.on(message, payload) (3)
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 that LoggerHandler
wins because it is declared last, then a call to on
will use
the implementation from LoggingHandler
. But the latter has a call to super
, which means the next trait in the
chain. Here, the next trait is DefaultHandler
so both will be called:
The interest of this approach becomes more evident if we add a third handler, which is responsible for handling messages
that start with say
:
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)
class Handler implements DefaultHandler, SayHandler, LoggingHandler {}
def h = new Handler()
h.on('foo', [:])
h.on('sayHello', [:])
Which means:
the logging handler calls super
which will delegate to the next handler, which is the SayHandler
if the message starts with say
, then the handler consumes the message
if not, the say
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 you
combine 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 the SayHandler
consumes the message without calling super
, the logging handler is
not called anymore.
5.12.1. Semantics of super inside a trait
If a class implements multiple traits and a call to an unqualified super
is found, then:
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)
In this example, when super.append
is encountered, there is no other trait implemented by the target object, so the
method which is called is the original append
method, that is to say the one from StringBuilder
. The same trick
is used for toString
, so that the string representation of the proxy object which is generated delegates to the
toString
of the StringBuilder
instance.
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)
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 provide
an implementation for a default method, then the implementation from the interface is chosen. Traits behave the same but
with a major difference: the implementation from the trait is always used if the class declares the trait in its interface
list and that it doesn’t provide an implementation even 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 an
already implemented method.
To illustrate the concept, let’s start with this simple example:
import groovy.test.GroovyTestCase
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
class 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 and shell) and uses those in
multiple test methods. Now imagine that you want to test the same, but with another distinct compiler configuration.
One option is to create a subclass of SomeTest
:
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 all
those 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 the setup
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 setup
code in case we decide to change it. Even if setup
is already implemented in the super class, since the test class declares
the 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 to
mock methods or force a particular implementation of a method in a subclass. It lets you refactor your code to keep
the overridden logic in a single trait and inherit a new behavior just by implementing it. The alternative, of course,
is to override the method in every place you would have used the new code.
It’s worth noting that if you use runtime traits, the methods from the trait are always preferred to those of the proxied
object:
Again, don’t forget that dynamic trait coercion returns a distinct object which only implements the original
interfaces, 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 about
runtime 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 an interface
those methods are visible from Java
they are compatible with type checking and static compilation
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)
The last point is actually a very important and illustrates a place where mixins have an advantage over traits: the instances
are not modified, so if you mixin some class into another, there isn’t a third class generated, and methods which respond to
A will continue responding to A even if mixed in.
5.15. Static methods, properties and fields
The following instructions are subject to caution. Static member support is work in progress and still experimental. The
information below is valid for 4.0.12 only.
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 a template for the implementing class, which means that each
implementing class will get its own static methods, properties and fields. So a static member
declared on a trait doesn’t belong to the Trait
, but to its implementing class.
You should typically not mix static and instance methods of the same signature. The normal
rules for applying traits apply (including multiple inheritance conflict resolution). If the
method chosen is static but some implemented trait has an instance variant, a compilation error
will 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).
class Bar implements TestHelper {} (1)
class Baz implements TestHelper {} (2)
Bar.init() (3)
assert Bar.TestHelper__CALLED (4)
assert !Baz.TestHelper__CALLED (5)
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 on
a 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
and y
, as well as a sum
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 calling f
is 3
, because f
delegates to sum
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()
The reason is that the sum
method accesses the fields of the trait. So it is using the x
and y
values defined
in the trait. If you want to use the values from the implementing class, then you need to dereference fields by using
getters 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.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 a
trait 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)
It is clear, here, that the Communicating
trait can only apply to Device
. However, there’s no explicit
contract to indicate that, because traits cannot extend classes. However, the code compiles and runs perfectly
fine, because id
in the trait method will be resolved dynamically. The problem is that there is nothing that
prevents the trait from being applied to any class which is not a Device
. Any class which has an id
would
work, while any class that does not have an id
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: because
the trait knows nothing about itself being a Device
, the type checker will complain saying that it does not find
the id
property.
One possibility is to explicitly add a getId
method in the trait, but it would not solve all issues. What if a method
requires this
as a parameter, and actually requires it to be a Device
?
class SecurityService {
static void check(Device d) { if (d.id==null) throw new SecurityException() }
If you want to be able to call this
in the trait, then you will explicitly need to cast this
into a Device
. This can
quickly become unreadable with explicit casts to this
everywhere.
5.17.2. The @SelfType annotation
In order to make this contract explicit, and to make the type checker aware of the type of itself, Groovy provides
a @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
trait 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 is not 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 contract
directly 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 == 1d
assert new UnitCylinder().volume == 0.7853981633974483d
Traits are not officially compatible with AST transformations. Some of them, like @CompileStatic
will be applied
on the trait itself (not on implementing classes), while others will apply on both the implementing class and the trait.
There is absolutely no guarantee that an AST transformation will run on a trait as it does on a regular class, so use it
at your own risk!
Record classes, or records for short, are a special kind of class
useful 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 @Canonical
which already dramatically reduce ceremony but records have been
introduced in Java and record classes in Groovy are designed to align
with Java record classes.
For example, suppose we want to create a Message
record
representing an email message. For the purposes of this example,
let’s simplify such a message to contain just a from email address,
a to email address, and a message body. We can define such
a 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('[email protected]', '[email protected]', 'Hello!')
assert msg.toString() == 'Message[[email protected], [email protected], body=Hello!]'
The reduced ceremony saves us from defining explicit fields, getters and
toString
, equals
and hashCode
methods. In fact, it’s a shorthand
for 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 term component
is typically used for records. So our Message
record has from
, to
, and body
components.
Like in Java, you can override the normally implicitly supplied methods
by 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 following Coord
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() == 5
def 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 way
by providing your own constructor - you need to make sure you set all the fields
if you do this.
However, for succinctness, a compact constructor syntax can be used where
the parameter declaration part of a normal constructor is elided.
For this special case, the normal implicit constructor is still provided
but 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'
Groovy native records follow the
special conventions
for serializability which apply to Java records.
Groovy record-like classes (discussed below) follow normal Java class serializability conventions.
Groovy supports default values for constructor arguments.
This capability is also available for records as shown in the following record definition
which has default values for y
and color
:
record ColoredPoint(int x, int y = 0, String color = 'white') {}
Arguments when left off (dropping one or more arguments from the right) are replaced
with 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 a Message
record and displayed it’s rough equivalent.
Groovy in fact steps through an intermediate stage where the record
keyword
is replaced by the class
keyword and an accompanying @RecordType
annotation:
@RecordType
class Message {
String from
String to
String body
Then @RecordType
itself is processed as a meta-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 class
with one of the constituent sub-annotations.
6.2.2. Declarative toString
customization
As per Java, you can customize a record’s toString
method by writing your own.
If you prefer a more declarative style, you can alternatively use Groovy’s @ToString
transform
to override the default record toString
.
As an example, you can a three-dimensional point record as follows:
package threed
import 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 the toString
by including the package name (excluded by default for records)
and by caching the toString
value since it won’t change for this immutable record.
We are also ignoring null values (the default value for z
in our definition).
We can have a similar definition for a two-dimensional point:
package twod
import 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 == 100
assert y == 200
assert 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.
It can be useful to make a copy of a record with some components changed.
This can be done using an optional copyWith
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 use copyWith
for the Fruit
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]'
The copyWith
functionality can be disabled by setting the
RecordOptions#copyWith
annotation attribute to false
.
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 mutable
data types. Records can make use of this defensive copying to gain deep immutability as follows:
@ImmutableProperties
record Shopping(List items) {}
def items = ['bread', 'milk']
def shop = new Shopping(items)
items << 'chocolate'
assert shop.items() == ['bread', 'milk']
These examples illustrate the principal behind
Groovy’s record feature offering three levels of convenience:
def 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 of TupleN
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 creating record-like classes as well as native records.
Record-like classes don’t extend Java’s Record
class and such classes
won’t be seen by Java as records but will otherwise have similar properties.
The @RecordOptions
annotation (part of @RecordType
) supports a mode
annotation attribute
which can take one of three values (with AUTO
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.
Produces a native record for JDK16+ and emulates the record otherwise.
Sealed classes, interfaces and traits restrict which subclasses can extend/implement them.
Prior to sealed classes, class hierarchy designers had two main options:
Sealed classes are also more flexible than other tricks previously used
to try to achieve a middle-ground. For example, for class hierarchies,
access modifiers like protected and package-private give some ability to restrict inheritance
hierarchies but often at the expense of flexible use of those hierarchies.
Sealed hierarchies provide full inheritance within a known hierarchy of classes, interfaces
and traits but disable or only provide controlled inheritance outside the hierarchy.
As an example, suppose we want to create a shape hierarchy containing
only circles and squares. We also want a shape interface to
be 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 type ShapeI
which, thanks to the permits
clause,
can point to either a Circle
or Square
and, since our classes are final
,
we know no additional classes will be added to our hierarchy in the future.
At least not without changing the permits
clause and recompiling.
In general, we might want to have some parts of our class hierarchy
immediately locked down like we have here, where we marked the
subclasses as final
but other times we might want to allow further
controlled 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 for Shape
are Circle
, Polygon
, and Rectangle
.
Circle
is final
and hence that part of the hierarchy cannot be extended.
Polygon
is implicitly non-sealed and RegularPolygon
is explicitly marked as non-sealed
.
That means our hierarchy is open to any further extension by subclassing,
as seen with Polygon → RegularPolygon
and RegularPolygon → Hexagon
.
Rectangle
is itself sealed which means that part of the hierarchy can be extended
but only in a controlled way (only Square
is permitted).
Sealed classes are useful for creating enum-like related classes
which 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 classes
and requires that one of final
, sealed
or non-sealed
be specified.
Groovy defaults to non-sealed but you can still use non-sealed/@NonSealed
if you wish.
We anticipate the style checking tool CodeNarc will eventually have a rule that
looks for the presence of non-sealed
so developers wanting that stricter
style will be able to use CodeNarc and that rule if they want.
Currently, Groovy doesn’t check that all classes mentioned in permittedSubclasses
are available at compile-time and compiled along with the base sealed class.
This may change in a future version of Groovy.
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.
Produces a native record for JDK17+ and emulates the record otherwise.
The Groovy programming language is supported by the Apache Software Foundation and the Groovy community.
Apache® and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.
© 2003-2023 the Apache Groovy project — Groovy is Open Source: license, privacy policy.
有爱心的山寨机 · 限制片网站:选择与影响_三围小说网 3 月前 |
飘逸的冰淇淋 · 如何在Three.js中加载模型? 9 月前 |
无邪的小蝌蚪 · 唐三(《斗罗大陆》中的角色)_搜狗百科 1 年前 |
绅士的大白菜 · 廊坊市人民政府 - 手机版 1 年前 |