A Groovy script is a sequence of statements:
def a= 1 assert "a is $a" == 'a is 1' def b= 2; assert "b is $b" == 'b is 2' //if two statements on one line, separate by semicolons def c= 3; ; def d= 4 //empty statement in between
When defining classes, we can provide 'asType' methods to convert the class into another using the 'as' operator. Classes we've seen in previous tutorials that convert to another using 'as' (eg, Integer, BigDecimal, String) use the 'asType' method under the hood:
class A{
def x
Object asType(Class c){
if(c == B) return new B(x:x*3)
}
}
class B{
def x
}
def a= new A(x:3)
def b1= a.asType(B)
assert b1.class == B && b1.x == 9
def b2= a as B //more common, shortcut syntax for asType()
assert b2.class == B && b2.x == 9
We can use 'as' to convert a list into a class instance using the list elements as constructor arguments:
class A{
int x,y
A(x,y){ this.x=x; this.y=y }
String toString(){ "x: $x; y: $y" }
}
def a= [1,2] as A
assert a.class == A && a.toString() == 'x: 1; y: 2'
Conditional Statements
The if and if-else statements let us choose subsequent statements to execute based on a condition:
def x= 7 if( x > 4 ){ println 'x is greater than 4' } //if-statement (no 'else' clause) if( x > 4 ) println 'x is greater than 4' //curlies optional if only one statement //if-else statement... if( x > 4 ){ println 'x is greater than 4' }else{ println 'x is less than or equal to 4' } if( x > 8 ) println 'x is greater than 8' //again, curlies optional if only one statement else println 'x is less than or equal to 8' //an 'else' clause always belongs to def result if( x > 4 ) if( x > 8 ) result= 'x is greater than 8' else result= 'x is less than or equal to 8' //a single 'else' with two 'if' clauses belongs to the innermost assert result == 'x is less than or equal to 8'
The meaning of the 'in' operator depends whether its context in the code is conditional or iterative. When in a conditional context, the 'isCase' method of the target is invoked:
class A{
boolean isCase(Object o){
if(o == 'A') return true
else return false
}
}
def a= new A()
assert a.isCase('A')
assert 'A' in a //more common, shortcut syntax for isCase()
assert ! (a.isCase('Z'))
assert ! ('Z' in a) //more common, shortcut syntax for isCase()
The switch statement inspects an expression and resumes execution from the first matching case-expression, ie, regex matched, list or set or range contained in, class an instance of, or object equal to:
def values= [
'abc': 'abc',
'xyz': 'list',
18: 'range',
31: BigInteger,
'dream': 'something beginning with dr',
1.23: 'none',
]
values.each{
def result
switch( it.key ){
case 'abc': //if switched expression matches case-expression, execute all
//statements until 'break'
result= 'abc'
break
case [4, 5, 6, 'xyz']:
result= 'list'
break
case 'xyz': //this case is never chosen because 'xyz' is matched by
//previous case, then 'break' executed
result= 'xyz'
break
case 12..30:
result= 'range'
break
case Integer:
result= Integer //because this case doesn't have a 'break', result
//overwritten by BigInteger in next line
case BigInteger:
result= BigInteger
break
case ~/dr.*/:
result= 'something beginning with dr'
break
default:
result= 'none'
}
assert result == it.value
}
When we supply our own values in the case-expression, the 'isCase' method is invoked to determine whether or not the switch-expression is matched. If there's no 'isCase' method, the 'equals' method is used to test for equality:
class A{
boolean isCase(Object switchValue){ //'isCase' method used for case-expression
if(switchValue == 'Hi') return true
else return false
}
}
switch( 'Hi' ){
case new A():
assert true
break
default:
assert false
}
class B{
boolean equals(Object switchValue){ //'equals' method used for case-expression
this.class == switchValue.getClass()
}
}
switch( new B() ){
case new B():
assert true
break
default:
assert false
}
Iterative Statements
The while statement lets us iterate through a block of code:
def x= 0, y= 0 while( x < 5 ){ x++ y += x } assert x == 5 && y == 15 while( x < 10 ) x++ //curlies optional if only one statement assert x == 10 while( x < 15 ){ //we can break out of a while-loop using 'break' x++ if( x == 12 ) break } assert x == 12 while( x != 15 && x != 18 ){ //we can jump to the next iteration of a while-loop using 'continue' x++ if( x == 15 ){ x++ continue } } assert x == 18
We've already seen the 'each' and other related method calls, which emulate the while-statement at a higher level of abstraction, but with some restrictions: the loop variable isn't available outside the loop, no guarantees are made about the order of iteration through the collection, and the 'break', 'continue', and 'return' commands aren't available:
int n=1 while( n <= 5 ){ def y= 0 (1..n).each{ //loop variable, here 'it', not available outside loop y += it //if( y > 8 ) break //a syntax error when uncommented: 'break' command not available here } assert y == (n+1)*(n/2.0) //another way to add the numbers 1 to n n++ }
Other method calls that loop are 'times', 'upto', 'downto', and 'step'. Like 'each', they don't allow 'break', 'continue', and 'return' commands, but do make guarantees about the order of iteration:
def a= 2
3.times{
a= a*a
}
assert a == 256
def list= []
1.upto(5){
list<< it
}
assert list == [1, 2, 3, 4, 5]
list= []
5.3.downto(2.1){ //'upto', 'downto', and 'step' also work with decimal numbers
list<< it
}
assert list == [5.3, 4.3, 3.3, 2.3]
list= []
1.step(9.5, 2.5){
list<< it
}
assert list == [1, 3.5, 6, 8.5]
We can label any statement with a name. Labelling a while loop lets any arbitrarily deep nested statement break out of or continue on from it:
yonder: def d= 4
there: {
def e= 5
here: if( e == 5 ){
def f= 6
there: def g= 7 //label can repeat a previously-used outer label
}
}
there: def h= 8
//label can repeat a previously-used label at same syntactic level
def i=0, j=0
outer: while( i<5 ){ //labelling a while loop is especially useful...
j= 0
i++
while( j<5 ){
j++
if( i==3 && j==2 ) break outer
//...because we can break out of a specified labelled while loop
}
}
assert i==3 && j==2
def outer= 0, inner= 0
outer: while( outer != 5 && outer != 8 ){
//label can have same name as any variables
inner= 0
outer++
while( inner<5 ){
inner++
if( outer==5 ){
outer++
continue outer
//we can also continue on from a specified labelled while loop
}
}
}
assert outer==8
For-Statements
For-statements are complex yet powerful iterative statements with many possible formats. When 'in' is used in the iterative context of a for-statement, the 'iterator' method of the target is invoked. The 'iterator' method must return an Iterator, defining at least the 'hasNext' and 'next' methods:
class CountToFive{
def n= 0
def iterator(){
[ hasNext: { n<5 },
next: { n++ },
] as Iterator
}
}
def list= []
def counter= new CountToFive()
for(int i in counter){
list<< i
}
assert list == [0, 1, 2, 3, 4]
The for-statement works with many kinds of objects (eg, Collection, array, Map, String, regex, File, Reader, InputStream, etc):
def list= [] for( e in [0, 1, 2, 3, 4] ){ //iterate over a list list<< e } assert list == [0, 1, 2, 3, 4] list= [] for( i in 1..9 ){ //iterate over a range list<< i } assert list == [1, 2, 3, 4, 5, 6, 7, 8, 9] list= [] for( e in (3..6).toArray() ){ //over an array list<< e } assert list == [3, 4, 5, 6] list= [] for( e in ['abc':1, 'def':2, 'xyz':3] ){ //over a map list<< e.value } assert list as Set == [1, 2, 3] as Set list= [] for( v in [1:'a', 2:'b', 3:'c'].values() ){ //over values in a map list<< v } assert list as Set == ['a', 'b', 'c'] as Set list = [] for( c in "abc" ){ //over the chars in a string list<< c } assert list == ['a', 'b', 'c']
We can use 'break' and 'continue' within a for-loop using 'in':
def list = [] for( c in 'abc' ){ list<< c if( c == 'b' ) break } assert list == ['a', 'b'] list = [] for( c in 'abc' ){ if( c == 'b' ) continue list<< c } assert list == ['a', 'c']
'each' methods can also be considered as emulating for-loops at a higher level of abstraction, without the guarantees about the order of iteration, and the 'break', 'continue', and 'return' commands being unavailable:
def list= []
['a', 'b', 'c'].each{
list<< it
}
assert list == ['a', 'b', 'c']
//instead of...
list= []
for( item in ['a', 'b', 'c'] ){
list<< item
}
assert list == ['a', 'b', 'c']
Another format for the for-statement is the initializer-condition-incrementer format:
def list= [] for(def i=0; i<5; i++){ //first value an initializer, second a condition, third an incrementer list<< i } assert list == [0, 1, 2, 3, 4] //equivalent while-statement... list= [] try{ def i=0 //initializer while( i<5 ){ //condition list<< i i++ //incrementer } } assert list == [0, 1, 2, 3, 4] //for-statement with 'break' list= [] for(def i=0; i<5; i++){ list<< i if( i == 2 ) break } assert list == [0, 1, 2] //equivalent while-statement with 'break' list= [] try{ def i=0 while( i<5 ){ list<< i if( i == 2 ) break i++ } } assert list == [0, 1, 2] //for-statement with 'continue' list= [] for(def i=0; i<5; i++){ if( i == 2 ){ i++; continue } //the incrementer isn't executed automatically when we 'continue' list<< i } assert list == [0, 1, 3, 4] //equivalent while-statement with 'continue' list= [] try{ def i=0 while( i<5){ if( i == 2 ){ i++; continue } list<< i i++ } } assert list == [0, 1, 3, 4]
We can have more than one initializer, and more than one incrementer:
//two initializers and two incrementers... def list= [] for(def i=0; def j=10; i<5; i++; j++){ //the middle expression is the condition list<< i + j } assert list == [10, 12, 14, 16, 18] //three initializers and three incrementers... list= [] for(def i=0; def j=10; def k=20; i<3; i++; j++; k++){ list<< i + j + k } assert list == [30, 33, 36] //when there's an even number of expressions, the condition is just before //the middle... list= [] try{ def i=0 for(def j=10; i<5; i++; j++){ list<< i + j } } assert list == [10, 12, 14, 16, 18] //we can force in more initializers than incrementers by using //'null' statements... list= [] for(def i=0; def j=10; i<5; i++; null ){ list<< i + j } assert list == [10, 11, 12, 13, 14]
Operator Overloading
The precedence heirarchy of the operators, some of which we haven't looked at yet, is, from highest to lowest:
$(scope escape) new ()(parentheses) [](subscripting) ()(method call) {}(closable block) [](list/map) . ?. *. (dots) ~ ! $ ()(cast type) **(power) ++(pre/post) --(pre/post) +(unary) -(unary) * / % +(binary) -(binary) << >> >>> .. ..< < <= > >= instanceof in as == != <=> & ^ | && || ?: = **= *= /= %= += -= <<= >>= >>>= &= ^= |=
We've seen how the 'as' operator is mapped to the asType() method, and how the 'in' operator is mapped to the isCase() and iterator() methods. Many more operators have equivalent method names. We've seen how [] subscripting has equivalent methods getAt() and putAt() in the HashMap class. They are also equivalent when we define such methods on our own classes:
class A{
int key
def value
def getAt(int n){ if(key == n) return value }
void putAt(int n, def o){ key= n; value= o }
}
def a= new A()
a[1]= 'abc' //calls putAt()
assert a[1] == 'abc' //calls getAt()
assert a[2] == null
We've also seen how various operators have equivalent method names in the numerical classes, such as Integer, BigDecimal, float, etc. They, too, are also equivalent when we define such methods on our own classes:
class OddNumber{ //only gives odd results to operations, adding 1 if necessary
int value
OddNumber(int n){ value= (n%2)? n: n+1 }
def power(int n){ value**n }
def multiply(int n){ def i= value*n; (i%2)? i: i+1 }
def div(int n){ int i= value/n; (i%2)? i: i+1 }
def mod(int n){ int i= value - div(n)*n; (i%2)? i: i+1 }
def plus(int n){ int i= value + n; (i%2)? i: i+1 }
def minus(int n){ int i= value - n; (i%2)? i: i+1 }
def and(int n){ n == value }
def or(int n){ n == value || (n == value-1) }
def xor(int n) {n == value-1 }
def leftShift(int n){ value= (n%2)? n: n+1 }
def rightShift(int n){ (value * 10**n) + 1 }
def rightShiftUnsigned(int n){ (value * 10**(n*2)) + 1 }
def next(){ new OddNumber(value + 2) }
def previous(){ new OddNumber(value - 2) }
}
def e= new OddNumber(6)
assert e.value == 7
assert e**3 == 343 //calls power()
assert e*4 == 29 //calls multiply()
assert e/3 == 3 //calls div()
assert e%3 == -1 //calls mod()
assert e+5 == 13 //calls plus()
assert e-1 == 7 //calls minus()
assert e & 7 //calls and()
assert e | 6 && e | 7 //calls or()
assert e ^ 6 //calls xor()
e<< 2 //calls leftShift()
assert e.value == 3
assert e>>2 == 301 //calls rightShift()
assert e>>>2 == 30001 //calls rightShiftUnsigned()
assert (e++).value == 3 //calls next()
assert e.value == 5
assert (++e).value == 7
assert e.value == 7
assert (e--).value == 7 //calls previous()
assert e.value == 5
assert (--e).value == 3
assert e.value == 3






