Skip to content
AgileLogics
5 min read

Enums and Records

Prerequisites: Classes and objects

Enums

An enum is a type with a fixed set of values:

enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    EARTH(5.976e+24, 6.37814e6),
    JUPITER(1.9e+27, 7.1492e7)

    final double mass, radius

    Planet(double mass, double radius) {
        this.mass = mass
        this.radius = radius
    }

    double surfaceGravity() { 6.67300E-11 * mass / (radius * radius) }
}

Planet.values().each {
    printf('%-8s %6.2f m/s²%n', it, it.surfaceGravity())
}
println Planet.valueOf('EARTH').ordinal()
println Planet.EARTH.next()
println (Planet.MERCURY..Planet.JUPITER)
MERCURY    3.70 m/s²
EARTH      9.80 m/s²
JUPITER   24.81 m/s²
1
JUPITER
MERCURY..JUPITER

Enums can have fields, constructors, and methods. Groovy adds next() and previous(), so enum values also work in ranges.

Enums in switch and maps

enum Level { DEBUG, INFO, WARN, ERROR }

def color(Level level) {
    switch (level) {
        case Level.DEBUG -> 'grey'
        case [Level.INFO, Level.WARN] -> 'yellow'
        case Level.ERROR -> 'red'
    }
}

println color(Level.WARN)
println Level.values().findAll { it >= Level.WARN }
def counts = new EnumMap<Level, Integer>(Level)
counts[Level.ERROR] = 2
counts[Level.INFO] = 10
println counts
Level lvl = 'DEBUG'            // Groovy converts a String to an enum
println lvl.class.simpleName
yellow
[WARN, ERROR]
[INFO:10, ERROR:2]
Level

Records

Groovy 4 added records — compact, immutable data classes, like Java's:

import groovy.transform.RecordOptions

@RecordOptions(copyWith = true)
record Point(int x, int y) {
    Point {
        if (x < 0 || y < 0) throw new IllegalArgumentException('negative coordinate')
    }

    double distanceTo(Point o) { Math.hypot(x - o.x, y - o.y) }
}

def a = new Point(0, 0)
def b = new Point(3, 4)
println b
println b.x() + ' ' + b.y
println a.distanceTo(b)
println b == new Point(3, 4)
def (x, y) = b              // records support destructuring
println "x=$x y=$y"
println b.copyWith(y: 10)
Point[x=3, y=4]
3 4
5.0
true
x=3 y=4
Point[x=3, y=10]
  • A record's fields are final; accessors are x() (and Groovy also allows .x).
  • The compact constructor (Point { ... }) validates arguments.
  • Groovy adds destructuring, named-argument construction (new Point(x: 1, y: 2)), and — when enabled with @RecordOptions(copyWith = true) — copyWith for changed copies.

Sealed types

sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}

double area(Shape s) {
    switch (s) {
        case Circle -> Math.PI * s.r() ** 2
        case Square -> s.side() ** 2
    }
}

println area(new Square(3))
printf('%.3f%n', area(new Circle(1)))
9.0
3.142

sealed ... permits limits which classes may implement a type, which makes "one of these cases" data models explicit.