//多级分组 //---------------------------------- //MEAT // FAT // [Test.Dish(name=pork, vegetarain=false, colories=800, type=MEAT)] // DIET // [Test.Dish(name=chicken, vegetarain=false, colories=400, type=MEAT)] // NORMAL // [Test.Dish(name=beef, vegetarain=false, colories=700, type=MEAT)] //---------------------------------- //FISH // DIET // [Test.Dish(name=prawns, vegetarain=false, colories=300, type=FISH)] // NORMAL // [Test.Dish(name=salmon, vegetarain=false, colories=450, type=FISH)] //---------------------------------- //OTHER // DIET // [Test.Dish(name=rice, vegetarain=true, colories=350, type=OTHER), Test.Dish(name=season fruit, vegetarain=true, colories=120, type=OTHER)] // NORMAL // [Test.Dish(name=french fries, vegetarain=true, colories=530, type=OTHER), Test.Dish(name=pizza, vegetarain=true, colories=550, type=OTHER)] //总结:groupingBy的核心参数为K生成器,V生成器。V生成器可以是任意类型的收集器Collector。 public static void group2() { Map<Type, Map<CaloricLevel, List<Dish>>> byTypeAndCalory = dishes.stream().collect( groupingBy(Dish::getType, groupingBy(d -> { /** 此处写if的时候注意要顾及到所有的情况 */ if (d.getColories() <= 400) { return CaloricLevel.DIET; } else if (d.getColories() <= 700) { return CaloricLevel.NORMAL; } else { return CaloricLevel.FAT; } } ) ) ); byTypeAndCalory.forEach((type, byCalory) -> { System.out.println("----------------------------------"); System.out.println(type); byCalory.forEach((level, dishList) -> { System.out.println("\t" + level); System.out.println("\t\t" + dishList); }); }); }
总结:groupingBy的核心参数为K生成器,V生成器。V生成器可以是任意类型的收集器Collector。 比如,V生成器可以是计算数目的, 从而实现了sql语句中的select count(*) from table A group by Type Map<Type, Long> typesCount = dishes.stream().collect(groupingBy(Dish::getType, counting())); System.out.println(typesCount); ----------- {FISH=2, MEAT=3, OTHER=4}
sql查找分组最高分select MAX(id) from table A group by Type Map<Type, Optional<Dish>> mostCaloricByType = dishes.stream() .collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));
这里的Optional没有意义,因为肯定不是null。那么只好取出来了。使用collectingAndThen Map<Type, Dish> mostCaloricByType = dishes.stream() .collect(groupingBy(Dish::getType, collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
到这里似乎结果出来了,但IDEA不同意,编译黄色报警,按提示修改后变为: Map<Type, Dish> mostCaloricByType = dishes.stream() .collect(toMap(Dish::getType, Function.identity(), BinaryOperator.maxBy(comparingInt(Dish::getCalories)))); 是的,groupingBy就变成toMap了,key还是Type,value还是Dish,但多了一个参数!!这里回应开头的坑,开头的toMap演示是为了容易理解,真那么用则会被搞死。
我们知道把一个List重组为Map必然会面临k相同的问题。当K相同时,v是覆盖还是不管呢?前面的demo的做法是当k存在时,再次插入k则直接抛出异常: java.lang.IllegalStateException: Duplicate key Dish(name=pork, vegetarian=false, calories=800, type=MEAT) at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) 正确的做法是提供处理冲突的函数,在本demo中,处理冲突的原则就是找出最大的,正好符合我们分组求最大的要求。(真的不想搞Java8函数式学习了,感觉到处都是性能问题的坑)
继续数据库sql映射,分组求和select sum(score) from table a group by Type Map<Type, Integer> totalCaloriesByType = dishes.stream() .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories))); 然而常常和groupingBy联合使用的另一个收集器是mapping方法生成的。这个方法接收两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来。其目的是在累加之前对每个输入元素应用一个映射函数,这样就可以让接收特定类型元素的收集器适应不同类型的对象。我么来看一个使用这个收集器的实际例子。比如你想得到,对于每种类型的Dish,菜单中都有哪些CaloricLevel。我们可以把groupingBy和mapping收集器结合起来,如下所示: Map<Type, Set<CaloricLevel>> caloricLevelsByType = dishes.stream() .collect(groupingBy(Dish::getType, mapping(this::getCaloricLevel, toSet()))); 这里的toSet默认采用的HashSet,也可以手动指定具体实现toCollection(HashSet::new)
https://yq.aliyun.com/articles/610729?spm=a2c4e.11153940.0.0.6a255562myIiAj
分区
看一个更加复杂也更为有趣的例子:将数组分为质数和非质数。 首先,定义个质数分区函数: private boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0); } 然后找出1到100的质数和非质数 Map<Boolean, List<Integer>> partitionPrimes = IntStream.rangeClosed(2, 100).boxed() .collect(partitioningBy(this::isPrime));
例如,如果你是素食者,你可能想要把菜单按照素食和非素食分开: Map<Boolean, List<Dish>> partitionedMenu = dishes.stream().collect(partitioningBy(Dish::isVegetarian)); 当然,使用filter可以达到同样的效果: List<Dish> vegetarianDishes = dishes.stream().filter(Dish::isVegetarian).collect(Collectors.toList()); 分区相对来说,优势就是保存了两个副本,当你想要对一个list分类时挺有用的。同时,和groupingBy一样,partitioningBy一样有重载方法,可以指定分组value的类型。 Map<Boolean, Map<Type, List<Dish>>> vegetarianDishesByType = dishes.stream() .collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType))); Map<Boolean, Integer> vegetarianDishesTotalCalories = dishes.stream() .collect(partitioningBy(Dish::isVegetarian, summingInt(Dish::getCalories))); Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = dishes.stream() .collect(partitioningBy(Dish::isVegetarian, collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));
//使用skip()和limit()进行分页 public static void pages() { int pageSize = 2; int pageIndex = 2; List<Integer> expected = Lists.newArrayList(61, 62, 63, 64, 65, 66, 67, 68, 69, 70); List<Integer> result = Stream.iterate(1, i -> i + 1) .skip((pageIndex - 1) * pageSize) .limit(pageSize) .collect(toList()); System.out.println(result); // assertEquals(expected, result); }
基础类 public enum CaloricLevel { DIET, NORMAL, FAT } public enum Type { MEAT, FISH, OTHER, } static List<Dish> dishes; static { dishes = Lists.newArrayList( new Dish("pork", false, 800, Type.MEAT), new Dish("beef", false, 700, Type.MEAT), new Dish("chicken", false, 400, Type.MEAT), new Dish("french fries", true, 530, Type.OTHER), new Dish("rice", true, 350, Type.OTHER), new Dish("season fruit", true, 120, Type.OTHER), new Dish("pizza", true, 550, Type.OTHER), new Dish("prawns", false, 300, Type.FISH), new Dish("salmon", false, 450, Type.FISH) ); } @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode @ToString public static class Dish { private String name; ///** 名字 */ private boolean vegetarain;///** 是否素食 */ private int colories;///** 卡路里 */ private Type type;///** 类型 */ }