Статьи

Компактность Java 8 Lambdas и Groovy Closures: группировка и суммирование

Java 8 имеет лямбды , которые похожи на конструкцию, которую Groovy уже некоторое время использует: замыкания .

В Groovy мы уже могли сделать это:

1
2
3
def list = ['a', 'b', 'c']
print list.collect { it.toUpperCase() }
// [A, B, C]

где { it.toUpperCase() } — это закрытие.

В Java 8 мы можем достичь той же функциональности теперь в сжатой форме.

1
list.stream().map( s -> s.toUpperCase() )

Хотя вы могли бы поспорить, что при правильном использовании нового Stream API, массовых операций и ссылок на методы, по крайней мере, теперь смысл кода передается более четко — многословность Java все еще может вызывать воспаление.

DukeGroovyChair

Вот еще несколько примеров.

Некоторые заводные животные

01
02
03
04
05
06
07
08
09
10
11
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")

Пример 1: Суммирование общей стоимости всех животных

Groovy

1
2
assert 9 == animals.sum { it.price }
// or animals.price.sum()

Что Groovy вы видите здесь:

  • sum можно вызвать в List и, необязательно, передать замыкание, определяющее свойство «it» — итерируемого животного — для сортировки.
  • или sum можно вызывать в List без каких-либо аргументов, что эквивалентно вызову метода « plus » для всех элементов в коллекции.

Java 8

1
2
3
4
5
6
Optional<BigDecimal> sum =
    animals.
        stream().
        map(Animal::getPrice).
        reduce((l, r) -> l.add(r));
assert BigDecimal.valueOf(9) == sum.get();

Какую Java вы видите здесь:

  • С помощью метода Stream API stream мы можем создать конвейер операций, таких как map и reduce
  • Аргументом операции map является ссылка на метод getPrice() текущего итерированного животного. Мы могли бы также заменить эту часть выражением a -> a.getPrice()
  • reduce — это общая операция сокращения (также называемая сгибанием ), в которой суммируются BigDecimals значения цен. Это также дает нам Optional с общей суммой.
  • Кстати, если бы мы использовали удвоение цены — что мы не делаем, потому что я хочу привести хороший пример — мы могли бы использовать существующий DoubleStream с sum() на нем, например
    1
    double sum = animals.stream().mapToDouble(Animal::getPrice).sum();

Пример 2: Группировка всех животных по фермеру

Groovy

1
2
def animalsByFarmer = animals.groupBy { it.farmer }
// [john:[Buttercup], dick:[Carmella, Cinnamon]]

Java 8

1
2
3
4
5
6
Map<String, List<Animal>> animalsByFarmer =
    animals
        .stream()
        .collect(
            Collectors.groupingBy(Animal::getFarmer));
// {dick=[Carmella, Cinnamon], john=[Buttercup]}

Пример 3: Суммирование общей стоимости всех животных, сгруппированных по фермеру

Groovy

1
2
3
4
5
def totalPriceByFarmer =
    animals
        .groupBy { it.farmer }
        .collectEntries { k, v -> [k, v.price.sum()] }
// [john:2, dick:7]

Что Groovy вы видите здесь:

  • collectEntries перебирает карту «groupBy», преобразуя каждую запись карты, используя закрытие k, v -> ... возвращая карту преобразованных записей. v.price самом деле v.price — это список цен (на одного фермера), например, в примере 1, по которому мы можем вызвать sum() .

Java 8

01
02
03
04
05
06
07
08
09
10
11
Map<String, BigDecimal> totalPriceByFarmer =
    animals
        .stream()
        .collect(
            Collectors.groupingBy(
                Animal::getFarmer,
                Collectors.reducing(
                    BigDecimal.ZERO,
                    Animal::getPrice,
                    BigDecimal::add)));
// {dick=7, john=2}

Этот код Java снова дает те же результаты. Поскольку IDE, по крайней мере, Eclipse, не форматируют это должным образом, вам придется самим создавать отступы для такого рода конструкций для удобства чтения.