Groovy

#27
PYPL#27
GitHub#61
IEEESpectrum#26
programming languagedynamic languageJVMscripting languageJava compatibleDSL

Programming Language

Groovy

Overview

Groovy is a dynamic programming language that runs on the JVM, providing high compatibility with Java and more concise syntax.

Details

Groovy is a dynamic programming language developed by James Strachan in 2003 that runs on the JVM, maintaining full compatibility with Java while providing more concise and expressive syntax. It supports both static and dynamic typing, combining functional and object-oriented programming features. It can directly utilize Java libraries and seamlessly integrate with existing Java code. It provides many features that enhance developer productivity, including closures, operator overloading, string interpolation, and concise collection operations. It is widely adopted in Apache Gradle build tool, Jenkins CI/CD, Spring Framework, and other areas, being utilized in scripting, test automation, DSL (Domain Specific Language) construction, and web development.

Code Examples

Hello World

// Basic output
println "Hello, World!"

// Output using variables
def message = "Hello, Groovy!"
println message

// String interpolation
def name = "John"
def age = 25
println "My name is ${name} and I am ${age} years old."

// GString (Groovy String)
def template = """
Name: $name
Age: $age
Description: This is Groovy string interpolation.
"""
println template

// Java-style output is also possible
System.out.println("Java-compatible output")

Variables and Data Types

// Dynamic typing (def)
def dynamicVar = 42
dynamicVar = "changed to string"
dynamicVar = [1, 2, 3, 4, 5]

// Static typing
String staticString = "static typed string"
int staticInt = 100
boolean staticBoolean = true

// Primitive types
byte b = 1
short s = 100
int i = 1000
long l = 10000L
float f = 3.14f
double d = 3.14159
char c = 'A'

// Lists (dynamic arrays)
def list = [1, 2, 3, 4, 5]
def mixedList = [1, "string", true, 3.14]
def emptyList = []

// List operations
list << 6  // Add element
list += [7, 8, 9]  // Add multiple elements
println "List: $list"
println "First element: ${list[0]}"
println "Last element: ${list[-1]}"

// Maps (associative arrays)
def map = [name: "John Doe", age: 30, city: "Tokyo"]
def emptyMap = [:]

// Map operations
map.job = "Engineer"  // Add new key
map["hobby"] = "Reading"   // Bracket notation
println "Map: $map"
println "Name: ${map.name}"

// Ranges
def range1 = 1..10     // 1 to 10 (inclusive)
def range2 = 1..<10    // 1 to 10 (exclusive)
def charRange = 'a'..'z'

println "Range1: $range1"
println "Character range: $charRange"

// Regular expressions
def pattern = ~/\d+/   // Groovy regex literal
def text = "I am 25 years old"
def matcher = text =~ pattern
println "Matched number: ${matcher[0]}"

Conditional Statements

def score = 85

// Basic if statement
if (score >= 90) {
    println "Grade: A"
} else if (score >= 80) {
    println "Grade: B"
} else if (score >= 70) {
    println "Grade: C"
} else {
    println "Grade: D"
}

// Ternary operator
def grade = score >= 80 ? "Pass" : "Fail"
println "Result: $grade"

// Elvis operator (null coalescing operator)
def name = null
def displayName = name ?: "Anonymous"
println "Display name: $displayName"

// Switch statement (Groovy extensions)
def value = "string"
switch (value) {
    case String:
        println "It's a string type"
        break
    case Integer:
        println "It's an integer type"
        break
    case List:
        println "It's a list type"
        break
    case ~/\d+/:  // Regex matching
        println "It's a numeric pattern"
        break
    default:
        println "Other type"
}

// Switch with in operator
def number = 5
switch (number) {
    case 1..5:
        println "Range 1 to 5"
        break
    case [10, 15, 20]:
        println "One of 10, 15, 20"
        break
    default:
        println "Other"
}

// Safe navigation operator
def person = null
println "Name: ${person?.name}"  // No error even if null

// Groovy Truth (Groovy's boolean evaluation)
if ("") println "Empty string is false"        // Not executed
if ("string") println "String is true"         // Executed
if ([]) println "Empty list is false"          // Not executed
if ([1, 2, 3]) println "List is true"         // Executed
if (0) println "0 is false"                   // Not executed
if (1) println "Non-zero is true"             // Executed

Collections and Higher-Order Functions

// List higher-order functions
def numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// each (forEach equivalent)
numbers.each { num ->
    println "Number: $num"
}

// eachWithIndex
numbers.eachWithIndex { num, index ->
    println "Index $index: $num"
}

// collect (map equivalent)
def squares = numbers.collect { it * it }
println "Squares: $squares"

// find (first matching element)
def firstEven = numbers.find { it % 2 == 0 }
println "First even: $firstEven"

// findAll (filter equivalent)
def evenNumbers = numbers.findAll { it % 2 == 0 }
println "Even numbers: $evenNumbers"

// any (any element satisfies condition)
def hasLargeNumber = numbers.any { it > 5 }
println "Has number > 5: $hasLargeNumber"

// every (all elements satisfy condition)
def allPositive = numbers.every { it > 0 }
println "All positive: $allPositive"

// sum
def total = numbers.sum()
println "Total: $total"

// min/max
println "Min: ${numbers.min()}"
println "Max: ${numbers.max()}"

// sort
def sorted = numbers.sort { a, b -> b <=> a }  // Descending
println "Sorted descending: $sorted"

// groupBy
def grouped = numbers.groupBy { it % 2 == 0 ? "even" : "odd" }
println "Grouped: $grouped"

// Map operations
def grades = [John: 85, Jane: 92, Bob: 78]

grades.each { name, grade ->
    println "$name: $grade"
}

def highGrades = grades.findAll { name, grade -> grade >= 80 }
println "High grades: $highGrades"

// String operations
def text = "Hello, Groovy World!"
println "Uppercase: ${text.toUpperCase()}"
println "Split: ${text.split(' ')}"

// List comprehension style
def evenSquares = (1..10).findAll { it % 2 == 0 }.collect { it * it }
println "Even squares: $evenSquares"

Functions and Closures

// Basic function (method) definition
def greet(name) {
    return "Hello, ${name}!"
}

// Return type specification
String greetTyped(String name) {
    "Hello, ${name}!"  // return can be omitted
}

// Default arguments
def calculateArea(width, height = 10) {
    width * height
}

// Variable arguments
def sum(... numbers) {
    numbers.sum()
}

// Closures (anonymous functions)
def square = { x -> x * x }
def add = { x, y -> x + y }

// Single parameter can use 'it'
def double = { it * 2 }

// Function taking closure as argument
def applyOperation(List list, Closure operation) {
    list.collect(operation)
}

// Higher-order function
def createMultiplier(factor) {
    return { number -> number * factor }
}

// Function usage examples
println greet("Alice")
println calculateArea(5)  // height uses default value
println sum(1, 2, 3, 4, 5)

println "Square: ${square(5)}"
println "Add: ${add(10, 20)}"

def doubled = applyOperation([1, 2, 3, 4, 5], double)
println "Doubled: $doubled"

def tripler = createMultiplier(3)
println "Triple: ${tripler(7)}"

// Closure delegate
class Calculator {
    def add(a, b) { a + b }
    def multiply(a, b) { a * b }
}

def calc = new Calculator()
def operation = {
    def result1 = add(5, 3)
    def result2 = multiply(4, 6)
    [result1, result2]
}

operation.delegate = calc
operation.resolveStrategy = Closure.DELEGATE_FIRST
println "Calculation result: ${operation()}"

// Recursive function
def factorial
factorial = { n ->
    n <= 1 ? 1 : n * factorial(n - 1)
}

println "Factorial: ${factorial(5)}"

// Memoization (Groovy 2.2+)
def fibonacci
fibonacci = { n ->
    n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2)
}.memoize()

println "Fibonacci: ${fibonacci(10)}"

Classes and Object-Oriented Programming

// Basic class definition
class Person {
    String name
    int age
    private String id
    
    // Constructor
    Person(String name, int age) {
        this.name = name
        this.age = age
        this.id = generateId()
    }
    
    // Method
    def greet() {
        "Hello, I'm ${name}."
    }
    
    def haveBirthday() {
        age++
        println "${name} is now ${age} years old."
    }
    
    private def generateId() {
        "ID_${System.currentTimeMillis()}"
    }
    
    // toString
    String toString() {
        "Person(name: $name, age: $age)"
    }
}

// Inheritance
abstract class Animal {
    String name
    
    Animal(String name) {
        this.name = name
    }
    
    abstract def makeSound()
    
    def introduce() {
        "I'm an animal named ${name}."
    }
}

class Dog extends Animal {
    String breed
    
    Dog(String name, String breed) {
        super(name)
        this.breed = breed
    }
    
    def makeSound() {
        "Woof"
    }
    
    def fetch() {
        "${name} fetched the ball."
    }
}

// Interfaces
interface Flyable {
    def fly()
}

interface Swimmable {
    def swim()
}

// Multiple interface implementation
class Duck extends Animal implements Flyable, Swimmable {
    Duck(String name) {
        super(name)
    }
    
    def makeSound() {
        "Quack"
    }
    
    def fly() {
        "${name} is flying"
    }
    
    def swim() {
        "${name} is swimming in the pond"
    }
}

// Traits (Groovy 2.3+)
trait Debuggable {
    def debug(message) {
        println "[DEBUG] ${this.class.simpleName}: $message"
    }
}

trait Timestamped {
    Date created = new Date()
    
    def getAge() {
        (System.currentTimeMillis() - created.time) / 1000
    }
}

// Class using traits
class Service implements Debuggable, Timestamped {
    String name
    
    Service(String name) {
        this.name = name
    }
    
    def doWork() {
        debug("Starting work: $name")
        // Some processing
        debug("Work completed")
    }
}

// Groovy properties (automatic getter/setter generation)
class Book {
    String title
    String author
    BigDecimal price
    
    // Custom getter
    String getDisplayTitle() {
        "\"$title\""
    }
    
    // Custom setter
    void setPrice(BigDecimal price) {
        if (price < 0) {
            throw new IllegalArgumentException("Price must be non-negative")
        }
        this.price = price
    }
}

// Usage examples
def person = new Person("John Doe", 30)
println person.greet()
person.haveBirthday()

def animals = [
    new Dog("Buddy", "Golden Retriever"),
    new Duck("Donald")
]

animals.each { animal ->
    println animal.introduce()
    println "Sound: ${animal.makeSound()}"
    if (animal instanceof Flyable) {
        println animal.fly()
    }
}

def service = new Service("Test Service")
service.doWork()
println "Service age: ${service.age} seconds"

def book = new Book(title: "Groovy in Action", author: "John Smith", price: 2500)
println "Book: ${book.displayTitle} by ${book.author}"

File Operations and I/O

// File reading and writing
def fileName = "sample.txt"

// Write to file
new File(fileName).text = """This is a test file.
Learning file operations in Groovy.
Can write multiple lines of text."""

// Read file
def content = new File(fileName).text
println "File content:\n$content"

// Read line by line
new File(fileName).eachLine { line, lineNumber ->
    println "Line $lineNumber: $line"
}

// File existence check and operations
def file = new File(fileName)
if (file.exists()) {
    println "File size: ${file.size()} bytes"
    println "Last modified: ${new Date(file.lastModified())}"
}

// CSV data processing
def csvData = """Name,Age,Job
John Doe,30,Engineer
Jane Smith,25,Designer
Bob Johnson,35,Manager"""

new File("employees.csv").text = csvData

// Simple CSV parser implementation
def employees = []
new File("employees.csv").eachLine { line, lineNumber ->
    if (lineNumber > 1) {  // Skip header
        def (name, age, job) = line.split(',')
        employees << [name: name, age: age as Integer, job: job]
    }
}

employees.each { emp ->
    println "${emp.name} (${emp.age}) - ${emp.job}"
}

// Directory operations
def dir = new File(".")
dir.eachFile { file ->
    println "${file.name} (${file.isDirectory() ? 'directory' : 'file'})"
}

// Groovy's @Grab (dynamic dependency resolution)
@Grab('org.apache.commons:commons-lang3:3.12.0')
import org.apache.commons.lang3.StringUtils

def text = "  Hello, World!  "
println "Trimmed: '${StringUtils.strip(text)}'"

// JSON processing
@Grab('org.apache.groovy:groovy-json:4.0.0')
import groovy.json.*

def jsonData = [
    name: "API Response",
    data: [
        [id: 1, title: "Item 1"],
        [id: 2, title: "Item 2"]
    ],
    status: "Success"
]

def jsonString = JsonOutput.toJson(jsonData)
println "JSON format:\n${JsonOutput.prettyPrint(jsonString)}"

def parsedJson = new JsonSlurper().parseText(jsonString)
println "Parsed name: ${parsedJson.name}"

Special Features

Metaprogramming

// Dynamic method addition
String.metaClass.isPalindrome = {
    delegate == delegate.reverse()
}

println "'racecar'.isPalindrome(): ${'racecar'.isPalindrome()}"

// Dynamic property addition
Integer.metaClass.getSquare = {
    delegate * delegate
}

println "5 squared: ${5.square}"

// MethodMissing (handling non-existent methods)
class DynamicClass {
    def methodMissing(String name, args) {
        "Called method: $name, args: $args"
    }
    
    def propertyMissing(String name) {
        "Non-existent property: $name"
    }
}

def dynamic = new DynamicClass()
println dynamic.someMethod("arg1", "arg2")
println dynamic.someProperty

// Categories (temporary metaclass changes)
class StringCategory {
    static String rot13(String self) {
        self.tr('A-Za-z', 'N-ZA-Mn-za-m')
    }
}

use(StringCategory) {
    println "'Hello'.rot13(): ${'Hello'.rot13()}"
}

// Expando (dynamic object)
def expando = new Expando()
expando.name = "Dynamic Object"
expando.greet = { "Hello, ${name}!" }
expando.age = 25

println expando.greet()

// AST Transformations (@Delegate example)
class CarEngine {
    def start() { "Engine started" }
    def stop() { "Engine stopped" }
}

class Car {
    @Delegate CarEngine engine = new CarEngine()
    String model
    
    Car(String model) {
        this.model = model
    }
}

def car = new Car("Prius")
println car.start()  // CarEngine methods are delegated

DSL (Domain Specific Language) Construction

// Builder pattern
class HtmlBuilder {
    def level = 0
    
    def methodMissing(String name, args) {
        def indent = "  " * level
        if (args.length == 1 && args[0] instanceof Closure) {
            println "${indent}<$name>"
            level++
            args[0]()
            level--
            println "${indent}</$name>"
        } else {
            println "${indent}<$name>${args[0]}</$name>"
        }
    }
}

def html = new HtmlBuilder()

html.html {
    head {
        title "Groovy DSL Example"
    }
    body {
        h1 "Welcome to Groovy"
        p "This is a DSL example"
        div {
            span "Nested content"
        }
    }
}

// Custom DSL example
class DatabaseDSL {
    def queries = []
    
    def select(String... columns) {
        queries << "SELECT ${columns.join(', ')}"
        this
    }
    
    def from(String table) {
        queries << "FROM $table"
        this
    }
    
    def where(String condition) {
        queries << "WHERE $condition"
        this
    }
    
    def build() {
        queries.join(' ')
    }
}

def query = new DatabaseDSL()
    .select("name", "age")
    .from("users")
    .where("age > 18")
    .build()

println "Generated SQL: $query"

// Configuration DSL
class ConfigDSL {
    def config = [:]
    
    def database(Closure closure) {
        def dbConfig = [:]
        closure.delegate = dbConfig
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure()
        config.database = dbConfig
    }
    
    def server(Closure closure) {
        def serverConfig = [:]
        closure.delegate = serverConfig
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure()
        config.server = serverConfig
    }
    
    def propertyMissing(String name, value) {
        this[name] = value
    }
}

def configDsl = new ConfigDSL()
configDsl.with {
    database {
        host = "localhost"
        port = 5432
        name = "myapp"
    }
    
    server {
        host = "0.0.0.0"
        port = 8080
        ssl = true
    }
}

println "Configuration: ${configDsl.config}"

Grape (Dependency Management) and Scripts

// Grape automatic dependency resolution
@Grab('org.apache.commons:commons-csv:1.9.0')
import org.apache.commons.csv.*

// Advanced CSV processing
def csvData = """Name,Age,City
John,25,Tokyo
Jane,30,Osaka
Bob,35,Kyoto"""

def format = CSVFormat.DEFAULT.withFirstRecordAsHeader()
def parser = CSVParser.parse(csvData, format)

parser.forEach { record ->
    println "${record.get('Name')} (${record.get('Age')}) from ${record.get('City')}"
}

// REST client example
@Grab('org.apache.groovy:groovy-json:4.0.0')
import groovy.json.JsonSlurper

// Simple HTTP client
def url = "https://jsonplaceholder.typicode.com/posts/1"
def connection = new URL(url).openConnection()
def response = connection.inputStream.text
def json = new JsonSlurper().parseText(response)

println "Title: ${json.title}"
println "Body: ${json.body}"

// One-liner script examples
// groovy -e "println 'Hello from command line'"
// groovy -e "(1..10).each { println it * it }"
// groovy -e "new File('.').eachFile { println it.name }"

Versions

Version Status Key Features Release Year
Groovy 4.0 Latest Java 17 support, performance improvements 2022
Groovy 3.0 Current Parrot parser, Java 14 support 2020
Groovy 2.5 Maintained Macro support, Android compatibility improvements 2018
Groovy 2.4 Legacy Traits introduction, annotation improvements 2014

Reference Pages

Official Documentation

Learning Resources

Frameworks and Tools