Over a million developers have joined DZone.

The Twisted Sisters of Overloadable Groovy Operators

DZone's Guide to

The Twisted Sisters of Overloadable Groovy Operators

Groovy operator overloading is fairly straightforward, but there are some oddities with the ++ and -- operators to be aware of. Also, never return "this". Ever.

· Java Zone ·
Free Resource

Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.

Groovy supports operator overloading for a limited set of operators. Each supported operator corresponds to a particular method signature. If a type implements that method, then that operator is effectively overloaded for instances of that type.

The + operator, for example, corresponds to the plus method, enabling a.plus(b) to be substituted with a + b. And thanks to the wonders of polymorphism, a type can define multiple plus methods to allow + to behave differently depending on the type on the right hand side of the operator (e.g. [1] + 2 inserts 2 at the end of the list while [1] + [2] joins the two lists together, resulting in [1, 2] for both).

This approach to overloading seemed fairly straightforward until I recently discovered two sister operators that had some interesting twists: ++ and --, which correspond to the next() and previous() methods, respectively.

Twist 1: Implicit Reassignment

The first twist is that when the ++ operator is used on a reference, it also reassigns that reference to the value returned by next(). (The same applies to -- and previous().) This means that, unlike the other overridable Groovy operators, you cannot interchange a standalone statement of a++ with a.next(), as shown by this code.

int i = 0
assert i == 1

int j = 0
j.next() // 1 is returned by next(), but not assigned to j, keeping its value unchanged
assert j == 0 

Twist 2: In Tandem Pre and Post Forms

Like Java, the placement of ++ or -- before or after a reference determines if the reassignment occurs before or after the operation is executed.

int i = 1
int j = ++i // i is incremented, then its value is assigned to j
int k = i++ // k is assigned the current value of i, then i is incremented

assert [i, j, k] == [3, 2, 2]

This holds true for other data types overriding ++ and --, like java.util.Date in the Groovy JDK.

def date = { int dayOfMonth -> new Date(2017 - 1900, Calendar.MAY, dayOfMonth).clearTime() }
Date d = date(1)
Date e = ++d
Date f = d++

assert [d, e, f].date == [3, 2, 2]

Unlike Java, however, Groovy lets you use the operator on both sides of the reference simultaneously (++a++); the corresponding method is called twice, but not in chained succession. Confused? Perhaps the behavior best explained by this code.

class Foo {
    int value
    def next() {
        println "next() called when value is $value"
        new Foo(value + 1)

def f = new Foo(1)
++f++ // our wacky tandem usage of ++
println "The final value is $f.value"

When executed, it prints:

next() called when value is 1
next() called when value is 1
The final value is 2

(I'm using Groovy 2.4.7 on Java 1.8.0_111)

Twist 3: A Twist to the Twists

But before we file these caveats under the "good to know" category and move on with our lives, it's worth noting that we can override next() and previous() in such a way that it invalidates the aforementioned behaviors. Specifically, we can return this from our method implementations.

class Bar {
    int value
    Bar next() {
        this // return this instance instead of a new Bar

As a consequence of returning a self-reference from our method, we cause the statements next() and ++ to be perfectly interchangeable.

def b = new Bar(1)
assert b.value == 2
assert b.value == 3

Furthermore, we can use the in-tandem operators and get the expected result (insomuch as you can expect in-tandem operators to effect a result at all).

def b = new Bar(1)
assert b.value == 3

Is This the Answer?

So are we to conclude that it's better to return this than a new instance from our next() and previous() methods? Certainly not!

For starters, assuming that ++ or -- is supposed to change something about the object it's called on, returning this must mean that the object's type is mutable. Who wants to write mutable data types these days?

Secondly, if we return this from our method, we break the ++a vs. a++ reassignment behavior, making the placement of the operator moot. Consider how our Bar type compares to int and java.util.Date when applying ++ in its pre and post forms.

def b = new Bar(1)
def c = ++b
def d = b++

assert [b, c ,d].value == [3, 3, 3] // not [3, 2, 2] as with int and java.util.Date

All three Bar references have the same value because they're all pointing to the same instance of Bar.


Groovy operator overloading is fairly straightforward, but there are some oddities with the ++ and -- operators to be aware of. Given that 99% of all custom data types outside of numbers and iterators will have no reasonable semantics for the next() and previous() methods, and given that there is a 0% need to ever write a statement like ++a++, you will likely never encounter these oddities. The important takeaway is that, on the rare occassions when you do decide to implement next() and previous() in order to override ++ and --, never return this.

Connect any Java based application to your SaaS data.  Over 100+ Java-based data source connectors.

groovy ,operator overloading ,mutability ,java ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}