区分Collection,Collector和collectjava
代码中用到的类与方法用红框标出,可从git库中查看git
// 按货币对交易进行分组 Map<Currency, List<Transaction>> currencyListMap = getTransactions().stream() .collect(groupingBy(Transaction::getCurrency)); for (Map.Entry<Currency, List<Transaction>> entry : currencyListMap.entrySet()) { System.out.println(entry.getKey() + "\t" + entry.getValue().size()); }
将流元素归约和汇总为一个值安全
元素分组app
元素分区,分组的特殊状况,使用谓词做为分组函数(谓词,返回boolean类型的函数)ide
// import static java.util.stream.Collectors.*; Stream<Dish> menuStream = getMenu().stream(); // Collectors类的静态工厂方法 List<Dish> dishes1 = menuStream.collect(toList()); Set<Dish> dishes2 = menuStream.collect(toSet()); Collection<Dish> dishes3 = menuStream.collect(toCollection(ArrayList::new)); long howManyDishes = menuStream.collect(counting()); int totalCalories = menuStream.collect(summingInt(Dish::getCalories)); double avgCalories = menuStream.collect(averagingInt(Dish::getCalories)); IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories)); String shortMenu = menuStream.map(Dish::getName).collect(joining(", ")); Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories))); Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories))); int totalCalories2 = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum)); int howManyDishes2 = menuStream.collect(collectingAndThen(toList(), List::size)); Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType)); Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian));
汇老是归约的一种特殊状况函数
菜单中有多少种菜性能
// 菜单里有多少种菜 long howManyDishes = getMenu().stream().collect(Collectors.counting()); System.out.println(howManyDishes); // 8 long howManyDishes2 = getMenu().stream().count(); System.out.println(howManyDishes2); // 8 System.out.println(getMenu().size()); // 8,这样不是更简单??
最大值,最小值和平均值测试
// 菜单中热量最高的菜 Optional<Dish> mostCalaorieDish = getMenu().stream().collect(maxBy(comparingInt(Dish::getCalories))); System.out.println(mostCalaorieDish.orElse(null)); // pork // 菜单中热量最低的菜 Optional<Dish> leastCalaorieDish = getMenu().stream().collect(minBy(comparingInt(Dish::getCalories))); System.out.println(leastCalaorieDish.orElse(null)); //season // 菜单中总热量 int totalCalories = getMenu().stream().collect(summingInt(Dish::getCalories)); System.out.println(totalCalories); // 3850 // 菜单中的平均热量 OptionalDouble averageCalories = getMenu().stream().mapToDouble(Dish::getCalories).average(); System.out.println(averageCalories.orElse(0d)); // 481.25
一个综合的方法:求count,sum,min,average,max优化
// 以上汇总数据可用下面一个方法执行 IntSummaryStatistics menuStatistics = getMenu().stream().collect(summarizingInt(Dish::getCalories)); System.out.println(menuStatistics); // IntSummaryStatistics{count=8, sum=3850, min=120, average=481.250000, max=800}
// 链接字符串 String shortMenu = getMenu().stream() .map(Dish::getName) // 省略这步,返回Dish的toString .collect(joining()); System.out.println(shortMenu); // porkchickenfrench friesriceseasonpizzaprawnssalmon // 逗号分隔 String shortMenu2 = getMenu().stream() .map(Dish::getName) .collect(joining(", ")); System.out.println(shortMenu2); // pork, chicken, french fries, rice, season, pizza, prawns, salmon
全部收集器,都是一个能够用reducing工厂方法定义的归约过程的特殊状况而已。 Collectors.reducing工厂方法是全部这些特殊状况的通常化。线程
// Collectors.reducing() 是以上状况的通常化 // 菜单中总热量 int totalCalories2 = getMenu().stream() .collect(reducing(0, // 第一个参数:初始值 Dish::getCalories, // 第二个参数:转换函数,要被操做的值 (i, j) -> i + j)); // 第三个参数:累积函数,求和代码 System.out.println(totalCalories2); // 3850 // 菜单中热量最高的菜 Optional<Dish> mostCaloriesDish = getMenu().stream() .collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)); System.out.println(mostCalaorieDish.orElse(null)); // pork // collect与reduce int totalCalories3 = getMenu().stream() .map(Dish::getCalories) .reduce(Integer::sum) .get(); System.out.println(totalCalories3);
按类型对菜肴进行分组
// 按类型分组 Map<Dish.Type, List<Dish>> typeMap = getMenu().stream() .collect(groupingBy(Dish::getType)); System.out.println(typeMap); // {OTHER=[rice, season, pizza], FISH=[prawns, salmon], MEAT=[pork, chicken, french fries]} // 按热量分组 Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = getMenu().stream() .collect(groupingBy(Dish::getCaloricLevel)); System.out.println(dishesByCaloricLevel); // {DIET=[french fries, season, prawns], FAT=[pork], NORMAL=[chicken, rice, pizza, salmon]}
先按类型分,再按热量分
// 先按类型分,再按热量分 Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloriclevel = getMenu().stream() .collect(groupingBy(Dish::getType, groupingBy(Dish::getCaloricLevel))); System.out.println(dishesByTypeCaloriclevel); // {OTHER={DIET=[season], NORMAL=[rice, pizza]}, // FISH={DIET=[prawns], NORMAL=[salmon]}, // MEAT={DIET=[french fries], FAT=[pork], NORMAL=[chicken]}}
按子组收集数据
// 每种类型的菜有多少个 Map<Dish.Type, Long> typesCount = getMenu().stream() .collect(groupingBy(Dish::getType, counting())); System.out.println(typesCount); // {OTHER=3, FISH=2, MEAT=3} // 注意:groupingBy(f) 等价于 groupingBy(f, toList())
把收集器的结果转换为另外一种类型
// 每种类型的中最高热量的那个菜 Map<Dish.Type, Optional<Dish>> mostCaloricByType = getMenu().stream() .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories)))); System.out.println(mostCaloricByType); // {OTHER=Optional[pizza], FISH=Optional[salmon], MEAT=Optional[pork]} // 把收集器的结果转换为另外一种类型 // 每种类型的中最高热量的那个菜 Map<Dish.Type, Dish> mostCaloricByType2 = getMenu().stream() .collect(groupingBy(Dish::getType, // 分类函数 collectingAndThen( // 这是一个收集器 maxBy(comparingInt(Dish::getCalories)), // 要转换的收集器 Optional::get))); // 转换函数
与groupingBy联合使用的其余收集器的例子
// 与groupingBy联合使用的其余收集器的例子 // 每种类型的总热量 Map<Dish.Type, Integer> totalCaloriesByType = getMenu().stream() .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories))); System.out.println(totalCaloriesByType); // 每种类型有哪些热量类型 // 使用toSet() Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = getMenu().stream() .collect(groupingBy(Dish::getType, mapping( // 在累加前对每一个输入元素应用一个映射函数,这样就可让接受特定类型元素的收集器适用不一样类型的对象 Dish::getCaloricLevel, // 对流中的元素作变换 toSet()))); // 将变换的结果对象收集起来 System.out.println(caloricLevelsByType); // {FISH=[NORMAL, DIET], MEAT=[FAT, NORMAL, DIET], OTHER=[NORMAL, DIET]} // 使用toCollection(HashSet::new) Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType2 = getMenu().stream() .collect(groupingBy(Dish::getType, mapping(Dish::getCaloricLevel, toCollection(HashSet::new)))); System.out.println(caloricLevelsByType2); // {FISH=[NORMAL, DIET], MEAT=[FAT, NORMAL, DIET], OTHER=[NORMAL, DIET]}
分区是分组的特殊状况:由一个谓词(返回一个布尔值的函数)做为分类函数,它称为分区函数。
// 区分素食与非素食 Map<Boolean, List<Dish>> partitionedMenu = getMenu().stream() .collect(partitioningBy(Dish::isVegetarian)); System.out.println(partitionedMenu); // {false=[pork, chicken, french fries, prawns, salmon], true=[rice, season, pizza]} // 区分素食与非素食,再按类型分类 Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = getMenu().stream() .collect(partitioningBy(Dish::isVegetarian, // 分区函数 groupingBy(Dish::getType))); // 收集器 // 素食与非素食中热量最高的菜 Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = getMenu().stream() .collect(partitioningBy(Dish::isVegetarian, collectingAndThen( maxBy(comparing(Dish::getCalories)), Optional::get))); System.out.println(mostCaloricPartitionedByVegetarian); // {false=pork, true=pizza}
判断质数
// 质数 public boolean isPrime(int candidate) { return IntStream.range(2, candidate) .noneMatch(i -> candidate % i == 0); } // 优化,仅测试小于等于待测试数平方根的因子(限制除数不超过被测试数的平方根) public boolean isPrime2(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); }
将数字按质数和非质数分区
// 将数字按质数和非质数分区 public Map<Boolean, List<Integer>> partitionPrimes(int n) { return IntStream.rangeClosed(2, n).boxed() .collect(partitioningBy(candidate -> isPrime2(candidate))); }
/** * 将Stream<T>中的全部元素收集到一个List<T>里 * Author: admin * Date: 2018/8/15 15:03 */ public class ToListCollector<T> implements Collector<T, List<T>, List<T>> { // T是流中要收集的项目的泛型 // A是累加器的类型,累加器是在收集过程当中用于累积部分结果的对象。 // R是收集操做获得的对象(一般但并不必定是集合)的类型。 // 创建新的结果容器 @Override public Supplier<List<T>> supplier() { // 必须返回一个结果为空的Supplier,也就是一个元参函数 // 在调用它时它会建立一个空的累加器实例,供数据收集过程使用 // return () -> new ArrayList<T>(); return ArrayList::new; // 修建集合操做的起始点 } // 将元素添加到结果容器 @Override public BiConsumer<List<T>, T> accumulator() { // 返回执行归约操做的函数 // return (list, item) -> list.add(item); return List::add; // 累积遍历过的项目,原位修改累加器 } // 对结果容器应用最终转换 @Override public Function<List<T>, List<T>> finisher() { return Function.identity(); // 恒等函数 } // 合并两个结果容器 @Override public BinaryOperator<List<T>> combiner() { return (list1, list2) -> { // 合并两个累加器 list1.addAll(list2); return list1; }; } // 返回一个不可变的Characteristics集合 @Override public Set<Characteristics> characteristics() { // IDENTITY_FINISH:将累加器A不加检查地转换为结果R是安全的 // CONCURRENT:accumulator函数能够从多个线程同时调用,且该收集器能够并行归约流 return Collections.unmodifiableSet( // 为收集器添加标志 EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT)); } }
使用
Stream<Dish> menuStream = FakeDb.getMenu().stream(); // 使用已有的收集器 List<Dish> dishes2 = menuStream.collect(Collectors.toList()); // 使用自定义的收集器 List<Dish> dishes = menuStream.collect(new ToListCollector<Dish>()); // 自定义收集而不去实现Collector List<Dish> dishes3 = menuStream.collect( ArrayList::new, /// 供应源 List::add, // 累加器 List::addAll // 组合器 );
/** * 将前n个天然数按质数和非质数分区 * Author: admin * Date: 2018/8/15 15:28 */ public class PrimeNumbersCollector implements Collector<Integer, Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> { @Override public Supplier<Map<Boolean, List<Integer>>> supplier() { // 从一个有两个空List的Map开始收集过程 return () -> new HashMap<Boolean, List<Integer>>() {{ put(true, new ArrayList<Integer>()); put(false, new ArrayList<Integer>()); }}; } @Override public BiConsumer<Map<Boolean, List<Integer>>, Integer> accumulator() { // 将已经找到的质数列表传递给isPrime方法 return (Map<Boolean, List<Integer>> acc, Integer candidate) -> { // 根据isPrime方法返回值,从Map中取质数或非质数列表,把当前的被测数据加进去 acc.get(isPrime(acc.get(true), candidate)).add(candidate); }; } @Override public BinaryOperator<Map<Boolean, List<Integer>>> combiner() { // 将第2个Map合并到第1个 return (Map<Boolean, List<Integer>> map1, Map<Boolean, List<Integer>> map2) -> { map1.get(true).addAll(map2.get(true)); map1.get(false).addAll(map2.get(false)); return map1; }; } @Override public Function<Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> finisher() { return Function.identity(); } @Override public Set<Characteristics> characteristics() { // 质数是按顺序发现的 return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH)); } // 再优化,仅仅用被测试数以前的质数来测试 public static boolean isPrime(List<Integer> primes, int candidate) { // return primes.stream().noneMatch(i -> candidate % i == 0); int candidateRoot = (int) Math.sqrt((double) candidate); return takeWhile(primes, i -> i <= candidateRoot) .stream() .noneMatch(p -> candidate %p == 0); } public static <A> List<A> takeWhile(List<A> list, Predicate<A> p) { int i = 0; for (A item : list) { if (!p.test(item)) { // 检查列表中的当前项目是否知足谓词 return list.subList(0, i); // 若是不知足,返回以前的列表 } i++; } return list; // 都知足,返回所有 } }
使用
// 使用自定义的素数收集器 实现 将数字按质数和非质数分区 public Map<Boolean, List<Integer>> partitionPrimesWithCustomCollector(int n) { return IntStream.rangeClosed(2, n).boxed() .collect(new PrimeNumbersCollector()); }
@Test public void test08() { long fastest = Long.MAX_VALUE; for (int i=0; i<10; i++) { long start = System.nanoTime(); partitionPrimes(1_000_000); long duration = (System.nanoTime() - start) / 1_000_000; if (duration < fastest) fastest = duration; } System.out.println("Fastest execution done in " + fastest + " msecs"); // Fastest execution done in 371 msecs } @Test public void test09() { long fastest = Long.MAX_VALUE; for (int i=0; i<10; i++) { long start = System.nanoTime(); partitionPrimesWithCustomCollector(1_000_000); long duration = (System.nanoTime() - start) / 1_000_000; if (duration < fastest) fastest = duration; } System.out.println("Fastest execution done in " + fastest + " msecs"); // Fastest execution done in 294 msecs }
环境:
性能提高(371 - 294) / 371 = 20.75%