First8 15 years

First8 15 years

This week my employer First8 celebrated 15 years of doing open-source Java. I’m proud to be already 11 years part of it. Hurray! To the next 15 years!

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.

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

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.
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 as map and reduce
  • The argument to the map operation is a method reference to the getPrice() method of the currently iterated animal. We could also replace this part with the expression a -> a.getPrice()
  • reduce is a general reduction operation (also called a fold) in which the BigDecimals of the prices are added up. This is also giving us an Optional 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

def animalsByFarmer = animals.groupBy { it.farmer }
// [john:[Buttercup], dick:[Carmella, Cinnamon]]
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

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 the k, 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 call sum().
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.