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 }
}