Because collections are so prominent in programming, Groovy alleviates the tedium of using them by directly supporting datatypes of a collective nature: ranges, lists, and maps. In accordance with what you have seen of the simple datatypes, Groovy’s support for collective datatypes encompasses new lightweight means for literal declaration, specialized operators, and numerous GDK enhancements.
The notation that Groovy uses to set its collective datatypes into action will be new to Java programmers, but as you will see, it is easy to understand and remember. You will pick it up so quickly that you will hardly be able to imagine there was a time when you were new to the concept.
Despite the new notation possibilities, lists and maps have the exact same semantics as in Java. This situation is slightly different for ranges, because they don’t have a direct equivalent in Java. So let’s start our tour with that topic.
Working with ranges:
Think about how often you’ve written a loop like this:
Next, consider how often you’ve written a conditional such as this:
Now, we’re not saying that we make mistakes using this syntax on a regular basis. We’re not saying that we can’t get used to (or indeed haven’t gotten used to) the C-style for loop, as countless programmers have over the years. What we’re saying is that it’s harder than it needs to be; and, more important, it’s less expressive than it could be. Can you understand it? Absolutely. Then again, you could understand this chapter if it were written entirely in capital letters—that doesn’t make it a good idea, though.
Groovy allows you to reveal the meaning of such code pieces by providing the concept of a range. A range has a left bound and a right bound. You can do something foreach element of a range, effectively iterating through it. You can determine whether a candidate element falls inside a range. In other words, a range is an interval plus a strategy for how to move through it.
Ranges are specified using the double dot .. range operator between the left and the right bound. This operator has a low precedence, so you often need to enclose the declaration in parentheses. Ranges can also be declared using their respective constructors.
The ..< range operator specifies a half-exclusive range—that is, the value on the right is not part of the range:
- Listing 4.1 Range declarations
Because every range is an object, you can pass a range around and call its methods. The most prominent methods are each , which executes a specified closure for each element in the range, and contains , which specifies whether a value is within a range or not.
Being first-class objects, ranges can also participate in the game of operator overriding (see section 3.3) by providing an implementation of the isCase method, with the same meaning as contains. That way, you can use ranges as grep filters and as switch cases. This is shown in listing 4.2.
- Listing 4.2 Ranges are objects
Listing 4.1 made use of date and string ranges. In fact, any datatype can be used with ranges, provided that both of the following are true:
As an example, we implement a class Weekday in listing 4.3 that represents a day of the week. From the perspective of the code that uses our class, a Weekday has a value 'Sun' through 'Sat' . Internally, it’s just an index between 0 and 6 . A little list maps indexes to weekday name abbreviations.
We implement next and previous to return the respective new Weekday object. compareTo simply compares the indexes. With this preparation, we can construct a range of working days and work our way through it, reporting the work done until we finally reach the well-deserved weekend. Oh, and our boss wants to assess the weekly work report. A final assertion does this on his behalf.
- Listing 4.3 Custom ranges: weekdays
* Groovy - Operator Overloading