This final note in the introductory Groovy series looks at language features that help keep small pieces of JVM code direct and readable. Earlier entries introduced the foundations; this one concentrates on flexible method calls, closures, and operators that commonly appear in Groovy codebases.

Variable arguments

Methods can accept an open-ended list of values. Older Groovy code often expressed that convention with a final array parameter, while newer syntax can use the Java-style ellipsis. Both examples below collect numbers and return their total.

Listing 2.1 · Variable arguments
class Calculator {
   def addAllGroovy(Object[] args) {
      int total = 0
      args.each { total += it }
      total
   }

   def addAllJava(int... args) {
      int total = 0
      args.each { total += it }
      total
   }
}

Calculator c = new Calculator()
assert c.addAllGroovy(1, 2, 3, 4, 5) == 15
assert c.addAllJava(1, 2, 3, 4, 5) == 15

There are more idiomatic ways to add a list in Groovy. The example is deliberately plain so that the parameter behavior remains visible.

Named parameters

A Map parameter can support a call style that reads like named arguments. Each key identifies a value within the method. This is particularly useful when a method takes several values with distinct roles.

Listing 3.1 · Map-based parameters
class Math {
   static getSlope(Map args) {
      def b = args.b ? args.b : 0
      (args.y - b) / args.x
   }
}

assert 1 == Math.getSlope(x: 2, y: 2)
assert 0.5 == Math.getSlope(x: 2, y: 2, b: 1)

For the line formula y = mx + b, the method calculates m from a point and an optional offset. The call communicates what each supplied value means. The trade-off is that a map does not state every permitted key in the conventional method signature, so clear documentation remains important.

Currying closures

Currying creates a new closure by fixing one or more values from an existing closure. The returned closure needs fewer values at call time, which can be useful when part of a calculation is stable across a set of operations.

Listing 4.1 · Curried closure
def getSlope = { x, y, b = 0 ->
   (y - b) / x
}

assert 1 == getSlope(2, 2)
def getSlopeX = getSlope.curry(5)
assert 1 == getSlopeX(5)
assert 0 == getSlopeX(2.5, 2.5)

Here, getSlopeX always begins with x set to 5. Its remaining arguments are assigned to the values that follow in the original closure definition.

resource note. Groovy’s compact syntax is most useful when it clarifies a domain action. In shared code, the shortest form is not always the clearest form; descriptive names and focused closures still do most of the work.

Revisiting operators

Groovy supports operator methods and adds a few forms that are less familiar to Java-only readers. The comparison operator <=>, sometimes called the spaceship operator, returns ordering information and works naturally with Comparable values.

Listing 5.1 · Comparison for sorting
assert [1, 2, 3, 4, 5, 6] ==
   [2, 4, 6, 1, 5, 3].sort { a, b -> a <=> b }

The spread forms are also useful in concise code. One expands a collection into a method call; another applies a method to every element in a collection.

Listing 5.2 · Spread forms
def calculate = { a, b, c -> "$a,$b,$c" }
assert "1,2,3" == calculate(*[1, 2, 3])

assert ["01", "11", "21"] ==
   ["10", "11", "12"]*.reverse()

The Elvis operator, ?:, supplies a fallback when the expression to its left is not truthy. It is a compact alternative to a simple conditional expression.

Listing 5.3 · Elvis fallback
assert "hello" == "hello" ?: "bye"
assert "bye" == null ?: "bye"

Groovy can also obtain a method reference as a closure. That closure can then be called, passed onward, or curried like any other closure.

Listing 5.4 · Method reference
class Math {
   static getSlope(x, y, b = 0) {
      (y - b) / x
   }
}

def getSlope = Math.&getSlope
assert getSlope instanceof Closure
assert 1 == getSlope(2, 2)

def getSlopeX = getSlope.curry(5)
assert 1 == getSlopeX(5)

Finally, the as keyword provides Groovy’s coercion syntax. A map can become a simple object shape, and a closure can fulfill a listener interface when its single method matches the closure’s intended role.

Listing 5.5 · Type coercion
class Person {
   String name
   int id
}

def person = [name: 'Duke', id: 1] as Person
assert person.class == Person
assert person.name == 'Duke'

Conclusion

These features show why Groovy often feels expressive beside Java: it keeps access to the JVM while offering terse ways to build calls, adapt values, and compose behavior. Builders and the Meta Object Protocol extend that story further, but these fundamentals provide a solid foundation for reading and maintaining Groovy code.

Discuss this article in the resource →