Java8使用(二)
Stream操作详解
创建流
创建流一般有五种方式:
//1.通过stream方法把List或数组转换为流 @Test public void stream() { Arrays.asList("a1", "a2", "a3").stream().forEach(System.out::println); Arrays.stream(new int[]{1, 2, 3}).forEach(System.out::println); } //2.通过Stream.of方法直接传入多个元素构成一个流 @Test public void of() { String[] arr = {"a", "b", "c"}; Stream.of(arr).forEach(System.out::println); Stream.of("a", "b", "c").forEach(System.out::println); Stream.of(1, 2, "a").map(item -> item.getClass().getName()).forEach(System.out::println); } //3.通过Stream.iterate方法使用迭代的方式构造一个无限流,然后使用limit限制流元素个数 @Test public void iterate() { Stream.iterate(2, item -> item * 2).limit(10).forEach(System.out::println); Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)).limit(10).forEach(System.out::println); } //4.通过Stream.generate方法从外部传入一个提供元素的Supplier来构造无限流,然后使用limit限制流元素个数 @Test public void generate() { Stream.generate(() -> "test").limit(3).forEach(System.out::println); Stream.generate(Math::random).limit(10).forEach(System.out::println); } //5.通过IntStream或DoubleStream构造基本类型的流 @Test public void primitive() { //演示IntStream和DoubleStream IntStream.range(1, 3).forEach(System.out::println); IntStream.range(0, 3).mapToObj(i -> "x").forEach(System.out::println); IntStream.rangeClosed(1, 3).forEach(System.out::println); DoubleStream.of(1.1, 2.2, 3.3).forEach(System.out::println); //各种转换,后面注释代表了输出结果 System.out.println(IntStream.of(1, 2).toArray().getClass()); //class [I System.out.println(Stream.of(1, 2).mapToInt(Integer::intValue).toArray().getClass()); //class [I System.out.println(IntStream.of(1, 2).boxed().toArray().getClass()); //class [Ljava.lang.Object; System.out.println(IntStream.of(1, 2).asDoubleStream().toArray().getClass()); //class [D System.out.println(IntStream.of(1, 2).asLongStream().toArray().getClass()); //class [J //注意基本类型流和装箱后的流的区别 Arrays.asList("a", "b", "c").stream() // Stream<String> .mapToInt(String::length) // IntStream .asLongStream() // LongStream .mapToDouble(x -> x / 10.0) // DoubleStream .boxed() // Stream<Double> .mapToLong(x -> 1L) // LongStream .mapToObj(x -> "") // Stream<String> .collect(Collectors.toList()); }
filter实现过滤操作类似于sql中where
//最近半年的金额大于40的订单 orders.stream() .filter(Objects::nonNull) //过滤null值 .filter(order -> order.getPlacedAt().isAfter(LocalDateTime.now().minusMonths(6))) //最近半年的订单 .filter(order -> order.getTotalPrice() > 40) //金额大于40的订单 .forEach(System.out::println);
map实现转换(投影)类似于sql中的select
//计算所有订单商品数量 //通过两次遍历实现 LongAdder longAdder = new LongAdder(); orders.stream().forEach(order -> order.getOrderItemList().forEach(orderItem -> longAdder.add(orderItem.getProductQuantity()))); //使用两次mapToLong+sum方法实现 assertThat(longAdder.longValue(), is(orders.stream().mapToLong(order -> order.getOrderItemList().stream() .mapToLong(OrderItem::getProductQuantity).sum()).sum()));
flatMap展开(扁平化操作)相当于map+flat
//直接展开订单商品进行价格统计 System.out.println(orders.stream() .flatMap(order -> order.getOrderItemList().stream()) .mapToDouble(item -> item.getProductQuantity() * item.getProductPrice()).sum()); //另一种方式flatMap+mapToDouble=flatMapToDouble System.out.println(orders.stream() .flatMapToDouble(order -> order.getOrderItemList() .stream().mapToDouble(item -> item.getProductQuantity() * item.getProductPrice())) .sum());
sorted排序操作类似sql中的order by
//大于50的订单,按照订单价格倒序前5 orders.stream().filter(order -> order.getTotalPrice() > 50) .sorted(comparing(Order::getTotalPrice).reversed()) .limit(5) .forEach(System.out::println);
distinct操作去重,类似sql中的distinct
//去重的下单用户 System.out.println(orders.stream().map(order -> order.getCustomerName()).distinct().collect(joining(","))); //所有购买过的商品 System.out.println(orders.stream() .flatMap(order -> order.getOrderItemList().stream()) .map(OrderItem::getProductName) .distinct().collect(joining(",")));
skip & limit操作分页,类似sql中的limit
//按照下单时间排序,查询前2个订单的顾客姓名和下单时间 orders.stream() .sorted(comparing(Order::getPlacedAt)) .map(order -> order.getCustomerName() + "@" + order.getPlacedAt()) .limit(2).forEach(System.out::println); //按照下单时间排序,查询第3和第4个订单的顾客姓名和下单时间 orders.stream() .sorted(comparing(Order::getPlacedAt)) .map(order -> order.getCustomerName() + "@" + order.getPlacedAt()) .skip(2).limit(2).forEach(System.out::println);
collect收集操作,对流进行终结(终止)
把流导出为我们需要的数据结构。“终结”指导出后,无法再串联使用其他中间操作,比如filter、map、flatmap、sorted、distinct、limit、skip
在Stream操作中,collect是最复杂的终结操作,比较简单的终结操作还有forEach、toArray、min、max、count、anyMatch等。
六个案例
//生成一定位数的随机字符串 System.out.println(random.ints(48, 122) .filter(i -> (i < 57 || i > 65) && (i < 90 || i > 97)) .mapToObj(i -> (char) i) .limit(20) .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString()); //所有下单的用户,使用toSet去重后实现字符串拼接 System.out.println(orders.stream() .map(order -> order.getCustomerName()).collect(toSet()) .stream().collect(joining(",", "[", "]"))); //用toCollection收集器指定集合类型 System.out.println(orders.stream().limit(2).collect(toCollection(LinkedList::new)).getClass()); //使用toMap获取订单ID+下单用户名的Map orders.stream() .collect(toMap(Order::getId, Order::getCustomerName)) .entrySet().forEach(System.out::println); //使用toMap获取下单用户名+最近一次下单时间的Map orders.stream() .collect(toMap(Order::getCustomerName, Order::getPlacedAt, (x, y) -> x.isAfter(y) ? x : y)) .entrySet().forEach(System.out::println); //订单平均购买的商品数量 System.out.println(orders.stream().collect(averagingInt(order -> order.getOrderItemList().stream() .collect(summingInt(OrderItem::getProductQuantity)))));
Collectors 类的一些常用静态方法
groupBy分组统计操作类似sql中的group by子句
8个案例
//按照用户名分组,统计下单数量 System.out.println(orders.stream().collect(groupingBy(Order::getCustomerName, counting())) .entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).collect(toList())); //按照用户名分组,统计订单总金额 System.out.println(orders.stream().collect(groupingBy(Order::getCustomerName, summingDouble(Order::getTotalPrice))) .entrySet().stream().sorted(Map.Entry.<String, Double>comparingByValue().reversed()).collect(toList())); //按照用户名分组,统计商品采购数量 System.out.println(orders.stream().collect(groupingBy(Order::getCustomerName, summingInt(order -> order.getOrderItemList().stream() .collect(summingInt(OrderItem::getProductQuantity))))) .entrySet().stream().sorted(Map.Entry.<String, Integer>comparingByValue().reversed()).collect(toList())); //统计最受欢迎的商品,倒序后取第一个 orders.stream() .flatMap(order -> order.getOrderItemList().stream()) .collect(groupingBy(OrderItem::getProductName, summingInt(OrderItem::getProductQuantity))) .entrySet().stream() .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) .map(Map.Entry::getKey) .findFirst() .ifPresent(System.out::println); //统计最受欢迎的商品的另一种方式,直接利用maxBy orders.stream() .flatMap(order -> order.getOrderItemList().stream()) .collect(groupingBy(OrderItem::getProductName, summingInt(OrderItem::getProductQuantity))) .entrySet().stream() .collect(maxBy(Map.Entry.comparingByValue())) .map(Map.Entry::getKey) .ifPresent(System.out::println); //按照用户名分组,选用户下的总金额最大的订单 orders.stream().collect(groupingBy(Order::getCustomerName, collectingAndThen(maxBy(comparingDouble(Order::getTotalPrice)), Optional::get))) .forEach((k, v) -> System.out.println(k + "#" + v.getTotalPrice() + "@" + v.getPlacedAt())); //根据下单年月分组,统计订单ID列表 System.out.println(orders.stream().collect (groupingBy(order -> order.getPlacedAt().format(DateTimeFormatter.ofPattern("yyyyMM")), mapping(order -> order.getId(), toList())))); //根据下单年月+用户名两次分组,统计订单ID列表 System.out.println(orders.stream().collect (groupingBy(order -> order.getPlacedAt().format(DateTimeFormatter.ofPattern("yyyyMM")), groupingBy(order -> order.getCustomerName(), mapping(order -> order.getId(), toList())))));
partitionBy用于分区是特殊的分组
//只有true和false两组 public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) { return partitioningBy(predicate, toList()); } //partitioningBy配合anyMatch把用户分为下过订单和没下过订单两组 //根据是否有下单记录进行分区 System.out.println(Customer.getData().stream().collect( partitioningBy(customer -> orders.stream().mapToLong(Order::getCustomerId) .anyMatch(id -> id == customer.getId()))));
Stream操作中观察数据变化
使用peek方法保存
List<Integer> firstPeek = new ArrayList<>(); List<Integer> secondPeek = new ArrayList<>(); List<Integer> result = IntStream.rangeClosed(1, 10) .boxed() .peek(i -> firstPeek.add(i)) .filter(i -> i > 5) .peek(i -> secondPeek.add(i)) .filter(i -> i % 2 == 0) .collect(Collectors.toList()); System.out.println("firstPeek:" + firstPeek); System.out.println("secondPeek:" + secondPeek); System.out.println("result:" + result);
借助IDEA的Stream调试功能
Collectors类实现自定义收集器
//统计出现最多次数的元素 assertThat(Stream.of(1, 1, 2, 2, 2, 3, 4, 5, 5).collect(new MostPopularCollector<>()).get(), is(2)); assertThat(Stream.of('a', 'b', 'c', 'c', 'c', 'd').collect(new MostPopularCollector<>()).get(), is('c')); //实现思路和方式:通过一个 HashMap 来保存元素的出现次数,最后在收集的时候找出 Map 中出现次数最多的元素: public class MostPopularCollector<T> implements Collector<T, Map<T, Integer>, Optional<T>> { //使用HashMap保存中间数据 @Override public Supplier<Map<T, Integer>> supplier() { return HashMap::new; } //每次累积数据则累加Value @Override public BiConsumer<Map<T, Integer>, T> accumulator() { return (acc, elem) -> acc.merge(elem, 1, (old, value) -> old + value); } //合并多个Map就是合并其Value @Override public BinaryOperator<Map<T, Integer>> combiner() { return (a, b) -> Stream.concat(a.entrySet().stream(), b.entrySet().stream()) .collect(Collectors.groupingBy(Map.Entry::getKey, summingInt(Map.Entry::getValue))); } //找出Map中Value最大的Key @Override public Function<Map<T, Integer>, Optional<T>> finisher() { return (acc) -> acc.entrySet().stream() .reduce(BinaryOperator.maxBy(Map.Entry.comparingByValue())) .map(Map.Entry::getKey); } @Override public Set<Characteristics> characteristics() { return Collections.emptySet(); } }
原文链接:https://time.geekbang.org/column/intro/100047701?tab=catalog