1+1 is just a convenient way of writing 1.plus(1). This is achieved by class
Integer having an implementation of the plus method. This convenient feature is also available for other operators.
You can easily use any of these operators with your own classes. Just implement the respective method. Unlike in Java, there is no need to implement a specific interface.
Method-based operators
Operator | Name | Method | Works with |
a + b | Plus | a.plus(b) | Number, string, collection |
a-b | Minus | a.minus(b) | Number, string, collection |
a*b | Star | a.multiply(b) | Number, string, collection |
a/b | Divide | a.div(b) | Number |
a%b | Module | a.mod(b) | Integral number |
a++ | Post increment | a.next() | Number, string, range |
++a | Pre increment | ||
a– | Post decrement | a.previous() | Number, string, range |
–a | Pre decrement | ||
a**b | Power | a.power(b) | Number |
a | b | Numerical or | a.or(b) | Integral number |
a & b | Numerical and | a.and(b) | Integral number |
a ^ b | Numerical xor | a.xor(b) | Integral number |
~ a | Bitwise compliment | a.negate() | Integral number, string (the
latter returning a regular expression pattern)
|
a [b] | Subscript | a.getAt(b) | Object, list, map,
String , Array
|
a [b]=c | Subscript assignment | a.putAt(b, c) | Object, list, map,
StringBuffer , Array
|
a << b | Left shift | a.leftShift(b) | Integral number, also used like
“append” to StringBuffers, Writers, Files, Sockets, Lists |
a >>b | Right shift | a.rightShift(b) | Integral number |
a >>> b | Right shift unsigned | a.rightShiftUnsigned(b) | Integral number |
switch(a){
case b: }
|
Classification | b.isCase(a) | Object, range, list,
collection, pattern, closure; also used with collection c in c.grep(b), which returns all items of c where b.isCase(item)
|
a==b | Equals | a.equals(b) | Object; consider
hashCode() a
|
a !=b | Not Equal | ! a.equals(b) | Object |
a <=> b | Spaceship | a.compareTo(b) | java.lang.Comparable |
a > b | Greater than | a.compareTo(b) > 0 | |
a >= b | Greater than or equal to | a.compareTo(b) >= 0 | |
a < b | Less than | a.compareTo(b) < 0 | |
a <= b | Less than or equal to | a.compareTo(b) <= 0 | |
a as type | Enforced correction | a.asType(typeClass) | Any type |
This is all good in theory, but let’s see how they work in practice.
Overridden operators in action:
We implement equals such that it copes with null comparison. This is Groovy style. The default implementation of the equals operator doesn’t throw any NullPointerExceptions either. Remember that == (or equals) denotes object equality (equal values), not identity (same object instances).
Operator override
Bellow code demonstrates an implementation of the equals == and plus + operators for a Money class. We allow money of the same form of currency to be added up but do not support multicurrency addition.
We implement equals such that it copes with null comparison. This is Groovy style. The default implementation of the equals operator doesn’t throw any NullPointerExceptions either. Remember that == (or equals) denotes object equality (equal values), not identity (same object instances).
Code:
class Money {
private int amount
private String currency
Money (amountValue, currencyValue) {
amount = amountValue
currency = currencyValue
}
boolean equals (Object other) { (override == operator)
if (null == other) return false
if (! (other instanceof Money)) return false
if (currency != other.currency) return false
if (amount != other.amount) return false
return true
}
int hashCode() {
amount.hashCode() + currency.hashCode()
}
Money plus (Money other) { (implement + operator)
if (null == other) return null
if (other.currency != currency) {
throw new IllegalArgumentException(
"cannot add $other.currency to $currency")
}
return new Money(amount + other.amount, currency)
}
}
def buck = new Money(1, 'USD')
assert buck
assert buck == new Money(1, 'USD') (use overhidden ==)
assert buck + buck == new Money(2, 'USD') (use overhidden +)
Overriding equals is straightforward, as we show at override == operator. We also provide a hashCode method to make sure equal Money objects have the same hashcode. This is required by Java’s contract for java.lang.Object. The use of this operator is shown at use overhidden== where one dollar becomes equal to any other dollar.
At Implement + operator, the plus operator is not overridden in the strict sense of the word, because there is no such operator in Money’s superclass (Object). In this case, operator implementing is the best wording. This is used at Use overhidden+, where we add two Money objects.
To explain the difference between overriding and overloading, here is a possible overload for Money’s plus operator. In case, we would like to add Money as
assert buck + 1 == new Money(2, 'USD')
We can provide the additional method
Money plus (Integer more) {
return new Money(amount + more, currency)
}
that overloads the plus method with a second implementation that takes an Integer parameter. The Groovy method dispatch finds the right implementation at runtime.
This example leads to the general issue of how to deal with different parameter types when implementing an operator method.
Making coercion work for you:
Implementing operators is straightforward when both operands are of the same
type. Things get more complex with a mixture of types, say
1 + 1.0
This adds an Integer and a BigDecimal. One of the two arguments needs to be promoted to the more general type. This is called coercion.
When implementing operators, there are three main issues to consider as part of coercion.
Supported argument types
You need to decide which argument types and values will be allowed. If an operator must take a potentially inappropriate type, throw an IllegalArgumentException where necessary. For instance, in our Money example, even though it makes sense to use Money as the parameter for the plus operator, we don’t allow different currencies to be added together.
Promoting more specific arguments
If the argument type is a more specific one than your own type, promote it to your type and return an object of your type. To see what this means, consider how you might implement the plus operator if you were designing the BigDecimal class, and what you’d do for an Integer argument. Integer is more specific than BigDecimal: Every Integer value can be expressed as a BigDecimal, but the reverse isn’t true. So for the BigDecimal. plus(Integer)operator, we would consider promoting the Integer to BigDecimal,performing the addition, and then returning another BigDecimal even if the result could accurately be expressed as an Integer.
Handling more general arguments with double dispatch
f the argument type is more general, call its operator method with yourself (“this,” the current object) as an argument. This is also called double dispatch,and it helps to avoid duplicated, asymmetric, possibly inconsistent code. Let’s reverse our previous example and consider Integer.plus (BigDecimal operand). We would consider returning the result of the expression operand.plus(this), delegating the work to BigDecimal’s plus(Integer) method. The result would be a BigDecimal, which is reasonable—it would be odd for 1+1.5 to return an Integer but 1.5+1 to return a BigDecimal. Of course, this is only applicable for commutative operators.
Groovy’s conventional behavior
Groovy’s general strategy of coercion is to return the most general type. Other languages such as Ruby try to be smarter and return the least general type that can be used without losing information from range or precision. The Ruby way saves memory at the expense of processing time. It also requires that the language promote a type to a more general one when the operation would generate an overflow of that type’s range. Otherwise, intermediary results in a complex calculation could truncate the result.