Table of contents
- Foreword;
- Operator-like methods;
- Groovy Equals;
- Multimethods;
- Overloaded operators;
- Optional parameters;
- Vararg methods;
- Named parameters;
- Constructors with named parameters;
- Constructors with positional parameters;
Foreword
This is a regular article produced on Groovy exploration way. We'll talk about Groovy method calls processing here.
Previous part may be found here - Groovy features, part 2 - java beans
Operator-like methods
Groovy offers convenient call syntax for predefined set of methods. It looks like operator appliance at source code level but is compiled to a method call. Example:
Integer i = 1;
Integer j = 2;
println i.class
println j.class
println i + j
Output:
class java.lang.Integer
class java.lang.Integer
3
We can see that it looks like '+' operator is applied to two objects of type 'java.lang.Integer', howver, it's transformed to call 'i.plus(j)'. I.e. Groovy automatically converts '+' defined at source level to invocation of 'plus()' method at runtime.
That means that it's possible to declare such a method at custom class and use operator-like syntax for it:
class FirstNumberWrapper {
Number i
def plus(j) {
println "FirstNumberWrapper.plus($j) is called"
new FirstNumberWrapper(i: i + j)
}
String toString() {"'wrapper for $i'"}
}
class SecondNumberWrapper {
Number i
String toString() {"'wrapper for $i'"}
}
first = new FirstNumberWrapper(i: 1)
second = new SecondNumberWrapper(i: 2)
println "Adding 3 to first wrapper ($first)..."
first += 3
println "done. Result: $first"
println "Adding 3 to second wrapper ($second)..."
second += 3
Output:
Adding 3 to first wrapper ('wrapper for 1')...
FirstNumberWrapper.plus(3) is called
done. Result: 'wrapper for 4'
Adding 3 to second wrapper ('wrapper for 2')...
Caught: groovy.lang.MissingMethodException: No signature of method: SecondNumberWrapper.plus() is applicable for argument types: (java.lang.Integer) values: [3]
at GroovyTestScript.run(GroovyTestScript.groovy:26)
Here is a complete list of method name - operator sign mappings:
| Operator | Method |
|---|---|
| a + b | a.plus(b) |
| a - b | a.minus(b) |
| a * b | a.multiply(b) |
| a / b | a.div(b) |
| a % b | a.mod(b) |
| a++; ++a | a.next() |
| a--; --a | a.previous() |
| a ** b | a.power(b) |
| a | b | a.or(b) |
| a & b | a.and(b) |
| a ^ b | a.xor(b) |
| ~a | a.negate() |
| a[b] | a.getAt(b) |
| a[b] = c | a.putAt(b, c) |
| a << b | a.leftShift(b) |
| a >> b | a.rightShift(b) |
| a >>> b | a.rightShiftUnsigned(b) |
| switch (a) { case b: } | b.isCase(a) |
| a == b | a.equals(b) |
| a != b | !a.equals(b) |
| a <=> b | a.compareTo(b) |
| a > b | a.compareTo(b) > 0 |
| a >= b | a.compareTo(b) >= 0 |
| a < b | a.compareTo(b) < 0 |
| a <= b | a.compareTo(b) <= 0 |
| a as type | a.asType(typeClass) |
Groovy Equals
I want to pay special attention to ' == ' operator processing - it is automatically mapped to 'equals()' call. You should use is() method to compare objects by identity:
s1 = "xxx"
s2 = new String("xxx")
s3 = s2
println s1 == s2 // true
println s1.is(s2) // false
println s2 == s3 // true
println s2.is(s3) // true
Java selects target overloaded method at compile-time, hence, it looks at static reference type. Consider the following example:
class GeneralItem {}
class SubItem extends GeneralItem {}
class BaseService {
public void test(Object data) {
System.out.println("BaseService.test()");
}
}
class SubService extends BaseService {
public void test(String data) {
System.out.println("SubService.test()");
}
}
public class JavaTest {
public static void main(String[] args) {
BaseService subService = new SubService();
subService.test(""); // prints 'BaseService.test()'
}
}
Here actual object type is 'SubService' and argument type is 'String'. However, BaseService.test() is called because static type of 'SubService' reference is 'BaseService', hence, algorithm that resolves overloaded method to use selects 'BaseService.test()'.
We can perform the same test in Groovy and see that 'SubService.test()' is printed. The reason is that Groovy resolves all method calls in runtime, hence, it checks both actual argument types and target reference type. This is really cool!
Multimethods allow to do the following nice trick - it's possible to define overloaded versions of base operator methods. Necessary version is automatically chose in runtime:
class NumberWrapper {
def number;
def multiply(Number number) {
println 'muliply(Number)'
new NumberWrapper(number: this.number * number)
}
def multiply(NumberWrapper wrapper) {
println 'muliply(NumberWrapper)'
new NumberWrapper(number: number * wrapper.number)
}
def String toString() {
"wrapper for $number"
}
}
def wrapper1 = new NumberWrapper(number: 1)
def wrapper2 = new NumberWrapper(number: 2)
println(wrapper1 * wrapper2)
println(wrapper1 * 3)
Output:
muliply(NumberWrapper)
wrapper for 2
muliply(Number)
wrapper for 3
Optional parameters
It's possible to define default parameters values for Groovy method arguments. Any number of such parameters can be introduced. The only restriction is that they should be trailing within the method definition:
def sum(first, second, third = 3, forth = 4) {
println "sum($first, $second, $third, $forth)"
}
sum(10, 20)
sum(10, 20, 30)
sum(10, 20, 30, 40)
Output:
sum(10, 20, 3, 4)
sum(10, 20, 30, 4)
sum(10, 20, 30, 40)
Vararg methods
Groovy supports java syntax sugar for var-arg parameter delivered as array at runtime. Moreover, any trailing array parameter is treated as vararg parameter with 0..* cardinality:
def test1(first, ... trailing) {
println "test1($first, $trailing)"
}
def test2(first, Object[] trailing) {
println "test2($first, $trailing)"
}
test1(1, 2, 3, 4)
test1(1)
test2(10, 20, 30, 40)
test2(10)
Output:
test1(1, [2, 3, 4])
test1(1, [])
test2(10, [20, 30, 40])
test2(10, [])
Named parameters
All method calls above used so-called 'positional parameters', i.e. parameter meaning is defined by its position at method call expression. However, it's possible to use rather convenient alternative to that - 'named parameters'. Method caller supplies every parameter with it's name and it's position doesn't matter at all. Moreover, it's even possible to provide support for optional parameters within method implementation:
def test(Map args) {
args.get('quantity', 1) // Provide default value for 'quantity' argument
println "Total price: ${args.quantity * args.price}"
}
test(quantity: 3, price: 2)
test(price: 4, quantity: 2)
test(price: 10)
Output:
Total price: 6
Total price: 8
Total price: 10
Constructors with named parameters
Groovy allows to create instances of classes with 'named parameters syntax' for their properties. The only restriction for such usage is that class which object is constructed should have no-args constructor:
class TestClass {
String property1, property2
def String toString() {
return "property1: $property1; property2: $property2";
}
}
println new TestClass(property1: 'xxx')
println new TestClass(property2: 'zzz', property1: 'xxx')
Output:
property1: xxx; property2: null
property1: xxx; property2: zzz
Constructors with positional parameters
There is convenient syntax for calling constructors with positional parameters as well.
The main idea is that every time Groovy sees the need to convert list to some other type it tries to call appropriate type's constructor with all list items as arguments (at same order). Such a conversion may be performed either explicitly (via 'as' operator) or implicitly:
class TestClass {
String property1, property2
def TestClass(property1, property2) {
this.property1 = property1
this.property2 = property2
}
def String toString() {
return "property1: $property1; property2: $property2";
}
}
def obj1 = [1, 2] as TestClass
TestClass obj2 = ['xxx', new Date()] // Note that we use explicit variable typing here
println obj1
println obj2
Output:
property1: 1; property2: 2
property1: xxx; property2: Mon May 10 23:14:43 MSD 2010

Very nice summary, thanks!
ReplyDeleteThanks :)
ReplyDelete