Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

A Groovy script is a sequence of statements:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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
    case {it instanceof Integer && it>30}: //use Closure
      result= 'result is > 30'
      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:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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:

Code Block
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):

Code Block
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':

Code Block
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:

Code Block
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:

Code Block
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 ){ 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:

Code Block
//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:

Code Block
$(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:

Code Block
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:

Code Block
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