Wednesday, August 9, 2017

Java 8 - Collectors

Collectors

One of the main advantages of functional-style programming over an imperative approach:
you just have to formulate the result you want to obtain the “what” and not the steps you need to perform to obtain it—the “how.”

Collectors can be seen as advanced reductions.

Factory methods provided by the Collectors class offer three main functionalities:

  • Reducing and summarizing stream elements to a single value
  • Grouping elements
  • Partitioning elements

Examples

// count the number of dishes in the menu, using the collector returned by the counting factory method
long howManyDishes = menu.stream().collect(Collectors.counting());
// you can write this far more directly as
long howManyDishes = menu.stream().count();

// Calculate the average value of an Integer property of the items in the stream.
double avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));

// get the count, sum, average, maximum, and minimum of the calories contained in each dish with a single summarizing operation:
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));

// joining internally makes use of a StringBuilder to append the generated strings into one.
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));

// maxBy - An Optional wrapping the maximal element in this stream according to the given comparator or Optional.empty() if the stream is empty.
Optional<Dish> mostCalorieDish  = menu.stream().collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));

// reducing - Reduce the stream to a single value starting from an initial value used as accumulator and iteratively combining it with each item of the stream using a BinaryOperator.
// using Collectors.reducing to get maximum calorie value
Optional<Dish> mostCalorieDish2 = menu.stream().collect(Collectors.reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
Integer mostCalorieValue = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, Integer::max));

Stream.reduce method is meant to combine two values and produce a new one; it’s an immutable reduction.
Stream.collect method is designed to mutate a container to accumulate the result it’s supposed to produce.

groupingBy - Group the items in the stream based on the value of one of their properties and use those values as keys in the resulting Map.
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
You pass to the groupingBy method a Function (expressed in the form of a method reference) extracting the corresponding Dish.Type for each Dish in the stream. We call this Function a classification function because it’s used to classify the elements of the stream into different groups.

Map<Dish.Type, Long> typesCount = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
The result can be the following Map: {MEAT=2, FISH=4, OTHER=3}

collectingAndThen - Wrap another collector and apply a transformation function to its result.
int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));

Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(
Collectors.groupingBy(Dish::getType,
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
There cannot be empty optional as value - key will not be present!
We can use Collectors.collectingAndThen factory method to get Dish instead Optional<Dish>
Map<Dish.Type, Dish> mostCaloricDishesByTypeWithoutOptionals = menu.stream().collect(
Collectors.groupingBy(Dish::getType,
        Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
                        Optional::get)));
// using Optional.get is safe because the reducing collector will never return an Optional.empty()

Partitioning

Partitioning is a special case of grouping: having a predicate (a function returning a boolean), called a partitioning function, as a classification function. The fact that the partitioning function returns a boolean means the resulting grouping Map will have a Boolean as a key type and therefore there can be at most two different groups—one for true and one for false.

Map<Boolean, List<Dish>> partitionByVegeterian = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
same functionality you can get with:
List<Dish> vegetarianDishes = menu.stream().filter(Dish::isVegetarian).collect(Collectors.toCollection(ArrayList::new));

Summary


  • Collect is a terminal operation that takes as argument various recipes (called collectors) for accumulating the elements of a stream into a summary result.
  • Predefined collectors include reducing and summarizing stream elements into a single value, such as calculating the minimum, maximum, or average.
  • Predefined collectors let you group elements of a stream with groupingBy and partition elements of a stream with partitioningBy.
  • Collectors compose effectively to create multilevel groupings, partitions, and reductions.
  • You can develop your own collectors by implementing the methods defined in the Collector interface.

Ref

No comments:

Post a Comment