Built-in AST Transformations
24.1 Code Generation
toString() (options: includeNames, excludes, includePackage) equals() + hashCode() Positional constructor(s)
@ToString + @EqualsAndHashCode + @TupleConstructor Immutable class (final fields, defensive copies, canonical methods) @ImmutableOptions, @KnownImmutable
Builder pattern (strategies: default, simple, external, initializer) @AutoClone, @AutoExternalize Cloning, serialization
Comparable implementation + comparators per property Copies superclass constructors
Delegates methods to a field (composition) Indexed getters/setters for list/array properties
Lazy field initialization (optionally soft references) Ruby/Python-style Person.new(...) or Person(...) instantiation
@NamedVariant, @NamedParam, @NamedDelegate Type-safe named parameters @RecordType / record
Singleton pattern (lazy, strict options) Method result caching Tail-call optimization into a loop
@ListenerList, @Bindable, @Vetoable Event and property-change support
@PropertyOptions, @VisibilityOptions Fine-grained generation control
import groovy.transform.*
@Canonical
class Book { String title; String author; int year }
@Immutable
class Money { BigDecimal amount; String currency }
@Builder
class Request { String url; String method = "GET"; Map headers = [:] }
@ToString(includeNames = true, excludes = ['password'])
@EqualsAndHashCode(includes = ['id'])
class Account { Long id; String user; String password }
@Sortable(includes = ['last', 'first'])
class Name { String first; String last }
class Team {
@Delegate List<String> members = [] // Team now has add(), size(), each()...
}
class Expensive {
@Lazy List<String> data = { println "loading"; ['x', 'y'] }()
@Memoized long compute(int n) { (1..n).sum() }
@TailRecursive long sumTo(long n, long acc = 0) { n == 0 ? acc : sumTo(n - 1, acc + n) }
}
def b = new Book("Groovy in Action", "König", 2015)
println b, b == new Book("Groovy in Action", "König", 2015)
def m = new Money(10.0, "USD")
// m.amount = 5 // ReadOnlyPropertyException
def req = Request.builder().url("https://x.com").method("POST").build()
def t = new Team(); t << "Ana"; t.add("Bo"); println t.size()
println new Expensive().sumTo(100000)24.2 Class Design, Safety and Logging
@TypeChecked, @CompileStatic, @CompileDynamic Static typing (Module 23) Script variable → field Custom script base class
Safer synchronized (private lock object) @WithReadLock, @WithWriteLock
@ThreadInterrupt, @TimedInterrupt, @ConditionalInterrupt
Safe script execution (interrupt long-running scripts) @Log, @Log4j, @Log4j2, @Slf4j, @Commons, @PlatformLog
Inject a log field (lazy message evaluation) Make parameters final
Implement abstract methods with defaults or exceptions @Final, @Sealed, @NonSealed Throw on null arguments
@Grab, @GrabResolver, @GrabExclude, @GrabConfig Grape dependencies (Module 41) Script source location @OperatorRename (Groovy 5)
Map operators to differently named methods
import groovy.util.logging.Slf4j
@Slf4j
class OrderService {
@groovy.transform.Synchronized
void process(order) {
log.info "Processing ${order}" // GString only evaluated if INFO is enabled
}
}24.3 Contracts (groovy-contracts, Groovy 4)
import groovy.contracts.*
@Invariant({ balance >= 0 })
class Account {
BigDecimal balance = 0
@Requires({ amount > 0 })
@Ensures({ balance == old.balance + amount })
void deposit(BigDecimal amount) { balance += amount }
}