Making Java Groovy: Operator Overloading

This is partly a spoiler for my “Making Java Groovy” talk that I normally do at NFJS events and plan to use in my similar talk at JavaOne, but to celebrate the book going to the printer I thought I’d try to contribute something technical here.

I’ve always felt that one of the keys to understanding Groovy was operator overloading. Operator overloading has a mixed reputation, conjuring up horror stories of developers changing + to be *, which might be good for job security but not good in general.

(Ultimately it’s not be good for job security, either.)

To be honest, I don’t do a lot of operator overloading in my own code, but I take advantage of it all the time. Most Groovy developers don’t realize how thoroughly operator overloading occurs throughout the Groovy JDK, among other places.

This web page lists all the operators in Groovy and their corresponding methods. What’s easy to overlook, however, is that every operator in Groovy actually invokes a method. That table doesn’t just provide you with options — it’s what’s actually happening under the hood. Note that it’s not just operators like + or * that call methods. Array-like access (the [] operator) calls getAt and putAt on maps, and even == invokes the equals method.

(Actually, == invokes the compareTo method if the class happens to implement java.util.Comparable, but according to the Comparable contract that ought to be equivalent.)

The code simplifications that result are dramatic. For example, consider the plus method in java.util.Date, which takes an int representing the number of days:

Date now = new Date()
Date then = now + 3
assert 4 == (now..then).size()

It’s also why you can not only concatenate with strings using +, you can also subtract substrings from them using the minus operator:

String s = 'silly string'
assert 'more silly string' == 'more ' + s
assert 'string' == s - 'silly '

Note that in each case, the operator is returning a new string rather than modifying the original. Strings are still immutable, even in Groovy.

Let me give a larger example that uses Groovy / Java integration to make coding easier. If you’ve ever done financial calculations in Java (or if you’ve ever seen Office Space), you know you need to use java.math.BigDecimal for the amounts rather than just doubles. But BigDecimal in Java is an annoying class to work with, because (1) you have to instantiate it, (2) you have to remember to use the constructor that takes a String, or the results are not predictable (see the JavaDocs for details), and (3) you lose all your operators and have to use all the associated method calls, keeping in mind that (4) all instances of the class are immutable. That makes for a rather verbose class when all you want is to wrap a numerical value.

You can do all that, or you can take advantage of the fact that the native data type for floating point values in Groovy is already BigDecimal. Not only that, but the Groovy JDK includes methods like plus, minus, and multiply in the java.lang.Number class (the superclass of BigDecimal), so you can use your normal operators. The result is a class like:

package com.mjg

import groovy.transform.Canonical

@Canonical
class Account {
    BigDecimal balance = 0.0
    
    BigDecimal deposit(BigDecimal amount) {
        balance += amount
    }
    
    BigDecimal withdraw(BigDecimal amount) {
        balance -= amount
    }
}

The only attribute is the balance, stored as a BigDecimal, initialized to zero. Since the attribute lacks a public or private modifier, Groovy automatically generates public getter and setter methods for it. The deposit and withdraw methods take advantage of operator overloading to update the balance using simple + and – operators. Finally, the Canonical AST transformation adds equals, hashCode, and toString methods to Account, as well as a “tuple” constructor that can be called from Java. That’s a lot of mileage for 16 lines of code.

Before I use it from Java, let me verify that it works so far:

package com.mjg

// Normal Groovy ctor that sets the property
Account acct = new Account(balance:250)
assert 250 == acct.balance
assert 300 == acct.deposit(50)
assert 200 == acct.withdraw(100)

// check the tuple ctor
acct = new Account(150)
assert 150 == acct.balance

// toString method
assert 'com.mjg.Account(150)' == acct.toString()

// equals and hashCode
Set accounts = [new Account(balance:250), new Account(250)]
assert accounts.size() == 1

Accounts can be created using the normal map-based constructor or the tuple constructor, which in this case has only a single argument (because the class has a single attribute). Accessing the balance property actually invokes the getBalance method, which is supplied by Groovy automatically. The generated toString method is as shown, and to prove that both equals and hashCode are working I added two identical accounts (instantiated by either constructor) into a Set and verified that Groovy added only one of them.

That’s all well and good, but if I can’t verify a Groovy class using Groovy tests, I might as well give it up. Instead, I want to access the Groovy class from Java. Here, therefore, is a highly contrived Bank class, in Java:

package com.mjg;

import java.math.BigDecimal;

public class Bank {
    public Account openAccount(double initialBalance) {
        Account acct = new Account();
        acct.deposit(new BigDecimal(initialBalance + ""));
        return acct;
    }

    public BigDecimal getBalance(Account acct) {
        return acct.getBalance();
    }
    
    public BigDecimal addFunds(Account acct, double amount) {
        return acct.deposit(new BigDecimal(amount + ""));
    }
    
    public BigDecimal removeFunds(Account acct, double amount) {
        return acct.withdraw(new BigDecimal(amount + ""));
    }
}

The Bank has a method to open an account using a double, which is wrapped in a BigDecimal using the String-based constructor. Then addFunds and removeFunds just delegate to the associated methods in Account.

To test this, I might as well switch back to Groovy again, but in a normal JUnit test:

package com.mjg

import static org.junit.Assert.*;

import org.junit.Test;

class BankTests {
    Bank bank = new Bank()
    Account acct = bank.openAccount(250);
    
    @Test
    void testOpenAccount() {
        assert acct
    }
    
    @Test
    void testGetBalance() {
        assert 250 == bank.getBalance(acct)
    }
    
    @Test
    void testAddFunds() {
        bank.addFunds(acct, 50)
        assert 300 == bank.getBalance(acct)
    }
    
    @Test
    void testRemoveFunds() {
        bank.removeFunds(acct, 100)
        assert 150 == bank.getBalance(acct)
    }
}

All the tests work without a problem, demonstrating how easy it is to switch from Groovy to Java and back again. If you ever have to add code to an existing Java system that involves BigDecimal, writing a class in Groovy is a very easy way to do it.

In my book, incidentally, the chapter on Spring and Groovy uses a similar Account class, but with an added id. Then I implement an AccountDAO interface (written in Java, of course) using the a Groovy class that takes advantage of Spring’s JdbcTemplate (again from Java), complete with Spock tests. If you want to see the source code now, just check out ch07 in the GitHub repository for the book.

As in this example, when I code in Groovy I don’t often do operator overloading, but the effects of it show up everywhere. Hopefully these simple examples will help you take advantage of it in the future.

BTW, I received notice from Manning that I will have actual hard copies of Making Java Groovy at the SpringOne2GX conference next week. Even if you don’t want one, please come by and say hello. Don’t be surprised if I can’t stop grinning the entire time.

About Ken Kousen
I teach software development training courses. I specialize in all areas of Java and XML, from EJB3 to web services to open source projects like Spring, Hibernate, Groovy, and Grails. Find me on Google+ I am the author of "Making Java Groovy", a Java / Groovy integration book published by Manning in the Fall of 2013, and "Gradle Recipes for Android", published by O'Reilly in 2015.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: