This article is based on Groovy in Action, Second Edition, to be published on Summer 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
also read:
Exiting Blocks and Methods
Introduction
Although it’s nice to have code that reads like a simple list of instructions with no jumping around, it’s often vital that control is passed from the current block or method to the enclosing block or the calling method—or sometimes even further up the call stack. Just like in Java, Groovy allows this to happen in an expected, orderly fashion with return, break, and continue statements and, in emergency situations, with exceptions. Let’s take a closer look.
Normal termination: return/break/continue
The general logic of return, break, and continue is similar to Java. One difference is that the return keyword is optional for the last expression in a method or closure. If it is omitted, the return value is that of the last expression. Methods with an explicit return type of void do not return a value, whereas closures always return a value.
Listing 1 shows how the current loop is shortcut with continue and prematurely ended with break. As in Java, there is an optional label.
Listing 1 Simple break and continue
def a = 1 while (true) { //#1 Do forever a++ break //#2 Forever is over now } assert a == 2 for (i in 0..10) { if (i==0) continue //#3 Proceed with 1 a++ if (i > 0) break //#4 Premature loop end } assert a==3 Using break and continue is sometimes considered smelly. However, they can be useful for controlling the workflow in services that run in an endless loop or for breaking apart complex conditional logic, such as this: for(i in whatever){ if (filterA) continue // skip if filter matches if (conditionB) break // exit loop if condition matches // normal case here }
Similarly, returning from multiple points in the method is frowned upon in some circles but other people find it can greatly increase the readability of methods that might be able to return a result early. We encourage you to figure out what you find most readable and discuss it with whomever else is going to be reading your code—consistency is as important as anything else.
As a final note on return handling, remember that, when closures are used with iteration methods such as each, a return statement within the closure returns from the closure rather than the method.
Exceptions: throw/try-catch-finally
Exception handling is exactly the same as in Java and follows the same logic. Just as in Java, you can specify a complete try-catch-finally sequence of blocks, or just try-catch, or just try-finally. Note that, unlike various other control structures, braces are required around the block bodies whether or not they contain more than one statement. The only difference between Java and Groovy in terms of exceptions is that declarations of exceptions in the method signature are optional, even for checked exceptions. Listing 2 shows the usual behavior.
Listing 2 Throw, try, catch, and finally
def myMethod() { throw new IllegalArgumentException() } def log = [] try { myMethod() } catch (Exception e) { log << e.toString() } finally { log << 'finally' } assert log.size() == 2
In accordance with optional typing in the rest of Groovy, a type declaration is optional in the catch expression. And, like in Java, you can declare multiple catches.
There are no compile-time or runtime warnings from Groovy when checked exceptions are not declared. When a checked exception is not handled, it is propagated up the execution stack like a RuntimeException in Java.
It is worth noting an issue relating to exceptions. When using a Groovy class from Java, you need to be careful—the Groovy methods will not declare that they throw any checked exceptions unless you’ve explicitly added the declaration even though they might throw checked exceptions at runtime. Unfortunately, the Java compiler attempts to be clever and will complain if you try to catch a checked exception in Java when it believes there’s no way that the exception can be thrown. If you run into this and need to explicitly catch a checked exception generated in Groovy code, you may need to add a throws declaration to the Groovy code just to keep javac happy.
Summary
This article covered one of Groovy’s control structures: exiting blocks and methods early. We haven’t seen any big surprises: everything turned out to be like Java, enriched with a bit of Groovy flavor. The only structural difference is the for loop. Exception handling is very similar to Java, minus the requirement to declare checked exceptions.