IV Control Flow Statements
39. Introduction
Most code needs to reflect a decision-making process. A decision may be an either-or in which we process one set of statements rather than another. Another decision may be to run the same set of statements for a finite (or infinite) period.
Groovy supports a range of control-flow statements:
- The conditional statements:
-
ifandif-else switchtry-catch-finally
-
- The looping statements:
for-
for-each while
- The branching statements:
breakcontinuereturn
This section will describe the conditional and looping statements and discuss how and where the break and continue branching statements fit in.
The try-catch-finally statement will be explored in the Exceptions section.
The return statement will be explored in the Methods section and the Closures section.
40. Blocks
Groovy uses curly brackets {..} to enclose blocks of code. These are primarily used to group a set of statements within elements such as control flow, class, method and closure declarations. Blocks also let you “partition off” parts of code so that items such as variables aren’t visible to other parts of your code - referred to as “scope”.
Groovy doesn’t allow the use of anonymous code block such as the one below:
{
def count = 0
assert count == 0
}
You need to label the block provided above if you want to use it in Groovy code:
Block1:{
def count = 0
assert count == 0
}
This limitation is primarily due to the closure syntax - labelling the block ensures it isn’t confused with a closure.
Blocks appearing within blocks are called “nested blocks”:
Block1:{
def count = 0
assert count == 0
NestedBlock: {
assert count == 0
}
}
If the above code was a Groovy script the block structures really would be redundant “noise” - you’re not likely to see usage such as Block1:{..} very often. You’re more likely to see blocks used with control flow statements (such as if):
if (true) {
//some code
}
… in class declarations:
class Person {
}
… in method declarations:
def doStuff() {
}
… in closure declarations
{name ->
println "Hello, $name"
}
Variable Scope
Variable Scope refers to the visibility of a variable from other blocks. A variable declared at the top level is visible to blocks nested within it. The reverse, however, is not true. This lets us declare variables within a block that is specific to that block (and any nested blocks) but doesn’t interfere with the rest of the program.
In the example below, the code within Block1 can “see” the count variable so both asserts work:
def count = 0
Block1: {
assert count == 0
}
assert count == 0
The second assert will fail as count is not in scope in the main body of the script:
Block1: {
def count = 0
assert count == 0
}
assert count == 0
In the next example, count is visible to the nested bock (Block2):
Block1:{
def count = 0
Block2:{
assert count == 0
}
}
What all of this means is that you need to keep an eye on where the variable can be “seen”. In the example below, the volume variable is visible (within scope) of the if block but the dbLevel variable is local to the if block.
def volume = 11
if (volume > 10) {
def dbLevel = 'extreme'
println "A volume of $volume is $dbLevel"
//more code
}
I repeatedly make a mistake in which I declare a variable within a try block but I need to use the variable later on. The code below is an example of my mistake - println myObj will never work as myObj is not visible at that level.
try {
def myObj = new Object()
} catch (any) {
//Do something to handle the exception
}
println myObj
I’ve put the def myObj = new Object() into the try block as the instantiation may cause an exception. What I needed to do was separate the variable definition (def) from the creation of a new instance (instantiation):
def myObj
try {
myObj = new Object()
} catch (any) {
//Do something to handle the exception
}
println myObj
41. The if Statement
Few programs consist of a set of statements read one after another from top-to-bottom. At various points we need the code to evaluate one set of statements rather than another - depending on the current context in which the program is running. The if statement is key to directing which route to take.
As an example, let’s say we have some code that displays the result of a division. It’s very important that we don’t try to divide a number by 0 as this causes an error. In the code below we use the if statement to check if the denominator is 0 before we perform the division:
def numerator = 0
def denominator = 0
// ... various statements
if (denominator != 0) {
println numerator / denominator
}
if evaluates the conditional expression between the parentheses - (..) - and will only process the statement block if the conditional result is true.
if - else
An else section can be provided where you want to process statements when the conditional result is false.
if - elsedef numerator = 0
def denominator = 0
// ... various statements
if (denominator != 0) {
println numerator / denominator
} else {
println 'I refuse to divide by zero'
}
if - else if - else
if and else let you deal with situations where you have two possible outcomes but sometimes you might have a few conditions that you want to check for:
if - else if - elsedef game = 'tennis'
if (game == 'chess') {
println 'I like to play chess'
} else {
if (game == 'tennis') {
println 'I can play tennis if you want'
} else {
println "Sorry, I don't know how to play $game"
}
}
The code above places another if check within the else block and checks if the player is asking for a different game (“tennis”). This looks (sort of) clean but start to picture a larger set of checks and the code gets confusing. Instead of nesting the second if within the else block, Groovy lets you use else if:
def game = 'tennis'
if (game == 'chess') {
println 'I like to play chess'
} else if (game == 'tennis') {
println 'I can play tennis if you want - just let me warm up'
} else if (game == 'golf') {
println 'I can play golf if you want but I get very angry'
} else {
println "Sorry, I don't know how to play $game"
}
The code above tidies up the nesting by allowing the second if to be part of the else block. Essentially you can provide a long series of checks in a single if-else if set and Groovy will evaluate each until one resolves to true. You can optionally provide an else block at the end to ensure that can all other outcomes (defined or otherwise) be dealt with.
It’s important to note that once an if evaluates to true, no other if expressions are evaluated. It’s a case of the first positive match wins. In the code below, the conditional (game=='chess' || game == 'tennis') is redundant because an earlier condition would have returned true ((game == 'chess'))
if (game == 'chess') {
println 'I like to play chess'
} else if (game == 'tennis') {
println 'I can play tennis if you want'
} else if (game=='chess' || game == 'tennis') {
println 'Can you really play both of these at once?'
} else {
println "Sorry, I don't know how to play $game"
}
That last point is also important as Groovy will also not evaluate any expressions used in further if expressions. In the example below, --i is never evaluated as the first if expression resolves to true:
def i = 10
if (++i > 10) {
//do something
} else if (--i < 10) {
//do something
}
assert i == 11
42. The switch Statement
There are times when the number of possibilities are larger than a readable if-else-if statement can handle. There are also times where we want to execute a specific set of statements but then “fall through” to others. It’s in these places that the switch statement comes in handy.
Let’s take a look at an example:
switch (game) {
case 'chess':
println 'I like to play chess'
break
case 'go':
println 'I can play go if you want'
break
case 'ludo':
println 'I can play ludo if you want'
break
default:
println "Sorry, I don't know how to play $game"
}
The switch statement evaluates an expression - in the example above it’s the value of a variable (game) but it could be the result of an operation. This is called the switch value.
The rest of the switch block is broken up into cases and (optionally) a default. Each case is assessed against the switch value and the first match is selected. Each case is declared:
- prefixed by the keyword
case, followed by- an expression, and lastly,
- a colon
:
- a colon
- an expression, and lastly,
The case expression can be a more complex expression but in our example above I’ve used a string value (e.g. chess). If the value of game was 'ludo' then the statements under case 'ludo': are evaluated.
The break statement indicates the end of the set of statements for the case and signals that the switch statement has completed. In the example above I’ve used break for every case but this isn’t required. If break isn’t provided, execution of the switch is said to “fall through” to the next set of statements. Essentially, Groovy will keep evaluating expressions until either a break is provided or the end of the switch block is reached.
Falling through can be useful if you want to perform the same set of statements for more than one case. The example snippet below provides an example of such a case (pun intended):
case 'snowboarding':
case 'snowball fight':
println 'But it\'s summer!'
break
Here’s another example of falling through:
def score = 2
println 'You win: '
switch (score) {
case 3:
println '- gift voucher'
case 2:
println '- toy'
case 1:
println '- stamp'
default:
println '- certificate'
}
In the code above the prizes are accumulated depending on your score and a score of 2 sees you win a toy, stamp and certificate - lucky you! Our default ensures that every child wins a prize, regardless of their score.
The Versatile Groovy switch
The Groovy switch statement is much more powerful than Java’s and can work across all data types.
In Java
switchis limited to the primitive types (and their object wrappers), Strings and Enums and thecaseexpression is limited to a value (not an operation).
Groovy achieves through the use of the isCase method defined for the Object class and overloaded by subclasses such as Pattern. Essentially, the switch value is passed to the case instance. In the example below, 10.isCase(score) would be called:
switch (score) {
case 10:
//etc
}
If this all sounds a little foreign, don’t worry, just check out the following sections to see how versatile the switch statement can be.
Using Ranges
Ranges can be used in the case and is selected when the switch value is in the range.
Let’s play some blackjack:
switch (hand) {
case 1..16:
println 'HIT'
break
case 17..21:
println 'STAND'
break
default:
println 'BUST'
break
}
Using Regular Expressions
Groovy extends the Java regular expression Pattern class to provide for their use in switch statements. This can be really handy if you want to test for a number of patterns.
In the example below I set up a list of URI’s1 and assess them against regular expressions based on various URI formats.
def location = ['urn:isbn:0451450523',
'http://en.wikipedia.org/wiki/Uniform_resource_locator',
'HTTPS://secure.example.com/',
'mailto:duncan@example.com',
'fax:53454567567']
location.each {
switch( it.toLowerCase() ) {
case ~/^urn:.*/ :
print 'This looks like a URN'
break
case ~/^https?:.*/ :
print 'This looks like a HTTP(S) URL'
break
case ~/^mailto:.*/ :
print 'This looks like an email address'
break
default:
print 'Not sure what this is'
}
println " ($it)"
}
Using Class Checks
Groovy’s switch can use a data type (Class) for comparison. Essentially, the switch will use the instanceof operator to compare the switch value with a class name provided in the case. In the example below I iterate through a list containing elements of various types. I use the switch statement to then determine the type of each list item:
def objList = [ 10,
'hello',
[1, 5, 8],
[name: 'Dave'],
~/\n/
]
for (item in objList) {
switch (item) {
case String:
println 'You gave me a string'
break
case Number:
println 'You gave me a number'
break
case List:
println 'You gave me a list'
break
case Map:
println 'You gave me a map'
break
default:
println "Sorry, I can't handle instances of ${item.class}"
}
}
- See http://en.wikipedia.org/wiki/Uniform_resource_identifier↩
43. The for Loop
The for loop will repeat a block of code whilst its condition is true:
forfor (<condition>) {
//statements
}
The for-in Loop
The for-each loop basically says “for each element is this collection, perform the following”:
for-infor (<var> in <iterator>) {
//statements
}
In the Groovy for-in loop we have a variable (var) provided as the next item in the iterator1. In most cases this is a collection of objects such as a list or map.
You can’t change the iterator within a loop - it’d just be too confusing! That means code such as the following causes an exception and won’t run:
def scores = [4, 8, 2]
for (i in scores) {
scores << i**2
}
Using a range
Consider a variable called countdown to hold a range:
def countdown = (10..0)
By itself, countdown probably doesn’t seem too useful but let’s look at a for loop:
def countdown = (10..0)
for (i in countdown) {
println "Launch in T-$i seconds"
}
println 'Blast off!'
So let’s break down for (i in countdown) {:
- The
(...)parentheses holds the condition - The variable
iwill hold the next element incountdown- You can name this as you would any other variable
- The element to the right of the
inreserved word is the variable being iterated (looped) over -
{starts the loop body- and its matching
}closes it
- and its matching
If you run this code in groovyConsole you’ll see our launch sequence displayed.
To make our code even more compact (yet readable), we can use the literal value for the range (10..0) in our for condition:
for (i in 10..0) {
println "Launch in T-$i seconds"
}
println 'Blast off!'
Lists
Looping through the items in a list is quite straight forward now you’ve seen the range example:
def planets = [
'Mercury',
'Venus',
'Earth',
'Mars'
]
for (planet in planets) {
println planet
}
Maps
Iterating through maps is much the same as we did for lists but the iteration variable consists of the key and the value for that map item. The next code segment will just display the value of each map item:
def domains = [
'com': 'Corporate sites',
'org': 'Non-commercial sites',
'mil': 'Military sites'
]
for (site in domains) {
println site
}
The code above will display a set of lines such as com=Corporate sites - illustrating that site contains a key/value pair.
Instead of printing out the iteration variable (site) we can be a little smarter and access the key (site.key) and value (site.value) individually:
def domains = [
'com': 'Corporate sites',
'org': 'Non-commercial sites',
'mil': 'Military sites'
]
for (site in domains) {
println site.key << ': ' << site.value
}
The Java for-each
Groovy supports Java’s version of a for-each loop:
for (<Type> <var>: <iterator>) {
//statements
}
Unlike Groovy’s for-in loop, the Java version:
- uses
:instead ifin - Requires you to declare a data type for the iteration variable
- Which makes lists consisting of elements with different data types a little more tricky.
Re-writing the previous planets example in the Java for-each style we’d see the following:
def planets = [
'Mercury',
'Venus',
'Earth',
'Mars'
]
for (String planet: planets) {
println planet
}
The Java version has no real benefit over the Groovy approach so I’d stick with the Groovy for (<var> in <iterator>). The Groovy approach also makes for easier iteration over maps.
If you really want to set a data type for your iteration variable you can still be Groovy about it:
def planets = [
'Mercury',
'Venus',
'Earth',
'Mars',
]
for (String planet in planets) {
println planet
}
A C-style for Loop
Java (and Groovy) both offer the for loop syntax found in the C programming language:
forfor (<init variable>; <condition>; <update expression>) {
//statements
}
-
<init variable>initialises the loop variable before the first iteration -
<condition>sets the condition to be met for the iteration to commence -
<update expression>is evaluated after each iteration
This next example does the same as for (i in (10..1)) but is more verbose:
for (i = 10; i >= 0; i--) {
println i
}
So what’s happening in (i = 10; i >= 0; i--)?
-
i = 10initialises the loop variableito10 -
i >= 0is the conditional that says “keep looping untiliis no longer greater than or equal to 10” -
i--is evaluated after each iteration -iis decremented by 1.
The <update expression> can be a more complex expression:
for (i = 0; i <= 20; i += 2) {
println i
}
Infinite loops
The C-style for loop can let you set up infinite loops:
for (;;) {
}
These are often used in event-based systems. Essentially, the program will enter a loop and await incoming events such as a mouse-click. It’s a very interesting aspect to coding but outside the scope of these tutorials.
The C-style loop doesn’t protect you from altering the thing you’re working on and, perhaps inadvertently, creating an infinite loop. That means that the code below needs the safety brake provided by (&& i < 20) as the loop keeps altering the size of scores. If you take out && i < 20 and run the code below it won’t stop unless you interrupt it or you run out of system resources:
def scores = [4, 8, 2]
for (i = 0; i < scores.size && i < 20; i++) {
println scores[i]
scores << scores[i]**2
}
- Many Groovy classes implement a feature (interface) named
Iterableand these let you easily iterate over a collection of items. Lists and Maps are both iterable, making it very easy to loop through their contents.↩
44. The while loop
The while loop will repeat a block of code whilst its condition is true:
while (<condition>) {
//statements
}
while loops feature the following:
-
<condition>can be a value or expression - the loop will only iterate if<condition>istrue. - The
whileloop syntax does not manage an iterator variable - you must do this yourself- It’s very easy to create an infinite
whileloop if you’re not paying attention
- It’s very easy to create an infinite
The example below uses a common approach for while loops and sets a flag to indicate when the desired state has been reached and the loop can stop:
def flag = false
def num = 0
while (!flag) {
num++
if (num**2 == 64) {
flag = true
}
}
println "The square root of 64 is $num"
The code above will increment num by 1 each iteration and test to see if num^2 is equal to 64. Once the correct num is reached, flag is change to true and the while condition now resolves to false - indicating that the while loop should run the next iteration.
The while loop can also be used to create an infinite loop: while(true) {}
There is no do...while loop
Unlike Java, Groovy does not have a do...while loop1. Don’t be sad.
45. Branching statements
Groovy has three branching statements:
breakcontinuereturn
break
We first came across the use of break in the switch statement - it’s used to indicate the end of the set of statements for the case and signals that the switch statement has completed.
The break reserved word is also used to exit out of a loop - even if there are more iterations possible. In the code below I iterate through the list until I reach the value 'Earth' and then break out of the loop:
def planets = [
'Mercury',
'Venus',
'Earth',
'Mars'
]
for (planet in planets) {
println planet
if (planet == 'Earth') {
break
}
}
When we looked at the while loop I gave an example of setting a flag variable and checking that as the loop’s condition. This could have been refined using break and not using flag at all:
def num = 0
while (true) {
num++
if (num**2 == 64) {
break
}
}
println "The square root of 64 is $num"
I’ll refine that while loop just a little further:
def num = 0
while (++num) {
if (num**2 == 64) {
break
}
}
println "The square root of 64 is $num"
As Groovy resolves a number other than 0 to be true, ++num will allow the loop to commence and we still rely on break to get us out of the loop.
continue
The continue reserved word will cause the next iteration of the loop - ignoring anything within the rest of the current iteration. In the loop below I use continue to ignore 'Earth'.
def planets = [
'Mercury',
'Venus',
'Earth',
'Mars'
]
for (planet in planets) {
if (planet == 'Earth') {
continue
}
println planet
}
Labelled branching
If you have a secret desire to create spaghetti code that quickly becomes unreadable then labels are for you!
Labels are used when you have nested loops - a loop within a loop. Both break and continue can be given a label that directs the program to another nesting level. In the example below I label the first loop flowerlist and, when I get to the colour 'Green' in the inner loop, my continue is directed not at the inner loop but at the outer one labelled flowerlist - this is called a “labelled continue”:
def colours = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']
def flowers = ['daisy', 'rose', 'tulip']
flowerlist:
for (flower in flowers) {
for (colour in colours) {
if (colour == 'Green') {
continue flowerlist
}
println "$colour $flower"
}
}
As you can see, the label consists of a name, followed by a colon (:) that appears above the loop being labelled. The code above will display the following:
Red daisy
Orange daisy
Yellow daisy
Red rose
Orange rose
Yellow rose
Red tulip
Orange tulip
Yellow tulip
A labelled break works much the same way and defers control back to the loop designated by the label.
There are times when labels are useful but really think about what you need to do before resorting to them. For example, using a break in the code above would have done the job:
def colours = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']
def flowers = ['daisy', 'rose', 'tulip']
for (flower in flowers) {
for (colour in colours) {
if (colour == 'Green') {
break
}
println "$colour $flower"
}
}
return
The return statement is used to hand control back to the caller1. In most cases, return is used in a method or closure to return a value. In the most basic usage, the keyword return just appears on a line by itself:
//some code
return
Any code that appears below the return is unreachable and is never evaluated:
//some code...
return
//unreachable code...
Return can be provided a value and this is returned to the caller:
//some code
return 3.14
You can use an expression in the return statement and the result is returned:
//some code
return circumference / diameter
Using return within the body of a script2 will cause it to exit as the command-line/terminal is the caller. When exiting you can choose to return a value - usually 0 if the script succeeds, or an error number if it fails.