Data collection is simplified in Java 8 syntax very much. you only need to point out "the expected result " not to worry about how to collect. In Java 8 , there exist a factory class "
Collectors", let't see how it applies.
Math Problems
long howManyDishes = menu.stream().collect(Collectors.counting()); // ==
long howManyDishes2 = menu.stream().count();
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream()
.collect(Collectors.maxBy(dishCaloriesComparator)); // get object with max calories
// get the total calories
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
// get the average calories
double avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
// get total, average, max, min
IntSummaryStatistics menuStatistics = menu.stream().collect(
Collectors.summarizingInt(Dish::getCalories));
String Concatenation
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));
Reduce
// get total calories in reduing way
int totalCalories2 = menu.stream().collect(Collectors.reducing(0, Dish::getCalories,
(i, j) -> i + j));
// get the max calories
Optional<Dish> mostCalorieDish2 = menu.stream().collect(Collectors.reducing( (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
// self-implement toList collector
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> numbers = stream.reduce(new ArrayList<Integer>(), // init value
(List<Integer> l, Integer e) -> { l.add(e);return l; }, // process
(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);return l1; }); // final process
// total calories
int totalCalories3 = menu.stream().collect(Collectors.reducing(0,Dish::getCalories,
Integer::sum));
int totalCalories4 = menu.stream().mapToInt(Dish::getCalories).sum(); // old way
Group By
Group by is like SQL group by statement., you can do nexted grouping , criteria grouping ...
// group by type , type is an enum
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(
Collectors.groupingBy(Dish::getType));
// criteria grouping
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if (dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
}else {
return CaloricLevel.FAT;
}}));
// nested grouping
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
Collectors.groupingBy(Dish::getType, // 1st grouping
Collectors.groupingBy(dish -> { // 2ed grouping
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
})));
// collecting data with grouping
// result is : {MEAT=3, FISH=2, OTHER=4}
Map<Dish.Type, Long> typesCount = menu.stream().collect(
Collectors.groupingBy(Dish::getType, Collectors.counting()));
// get the max calories of each group , group by type
//result is : {FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(
Collectors.groupingBy(Dish::getType,
Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
// get the max colories of each group , retrive value of each optional from above
//result is {FISH=salmon, OTHER=pizza, MEAT=pork}
Map<Dish.Type, Dish> mostCaloricByType2 = menu.stream().collect(
Collectors.groupingBy(Dish::getType, // group by type
Collectors.collectingAndThen(Collectors.maxBy(
Comparator.comparingInt(Dish::getCalories)), //transformed Collector
Optional::get))); // function of transfering
// grouping by can pass in 2 params , the second param will apply to all elements of each group.
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(
Collectors.groupingBy(Dish::getType, Collectors.summingInt(Dish::getCalories)));
// get all caloricLevels of each Dish Type
// Collectors.mapping is powerful like map()
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(
Collectors.groupingBy(Dish::getType, Collectors.mapping( dish -> {
if (dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if (dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else {
return CaloricLevel.FAT;
}},Collectors.toSet())));
// point out which kind of set you want to use
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType2 = menu.stream().collect(
Collectors.groupingBy(Dish::getType, Collectors.mapping( dish -> {
if (dish.getCalories() <= 400) {
return CaloricLevel.DIET;
} else if (dish.getCalories() <= 700) {
return CaloricLevel.NORMAL;
} else {
return CaloricLevel.FAT;
}},Collectors.toCollection(HashSet::new))));
Partition By
Partion By is similar to Group by but the input parameter is limited to "Predicate".
//{false=[pork, beef, chicken, prawns, salmon],
//true=[french fries, rice, season fruit, pizza]}
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
List<Dish> vegetarianDishes = partitionedMenu.get(true); // to retrive values
// ==
List<Dish> vegetarianDishes2 = menu.stream().filter(Dish::isVegetarian).collect(
Collectors.toList());
// nexted partioning & grouping , producing a 2 level grouping
//{false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
// true={OTHER=[french fries, rice, season fruit, pizza]}}
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect( Collectors.partitioningBy(Dish::isVegetarian,
Collectors.groupingBy(Dish::getType)));
// getType is not predicate so use group by