Effective Java with Groovy - Favour Internal Iterators
Let's start with the following code making use of the traditional for loop.
List<String> languages = List.of("Java", "Groovy", "Kotlin"); // List.of() requires Java 9 or above
for (int i = 0; i < languages.size(); i++) {
System.out.println(languages.get(i));
}
Compare the above code to the following one making use of for-each loop.
List<String> languages = List.of("Java", "Groovy", "Kotlin");
for (String language : languages) {
System.out.println(language);
}
By using the for-each loop, we have simplified our code due to the following two reasons.
First, Using for-each, we have directly translated our intension into code - "Hey, print each element from the list", while using the traditional for loop here is at a different level of abstraction.
Second, the code making use of for-each has fewer moving parts, reducing the probability of introducing bugs. With the traditional for-loop, you always have the risk of 'off by one' error. Put differently, the fewer moving parts you have, lesser the cognitive load.
For the above reasons, Effective Java recommends "prefer for-each loops to traditional for loops".
Let's apply this to Groovy. Going by the recommendation verbatim, we could retain the Java version, or we could make use of for-in as follows.
List<String> languages = ['Java', 'Groovy', 'Kotlin']
for (language in languages) {
println language
}
Note how making use of 'in' allowed us to drop the type specifier for the variable language
. Also, I have made use of the native support for creating lists (the square bracket syntax).
If you are new to Groovy, you might want to get a taste of Groovy from this post.
When we moved from the traditional for loop to for-each loop, we gave up the control of how to iterate through the list. We limited ourselves to specify what to do with each element we obtain during the iteration. In other words, we moved from an external iterator to an internal iterator.
Groovy also provides a higher-order method each
for all iterable types. each
accepts a closure. We can make use of it as follows.
languages.each { language -> println language }
Groovy also provides several special-purpose internal iterators. The following example shows a few of them in action.
List<String> languagesWithLetterV = languages.findAll
{ language -> language.containsIgnoreCase("V") }
assert languagesWithLetterV == ['Java', 'Groovy']
List<Integer> numberOfCharsInEach = languages.collect
{ language -> language.size() }
assert numberOfCharsInEach == [4, 6, 6]
String commaSeparatedLanguages = languages.join(", ")
assert commaSeparatedLanguages == 'Java, Groovy, Kotlin'
See how internal iterators make out code declarative. There is a good chance that the internal iterator you are looking for is already available in Groovy.
If you are interested in learning more about how Effective Java applies to Groovy code, you may want to take a look at my GR8Conf EU presentation.
References: