Stream流

作者: 我是黄俊俊 | 来源:发表于2019-07-29 13:58 被阅读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)));
    
    

    相关文章

      网友评论

          本文标题:Stream流

          本文链接:https://www.haomeiwen.com/subject/ufvprctx.html