Understanding Groovy Collection Methods

If you learned Groovy with a working knowledge of Java, you probably found the transition to be a piece of cake.  I know I, personally, started coding right away and didn’t even realize when I was neglecting “the Groovy way.” The most common series of instances in which a Groovy solution eluded me pertained to iteration over a Collection of objects.  For and While loops are perfectly capable of iterating through any collection in Groovy; however, if you want a simpler (and often more efficient) way, you should learn how and when to use five methods for the Collection class (this tutorial assumes knowledge of closures):

  • each – This method can be used to iterate through every object in a collection, and perform an operation or series of operations on that object.
  • findAll – The findAll method iterates through each object in a collection, but cannot act on them. Instead, it only returns objects that meet the condition in the closure.
  • find – The find method works exactly like findAll, except it only returns the first value that meets the criteria in the closure.
  • collect – This method works similarly to the each method, except every value the method iterates over is instantiated as a NEW object; thus, modifications will not affect the original collection as they would if the each method were used
  • inject – The most valuable of these methods, inject method can be used to list complex objects of a collection, either starting at the beginning of the collection or at a specified starting point. In the closure, you pass in either two or three variables. The first variable will always represent the object being returned; the second represents the object being iterated over in the collection.  If you choose to add the third variable, it will represent the value of the object being iterated over, given it is a key-value pair.

For example, consider the simple class Car:


class Car {

    def wheelSize
    def color
    def name
    def rating
    def notes
}

I will go ahead and make a collection of Car objects as follows:


<span style="color: #808080;">
def myCollectionOfCars = [new Car([wheelSize: 16, color: 'blue',</span></pre>
<span style="color: #808080;">                                    name: '2001 Celica', rating: 9.5, notes: 'Attracts cops']),</span>
<span style="color: #808080;">                           new Car([wheelSize: 18, color: 'silver',</span>
<span style="color: #808080;">                                    name: '1995 NSX', rating: 9.7, notes: 'Handles heavy']),</span>
<span style="color: #808080;">                           new Car([wheelSize: 17, color: 'pale gold',</span>
<span style="color: #808080;">                                    name: '1998 SL320', rating: 9.1, notes: 'Terrible in snow']),</span>
<span style="color: #808080;">                           new Car([wheelSize: 15, color: 'black',</span>
<span style="color: #808080;">                                    name: '2015 Evoque', rating: 8.8, notes: 'Off-road capable'])]


The following example of the each method illustrates that the method iterates through every object in the collection and makes the modification defined in the closure:


<span style="color: #808080;">
myCollectionOfCars.each { car -></span>
<span style="color: #808080;">     car.notes = "Hi and hello, auto fans!"</span>
<span style="color: #808080;"> }</span>
<span style="color: #808080;">assert(myCollectionOfCars[0].notes = "Hi and hello, auto fans!" </span>
<span style="color: #808080;">assert(myCollectionOfCars[1].notes = "Hi and hello, auto fans!" </span>
<span style="color: #808080;">assert(myCollectionOfCars[2].notes = "Hi and hello, auto fans!" </span>
<span style="color: #808080;">assert(myCollectionOfCars[3].notes = "Hi and hello, auto fans!"


 A visual representation of the result:

Screen Shot 2016-02-12 at 9.59.59 AM
The big takeaway here is that using the each method, I replaced the “notes” attribute of each object with the message defined in my closure. Remember, unlike collect, each does NOT return a new object. 


The next method, findAll, iterates through every object in my condition similarly to the each method, but only returns the objects that meet the criteria within the closure! For example:


<span style="color: #808080;">
def offRoadCapableCars = myCollectionOfCars.findAll { car -></span>
<span style="color: #808080;">        car.notes.contains("Off-road")
</span><span style="color: #808080;">}</span>
<span style="color: #808080;">assert(offRoadCapableCars.size() == 1)</span></pre>

returns one object:

Screen Shot 2016-02-12 at 10.06.22 AM


However, it is important to remember that findAll returns every object that meets the criteria, not just the first one, as illustrated with this example:


</pre>
<span style="color: #808080; line-height: 1.7;">def ratingAbove9 = myCollectionOfCars.find { car -></span>

<span style="color: #808080;">     car.rating > 9</span>
<span style="color: #808080;"> }
assert(ratingAbove9.size() == 3)


returns:

Screen Shot 2016-02-12 at 10.09.23 AM

While a find method with the same condition only returns the first object:


</pre>
<span style="color: #808080; line-height: 1.7;">def ratingAbove9 = myCollectionOfCars.find { car -></span>

<span style="color: #808080;">     car.rating > 9</span>
<span style="color: #808080;"> }
assert(ratingAbove9.size() == 1)


Returns:

Screen Shot 2016-02-12 at 10.08.13 AM

thus, while findAll always returns a collection (even if the collection has only one object in it), find always returns a single object.


Next, we turn our attention to the collect method. As we discussed above, the collect method iterates through every object in the collection and returns new instances of whichever objects are modified in the closure condition:


</pre>
<span style="color: #808080;">def myUpdatedCollectionOfCarRatings = myCollectionOfCars.collect { car -></span>

<span style="color: #808080;"><span style="line-height: 1.7;">    if(car.rating > 9)
</span>        car.rating = 10</span>
<span style="color: #808080;">     else</span>
<span style="color: #808080;">         car.rating = 0</span>
<span style="color: #808080;"> }</span>
<span style="color: #808080;"> assert(myUpdatedCollectionOfCarRatings[0] == 10)</span>
<span style="color: #808080;"> assert(myUpdatedCollectionOfCarRatings[1] == 10)</span>
<span style="color: #808080;"> assert(myUpdatedCollectionOfCarRatings[2] == 10)</span>
<span style="color: #808080;"> assert(myUpdatedCollectionOfCarRatings[3] == 0)


returns new objects containing only the new ratings assigned in the closure:

Screen Shot 2016-02-12 at 10.13.55 AM

The major similarity between collect and each is that they both apply the closure expression on every single object in the collection; the difference is that while each modifies the original collection, collect returns a new collection of only the modified attributes, with the reflected modifications from the closure applied.  Note that modification of the attribute(s) is not necessary, and collect can simply be used to pull the attributes into a new collection. To do this, name the attribute(s) you want added to your new collection in the closure, but do not add a modifying statement:


<span style="color: #808080;">
def myUpdatedCollectionOfCarRatings = myCollectionOfCars.collect { car ->
</span>    <span style="color: #808080;">car.rating</span>
<span style="color: #808080;">}</span>
<span style="color: #808080;">assert(myUpdatedCollectionOfCarRatings[0] == 9.5)</span>
<span style="color: #808080;">assert(myUpdatedCollectionOfCarRatings[1] == 9.7)</span>
<span style="color: #808080;">assert(myUpdatedCollectionOfCarRatings[2] == 9.1)</span>
<span style="color: #808080;">assert(myUpdatedCollectionOfCarRatings[3] == 8.8)</span></pre>

To get the result:

Screen Shot 2016-02-13 at 12.56.03 PM


 

Finally, we reach the inject method.  Remember, there are two params: the optional “starting point” parameter, and the closure. Since our collection is so small, we will enter a blank first parameter, meaning the method will start iterating at the beginning of our collection.  Since our object is not a map, we will have two values in our closure: carRatingTotal, and eachCar. The purpose of this method will be to construct an arrayList that will use the variable count to increment elements, and will concatenate the rating and color of each car as the value of each corresponding element.


<span style="color: #808080;">
int count = 0</span></pre>
<span style="color: #808080;"> def carRatingTotal = myCollectionOfCars.inject([]){ carRatingTotal, eachCar -></span>
<span style="color: #808080;">     carRatingTotal[count] = eachCar.rating.toString() + " " + eachCar.color</span>
<span style="color: #808080;">     count++</span>
<span style="color: #808080;">     carRatingTotal</span>
<span style="color: #808080;"> }
assert(carRatingTotal[0] == ("9.5 blue")
assert(carRatingTotal[1] == ("9.7 silver")
assert(carRatingTotal[2] == ("9.1 pale gold")
assert(carRatingTotal[3] == ("8.8 black")


There are a few things to note inside the closure:

  • The variables, carRatingTotal and eachCar. As you may remember, we discussed that the two values in the inject closure represent the collection being created and the object being iterated over, respectively.
  • After the arrow, comes the action statement. We set an array, carRatingTotal, to equal the concatenation of each rating and corresponding color.
  • Count is used to increment the element in the arrayList. In real business examples, this is often done using the id of the object.
  • Lastly, we return the collection we have created

Screen Shot 2016-02-12 at 12.30.39 PM

 

Leave a comment