DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • GitOps Secrets Management: The Vault + External Secrets Operator Pattern (With Auto-Rotation)
  • GitOps-Backed Agentic Operator for Kubernetes: Safe Auto-Remediation With LLMs and Policy Guardrails
  • Agentic AI: The Next Evolution of Artificial Intelligence and Autonomous Automation
  • Exploring C++23: Multidimensional Subscript Operator

Trending

  • How to Format Articles for DZone
  • Operationalizing Enterprise AI at Scale: Architecture, Governance, and Adoption
  • Architecting Proactive IT: NinjaOne Remote Monitoring and Management
  • Give Your AI Assistant Long-Term Memory With perag
  1. DZone
  2. Coding
  3. Languages
  4. The Twisted Sisters of Overloadable Groovy Operators

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.

By 
Joe Wolf user avatar
Joe Wolf
·
Jan. 17, 17 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
9.5K Views

Join the DZone community and get the full member experience.

Join For Free

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
i++
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.

@groovy.transform.TupleConstructor
class Bar {
    int value
    Bar next() {
        ++value
        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)
b++
assert b.value == 2
b.next()
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)
++b++
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.

Summary

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.

Operator (extension) Groovy (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • GitOps Secrets Management: The Vault + External Secrets Operator Pattern (With Auto-Rotation)
  • GitOps-Backed Agentic Operator for Kubernetes: Safe Auto-Remediation With LLMs and Policy Guardrails
  • Agentic AI: The Next Evolution of Artificial Intelligence and Autonomous Automation
  • Exploring C++23: Multidimensional Subscript Operator

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook