Java 8 Lambdas vs Groovy Closures Compactness: Grouping And Summing
Java 8 is featuring lambdas, which are similar to a construction Groovy has already for some time: closures.
In Groovy we could already do this:
def list = ['a', 'b', 'c'] print list.collect { it.toUpperCase() } // [A, B, C]
where { it.toUpperCase() }
is the closure.
In Java 8 we can achieve the same functionality now in a concise way.
list.stream().map( s -> s.toUpperCase() )
Although you could argue that with proper use of the new Stream API, bulk operations and method references, at least the intent of a piece of code is conveyed more clearly now – Java’s verboseness can still cause sore eyes.
Here are some other examples.
Some Groovy animals
class Animal { String name BigDecimal price String farmer String toString() { name } } def animals = [] animals << new Animal(name: "Buttercup", price: 2, farmer: "john") animals << new Animal(name: "Carmella", price: 5, farmer: "dick") animals << new Animal(name: "Cinnamon", price: 2, farmer: "dick")
Example 1: Summing the total price of all animals
Groovy
assert 9 == animals.sum { it.price } // or animals.price.sum()
What Groovy you see here:
sum
can be called on a List and optionally passed a closure defining the property of “it” – the animal being iterated over – to sort on.- or
sum
can be called on a List without any arguments, which is equivalent to invoking the “plus” method on all items in the collection.
Java 8
Optional<BigDecimal> sum = animals. stream(). map(Animal::getPrice). reduce((l, r) -> l.add(r)); assert BigDecimal.valueOf(9) == sum.get();
What Java you see here:
- Through the Stream API’s
stream
method we can create a pipeline of operations, such asmap
andreduce
- The argument to the
map
operation is a method reference to thegetPrice()
method of the currently iterated animal. We could also replace this part with the expressiona -> a.getPrice()
reduce
is a general reduction operation (also called a fold) in which theBigDecimals
of the prices are added up. This is also giving us anOptional
with the total sum.- BTW, if we were to use a double for price – which we don’t because I want to give a good example – we could have used an existing DoubleStream with a
sum()
on it e.g.double sum = animals.stream().mapToDouble(Animal::getPrice).sum();
Example 2: Grouping all animals by farmer
Groovy
def animalsByFarmer = animals.groupBy { it.farmer } // [john:[Buttercup], dick:[Carmella, Cinnamon]]
Java 8
Map<String, List<Animal>> animalsByFarmer = animals .stream() .collect( Collectors.groupingBy(Animal::getFarmer)); // {dick=[Carmella, Cinnamon], john=[Buttercup]}
Example 3: Summing the total price of all animals grouped by farmer
Groovy
def totalPriceByFarmer = animals .groupBy { it.farmer } .collectEntries { k, v -> [k, v.price.sum()] } // [john:2, dick:7]
What Groovy you see here:
collectEntries
iterates through the “groupBy” map transforming each map entry using thek, v -> ...
closure returning a map of the transformed entries.v.price
is actually a List of prices (per farmer) – such as in example 1 – on which we can callsum()
.
Java 8
Map<String, BigDecimal> totalPriceByFarmer = animals .stream() .collect( Collectors.groupingBy( Animal::getFarmer, Collectors.reducing( BigDecimal.ZERO, Animal::getPrice, BigDecimal::add))); // {dick=7, john=2}
This Java code again yields the same results. Since IDE’s, Eclipse at least, don’t format this properly, you’ll have to indent these kinds of constructions for readability a bit yourself.
Reference: | Java 8 Lambdas vs Groovy Closures Compactness: Grouping And Summing from our JCG partner Ted Vinke at the Ted Vinke’s Blog blog. |