Skip to content
AgileLogics
7 min read

Classes and Objects

Prerequisites: Methods

Groovy classes compile to ordinary Java classes, but need far less code.

class Person {
    String name
    int age

    String greet() {
        "Hi, I'm $name and I'm $age."
    }

    boolean isAdult() { age >= 18 }
}

def ada = new Person(name: 'Ada', age: 36)
println ada.greet()
println ada.name
ada.age = 37
println ada.getAge()
println ada.adult
Hi, I'm Ada and I'm 36.
Ada
37
true

What Groovy did for you:

  • String name without a modifier is a property: a private field plus a public getName() and setName(). ada.name calls the getter; ada.age = 37 calls the setter.
  • Classes and methods are public by default.
  • A named-argument constructor (new Person(name: ..., age: ...)) is available whenever the class has a no-argument constructor.
  • isAdult() can be read as the property adult.

Constructors and custom accessors

class Temperature {
    final BigDecimal celsius

    Temperature(BigDecimal celsius) {
        if (celsius < -273.15) throw new IllegalArgumentException('below absolute zero')
        this.celsius = celsius
    }

    BigDecimal getFahrenheit() { celsius * 9 / 5 + 32 }

    String toString() { "${celsius}°C" }
}

def t = new Temperature(21.5)
println t
println t.fahrenheit
try {
    new Temperature(-300)
} catch (IllegalArgumentException e) {
    println "Error: $e.message"
}
21.5°C
70.7
Error: below absolute zero
  • A final property gets a getter but no setter.
  • A getX() method creates a read-only computed property x.
  • Overriding toString() controls how the object prints.

Static members and fields

class Counter {
    static int created = 0
    private int value = 0        // an explicit modifier makes a plain field

    Counter() { created++ }

    Counter increment() { value++; this }
    int getValue() { value }
}

def c = new Counter().increment().increment()
new Counter()
println c.value
println Counter.created
2
2

Returning this makes methods chainable. Adding private, protected, or public to a field turns off property generation.

Equality and hashing

class Point {
    int x, y
    boolean equals(Object o) { o instanceof Point && o.x == x && o.y == y }
    int hashCode() { Objects.hash(x, y) }
    String toString() { "($x, $y)" }
}

def p = new Point(x: 1, y: 2)
def q = new Point(x: 1, y: 2)
println p == q
println p.is(q)
println([p, q].toSet())
true
false
[(1, 2)]

Writing equals, hashCode, and toString by hand is tedious — the AST transformations lesson shows how one annotation generates all three.

Practice

Write a BankAccount class with an owner, a read-only balance, and deposit and withdraw methods that reject invalid amounts.

Show solution
class BankAccount {
    final String owner
    private BigDecimal balance = 0

    BankAccount(String owner) { this.owner = owner }

    BigDecimal getBalance() { balance }

    void deposit(BigDecimal amount) {
        if (amount <= 0) throw new IllegalArgumentException('amount must be positive')
        balance += amount
    }

    void withdraw(BigDecimal amount) {
        if (amount > balance) throw new IllegalStateException('insufficient funds')
        balance -= amount
    }
}

def acct = new BankAccount('Ada')
acct.deposit(100)
acct.withdraw(30.25)
println "$acct.owner has $acct.balance"
try {
    acct.withdraw(500)
} catch (IllegalStateException e) {
    println e.message
}
Ada has 69.75
insufficient funds