๐ Groovy Core Philosophy
๐ฏ The Agile JVM Language
Groovy (James Strachan, 2003) is a powerful, optionally typed, dynamic language for the JVM with seamless Java integration. It combines Python/Ruby-like conciseness with Java's robustness. Key pillars: closures, builders, metaprogramming, and DSL support. Groovy compiles to Java bytecode and runs anywhere Java runs.
Deep Explanation
Groovy is both dynamic and static. With @CompileStatic, you get Java-like performance. Without, you leverage dynamic method dispatch, runtime metaprogramming, and categories. Groovy's Multi-methods dispatch based on runtime types. The GDK extends Java's core classes with hundreds of convenience methods.
Why it matters: Groovy reduces boilerplate by 50-80% compared to Java, making it ideal for scripts, build systems (Gradle), testing (Spock), and internal DSLs.
// Groovy's conciseness vs Java
// No semicolons, optional parentheses, optional types
def name = "Groovy"
println "Hello, $name" // GString interpolation
// Dynamic list and map literals
def list = [1, 2, 3]
def map = [name: "Groovy", type: "JVM"]
println list.class // java.util.ArrayList
// Safe navigation operator
def person = null
println person?.name ?: "Anonymous" // Elvis operator๐ญ Dynamic & Static Nature
๐ฏ def vs @CompileStatic
Groovy is dynamic by default but supports static compilation via annotations. Type checking with @TypeChecked.
Dynamic Dispatch vs Static Safety
Dynamic mode enables runtime method addition (ExpandoMetaClass), methodMissing, and propertyMissing. Static mode catches errors at compile time and performs Java-like performance. Use @CompileStatic for critical paths.
// Dynamic: add methods at runtime
String.metaClass.reverseAndShout = { -> delegate.reverse().toUpperCase() }
println "hello".reverseAndShout() // OLLEH
// Static compilation
import groovy.transform.CompileStatic
@CompileStatic
int add(int a, int b) { a + b }
// Type checking
import groovy.transform.TypeChecked
@TypeChecked
void safeMethod(String s) {
// println s.length() works; but dynamic dispatch disabled
}๐ Closures & Lambdas
๐ฏ First-Class Functions
Closures are anonymous blocks with lexical binding, currying, delegation strategy (OWNER_FIRST, DELEGATE_FIRST).
Closure Magic
Closures remember their surrounding context (owner, delegate, this). They support currying (.curry()), trampoline for recursion, and method pointers (&.methodName). Used heavily in Gradle and collections.
// Basic closure
def greet = { name -> "Hello, $name" }
println greet("Groovy")
// Currying
def multiply = { a, b -> a * b }
def doubleIt = multiply.curry(2)
println doubleIt(5) // 10
// Collection iteration with closure
[1,2,3].collect { it * 2 } // [2,4,6]
// Method pointer
def strings = ["a", "bb", "ccc"]
println strings.collect(&.toUpperCase)๐ GDK Collections Power
๐ฏ Enriched Collection API
Java Collections extended with each, findAll, collect, inject, groupBy, countBy and many more.
Functional Style
Groovy's GDK adds functional methods to all collections. findAll filters, collect maps, inject reduces, groupBy organizes. These are lazy with .iterator() or eager by default.
def numbers = 1..10
def evens = numbers.findAll { it % 2 == 0 } // [2,4,6,8,10]
def squares = numbers.collect { it * it }
def sum = numbers.inject(0) { acc, val -> acc + val }
def fruits = ['apple', 'banana', 'apple', 'orange']
def countMap = fruits.countBy { it } // [apple:2, banana:1, orange:1]
// Nested collections
def matrix = [[1,2],[3,4]]
println matrix.flatten() // [1,2,3,4]๐ GStrings & Multiline
๐ฏ String Interpolation
GString ("Hello $name"), multiline triple quotes ('''text''' or """text"""), slashy strings for regex.
Flexible String Handling
GStrings are lazy-evaluated: expressions inside ${} are re-evaluated when the GString is used in certain contexts. Multiline strings preserve formatting. Slashy strings (/regex/) simplify regex patterns.
def name = "Groovy"
def greeting = "Hello, ${name.toUpperCase()}"
println greeting // Hello, GROOVY
// Multiline
def poem = """
Roses are red,
Violets are blue,
Groovy is sweet,
And so are you.
"""
// Slashy string for regex
def pattern = /(\w+)@(\w+)\.\w+/
def matcher = "user@example.com" =~ pattern
println matcher[0][1] // user๐ง Operator Overloading & Elvis
๐ฏ Groovy Operators
Elvis ?:, safe navigation ?., spaceship <=>, spread *., and overloadable operators.
Expressive Syntax
Elvis operator returns default value when left side is null. Spread operator calls method on all collection elements. All operators map to method names (plus, minus, getAt, putAt).
// Elvis operator
def value = null
def result = value ?: "default"
// Safe navigation
def person = [address: null]
def city = person?.address?.city ?: "Unknown"
// Spread operator
def words = ["Groovy", "Closure"]
println words*.toUpperCase() // [GROOVY, CLOSURE]
// Overloading + for custom class
class Complex {
int real, imag
Complex plus(Complex other) {
new Complex(real + other.real, imag + other.imag)
}
}๐ Groovy Control Structures
๐ฏ Enhanced Syntax
switch with any type, for-in loops, assert with power asserts, with(), use() categories.
Smart Control Flow
Groovy's switch supports any object (String, Class, regex patterns). Power asserts display detailed expression trees. with() eliminates repetitive obj. prefixes.
// Switch on any type
def checkType(value) {
switch(value) {
case String: return "String"
case ~/\d+/: return "Number string"
case 0..100: return "Between 0-100"
default: return "Other"
}
}
println checkType("42") // Number string
// Power assert
def x = 5
assert x * 2 == 11 : "Should be 10"
// with() for grouping
def sb = new StringBuilder()
sb.with {
append("Groovy ")
append("is ")
append("awesome")
}๐๏ธ POGO & Metaprogramming
๐ฏ Plain Old Groovy Objects
Automatic property accessors, @Canonical, @Builder, @Delegate, runtime metaclass modifications.
AST Transformations
Groovy's compile-time metaprogramming (AST transforms) injects boilerplate code. @ToString, @EqualsAndHashCode, @TupleConstructor reduce verbosity. ExpandoMetaClass enables runtime method injection.
import groovy.transform.*
@Canonical
@Builder
class Developer {
String name
int years
}
def dev = Developer.builder().name("Alice").years(5).build()
println dev // Developer(Alice, 5)
// Runtime metaclass
String.metaClass.scream = { -> delegate.toUpperCase() + "!!!" }
println "groovy".scream() // GROOVY!!!๐ DSL & Builders
๐ฏ Internal DSLs
Groovy's builder pattern: MarkupBuilder, JsonBuilder, SwingBuilder, and custom DSLs using methodMissing and propertyMissing.
Creating Fluent APIs
Builders transform nested closures into object structures. MarkupBuilder generates XML/HTML. Command chains allow natural language DSLs.
import groovy.xml.MarkupBuilder
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.team {
member(role: "lead") { name("Alice") }
member(role: "dev") { name("Bob") }
}
println writer.toString()
// Command chain (natural language)
def move = { args -> println "Moving to $args" }
def forward = { move("forward") }
def backward = { move("backward") }๐ XML/JSON Slurpers
๐ฏ GPath & Parsing
XmlSlurper, JsonSlurper provide XPath-like navigation (GPath) for easy data extraction.
Effortless Parsing
XmlSlurper handles large XML lazily. JsonSlurper converts JSON to native Groovy maps/lists. GPath syntax: xml.book[0].author.
import groovy.json.JsonSlurper
import groovy.xml.XmlSlurper
// JSON slurper
def jsonText = '{"name":"Groovy", "version":4.0}'
def data = new JsonSlurper().parseText(jsonText)
println data.name
// XML slurper
def xml = '''Groovy in Action '''
def parsed = new XmlSlurper().parseText(xml)
println parsed.book.title.text()โ Groovy-Java Integration
๐ฏ Seamless Interop
Groovy can import Java classes, extend Java classes, implement Java interfaces. Java can call Groovy via scripting engine or compiled classes.
Two-Way Integration
From Java, use GroovyScriptEngine or compile Groovy to .class. From Groovy, all Java libraries are available without wrappers. Groovy closures automatically convert to SAM interfaces (Single Abstract Method).
// Groovy using Java's CompletableFuture
import java.util.concurrent.CompletableFuture
def future = CompletableFuture.supplyAsync { "Hello from Groovy" }
println future.get()
// Java can call Groovy script (in Java code)
// ScriptEngine engine = new ScriptEngineManager().getEngineByName("groovy");
// engine.eval("println 'Hello Java'");
// SAM conversion: Groovy closure as Runnable
Runnable task = { println "Running in thread" }๐ Grape Dependency Manager
๐ฏ @Grab Annotations
Declarative dependency fetching from Maven Central/Ivy. Resolves transitive dependencies at runtime.
Zero-Build Scripts
@Grab avoids build files. Perfect for scripts and one-off tools. Grape caches artifacts locally, supports custom resolvers.
// Script with dependencies, no build.gradle needed!
/*
@Grab(group='org.apache.commons', module='commons-math3', version='3.6.1')
*/
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
def stats = new DescriptiveStatistics()
[1,2,3,4,5].each { stats.addValue(it) }
println "Mean: ${stats.mean}, Std: ${stats.standardDeviation}"๐งช Spock & Testing
๐ฏ Specification Framework
Spock: given-when-then blocks, mocking, data-driven testing. Built on JUnit.
Expressive Tests
Spock's blocks create readable tests. Data-driven tests with where: input | expected simplify parameterization. Interaction-based mocking replaces traditional mocking frameworks.
// Spock specification example
/*
def "addition should work"() {
given:
def calculator = new Calculator()
expect:
calculator.add(a, b) == result
where:
a | b || result
1 | 1 || 2
2 | 3 || 5
}
*/
// Groovy's built-in assertion is test-friendly
assert [1,2,3].sum() == 6โฉ GPars & Concurrency
๐ฏ Parallelism for Groovy
GPars (Groovy Parallel Systems) provides actors, dataflow, fork/join, and parallel collections.
High-Level Concurrency
GPars simplifies multi-threading with GParsPool for parallel collection operations, actors for message-passing, and dataflow for reactive streams. Works with Groovy's closures seamlessly.
import static groovyx.gpars.GParsPool.withPool
def numbers = 1..1000
withPool(4) {
def results = numbers.collectParallel { it * it }
def sumParallel = numbers.sumParallel()
println "Sum: $sumParallel"
}
// Actor example (simplified)
// def actor = Actors.actor { loop { react { println it } } }
// actor.send("Hello")