Stream流

2019-07-29  本文已影响0人  我是黄俊俊

  • 一个数据源(如集合)来生成流;
  • 一个中间操作链,形成一条流的流水线;
  • 一个终端操作,执行流水线,并能生成结果。

1. 数据源构造流

1.1 由集合生成流

集合的Stream()方法获取一个Stream流

List<String> list = new ArrayList<>();
        Stream stream = list.stream();
1.2 由数组生成流

你可以使用静态方法Arrays.stream从数组创建一个流,将原始类型的int数组转化为一个IntStream流对其求和

    int[] numbers = {2, 3, 5, 7, 11, 13};
    int sum = Arrays.stream(numbers).sum();
1.3 由文件生成流

java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。你可以用这个方法看看一个文件中有多少各不相同的词:

    long uniqueWords = 0;
    try(Stream<String> lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
                                            .distinct()
                                           .count();
    }
    catch(IOException e){
    }
1.4 由函数生成流

Stream API提供了两个静态方法来从函数生成无限流流:Stream.iterate和Stream.generate这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值,利用IntStream,LongStream的静态方法range,rangeClose可以生成某个数值范围值的原始流

1. 迭代:iterate提供一个初值,利用上一次生成的结果不断迭代生成一个无限流

//生成偶数流
    Stream.iterate(0, n -> n + 2) 
           .limit(10) 
           .forEach(System.out::println);

/*斐波纳契数列 0, 1, 1,2, 3, 5, 8, 13, 21, 34, 55…数列中开始的两个数字是
0和1,后续的每个数字都是前两个数字之和。用iterate方法生成斐波纳契元组序列中的前20个元素。*/
    Stream.iterate(new int[]{0, 1},t -> new int[]{t[1], t[0]+t[1]})
          .limit(20)
          .forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));


2. 生成:generate不是依次对每个新生成的值应用函数的。它接受一个Supplier<T>类型的Lambda提供新的值

//生成随机数流
    Stream.generate(Math::random)
            .limit(5)
            .forEach(System.out::println);
            
3. 数值范围,利用IntStream,LongStream的静态方法rangeClosed方法来生成1到100之间的所有数字如果改用IntStream.range(1, 100),则结果将会是49个偶数,因为range是不包含结束值的

    IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
    System.out.println(evenNumbers.count());

2 中间操作

2.1 筛选

filter
distinct
limit
skip

2.2 映射

map
flatMap 流的扁平化,将多个流合并成一个流

对于一张单词表,如何返回一张列表,列出里面 各不相同的字符 呢?例如,给定单词列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]。

第一个版本可能是这样的:
    words.stream()
          .map(word -> word.split(" "))
          .distinct()
          .collect(toList());
传递给map方法的Lambda为每个单词返回了一个String[](String列表)。因此,map返回的流实际上是Stream<String[]>类型的。


尝试使用map和Arrays.stream()
    words.stream()
          .map(word -> word.split(" "))
          .map(Arrays::stream)
          .distinct()
          .collect(toList());
当前的解决方案仍然搞不定!这是因为,你现在得到的是一个流的列表(更准确地说是Stream<String>)

使用flatMap,你可以像下面这样使用flatMap来解决这个问题:
    List<String> uniqueCharacters = words.stream()
                                        .map(w -> w.split(" "))
                                        .flatMap(Arrays::stream)
                                        .distinct()
                                        .collect(Collectors.toList());
各个数组并不是分别映射成一个流,而是映射成流的内容。所
有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
2.3 排序

sorted

3 终端操作

3.1 遍历消费

forEach

3.2 查找和匹配

allMatch和anyMatch和noneMatch
findFirst和findAny

3.3 归约

reduce归约操作,将流中元素归约为一个值

//有初始值,传入一个初始值和一个Lambda表达式,不断归约
    int sum = numbers.stream().reduce(0, (a, b) -> a + b);
    int sum = numbers.stream().reduce(0, Integer::sum);
    int product = numbers.stream().reduce(1, (a, b) -> a * b);

//无初始值,考虑流中没有任何元素的情况返回一个Optional对象
    Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
    Optional<Integer> max = numbers.stream().reduce(Integer::max);
    Optional<Integer> min = numbers.stream().reduce(Integer::min);
3.4 收集

collect 收集器是一种高级归约,使用收集器已实现的归约方法,collect收集器只需传入一个收集器即可,Collectors实用类提供了很多静态工厂方法,可以方便地创建常见收集器的实例。而实现Collector接口中即实现了一个自定义收集器,其实现决定了如何对流执行归约操作。

最直接和最常用的收集器是toList等静态方法,它会把流中所有的元素收集到一个List中:但其list是抽象类,若要具体的集合,则需要使用toCollection(xxx::new)

   List<Transaction> transactions = transactionStream.collect(Collectors.toList());
   Set<Transaction> transactions = transactionStream.collect(Collectors.toSet());
   List<Transaction> transactions = transactionStream.collect(Collectors.toCollection(ArrayList::new));
   Set<Transaction> transactions =transactionStream.collect(Collectors.toCollection(HashSet::new));

将数据归约汇总为一个值,计数,求最值,求和,求平均值等等
   long howManyDishes = menu.stream().collect(Collectors.counting());
   long howManyDishes = menu.stream().count();

   Comparator<Dish> dishCaloriesComparator =
   Comparator.comparingInt(Dish::getCalories);
   Optional<Dish> mostCalorieDish = menu.stream()
                                       .collect(Collectors.maxBy(dishCaloriesComparator));

   Optional<Dish> mostCalorieDish = menu.stream()
                                       .max(dishCaloriesComparator);

   int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));

   double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));

通过一次summarizing操作你可以就数出菜单中元素的个数,并得到菜肴热量总和、平均值、最大值和最小值:这个收集器会把所有这些信息收集到一个叫作IntSummaryStatistics的类里,它提供了方便的取值(getter)方法来访问结果。打印menuStatisticobject会得到以下输出:
   IntSummaryStatistics menuStatistics =menu.stream()
                                           .collect(summarizingInt(Dish::getCalories));
   IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800}


连接字符串,joining工厂方法有一个重载版本可以接受元素之间的分界符
   String shortMenu = menu.stream().map(Dish::getName).collect(joining());
   String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));

使用收集器分组,如何分组决定于分组函数
   Map<Dish.Type, List<Dish>> dishesByType =menu.stream().collect(groupingBy(Dish::getType));

   Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
   groupingBy(dish -> {
       if (dish.getCalories() <= 400) return CaloricLevel.DIET;
       else if (dish.getCalories() <= 700) return
       CaloricLevel.NORMAL;
       else return CaloricLevel.FAT;
   } ));

多级分组,双参数版本的Collectors.groupingBy工厂方法创建的收集器,它除了普通的分类函数之外,还可以接受collector类型的第二个参数,单参数groupingBy(f)(其中f是分类函数)实际上是groupingBy(f,toList())的简便写法
   Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
   menu.stream().collect(
   groupingBy(Dish::getType,
   groupingBy(dish -> {
       if (dish.getCalories() <= 400) return CaloricLevel.DIET;
       else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
       else return CaloricLevel.FAT;
} )
)
);

//按子组收集数据,要数一数菜单中每类菜有多少个,可以传递counting等收集器作为,groupingBy收集器的第二个参数即可按子组收集数据:
   Map<Dish.Type, Long> typesCount = menu.stream().collect(
   groupingBy(Dish::getType, counting()));

   Map<Dish.Type, Optional<Dish>> mostCaloricByType =
   menu.stream()
   .collect(groupingBy(Dish::getType,
   maxBy(comparingInt(Dish::getCalories))));

//把收集器的结果转换为另一种类型,因为分组操作的Map结果中的每个值上包装的Optional没什么用,所以你可能想要把它们去掉,把收集器返回的结果转换为另一种类型,你可以使用Collectors.collectingAndThen工厂方法返回的收集器
   Map<Dish.Type, Dish> mostCaloricByType =  menu.stream()
                           .collect(groupingBy(Dish::getType,collectingAndThen(
                           maxBy(comparingInt(Dish::getCalories)),Optional::get)));

上一篇 下一篇

猜你喜欢

热点阅读