Java8实战[第5章]使用流、筛选和切片filter、map、skip、limit、findFirst、findAny、noneMatch、anyMatch、reduce、distinct

1、流的使用

  1. 你可以使用filter、distinct、skip和limit对流做筛选和切片。
  2. 你可以使用map和flatMap提取或转换流中的元素。
  3. 你可以使用findFirst和 findAny方法查找流中的元素。
  4. 你可以用allMatch、 noneMatch和anyMatch方法让流匹配给定的谓词。
  5. 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  6. 你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。 filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才 能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所 有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  7. 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操 作也有相应的特化。
  8. 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
  9. 无限流是没有固定大小的流。

2、实例部分

    @GetMapping("/nodeOne")
    public void ScreeningAndSlicing(){
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

        // 以下代码会筛选出列表中所有的偶数
        numbers.stream()
                .filter(i -> i % 2 == 0)
                // 流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)和skip(n)是互补的
                .skip(1)
                .distinct()
                .forEach(System.out::println);

        ArrayList<String> objects = Lists.newArrayList();

        ArrayList<User> users = Lists.newArrayList();
        users.add(User.builder().name("张三").build());
        users.add(User.builder().name("李四").build());
        users.add(User.builder().name("王五").build());

        users.forEach(user->objects.add(user.getName()));

        //objects.forEach(object-> System.out.println(object));
        objects.forEach(System.out::println);

        /**
         * 这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String
         * 列表)。因此,map返回的流实际上是Stream<String[]>类型的。你真正想要的是用
         * Stream<String>来表示一个字符流。
         */
        // List<Integer> collect = objects.stream().map(object -> object.length()).collect(Collectors.toList());
        List<Integer> collect = objects.stream().map(String::length).collect(toList());
        System.out.println("collect ==> " + collect);

        List<Integer> map2s = users.stream().map(User::getName).map(String::length).collect(toList());
        System.out.println("map2s ==> " + map2s);

        /**
         * Stream<String>来表示一个字符流。幸好可以用flatMap来解决这个问题!
         * 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所
         * 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
         */
        List<String> words = Arrays.asList("Hello", "World");
        List<String> ws = words.stream()
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(toList());
        /**
         * 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所
         * 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
         * 一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
         */
        // 打印的信息 [H, e, l, o, W, r, d]
        System.out.println(ws);

        /**
         * (1) 给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4,5],应该返回[1, 4, 9, 16, 25]。
         *    答案:你可以利用map方法的Lambda,接受一个数字,并返回该数字平方的Lambda来解决这个问题。
         */

        List<Integer> test1s = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> squares =
                test1s.stream()
                        .map(n -> n * n)
                        .collect(toList());
        System.out.println(squares);

        /**
         * (2) 给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],
         * 应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代表数对。
         */

        List<Integer> test2s = Arrays.asList(3, 4);
        List<int[]> pairs = test1s.stream()
                .flatMap(i -> test2s.stream().map(j -> new int[]{i, j}))
                .collect(toList());
        System.out.println(pairs);

        /**
         * (3) 如何扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的。
         * 答案:你在前面看到了,filter可以配合谓词使用来筛选流中的元素。因为在flatMap
         * 操作后,你有了一个代表数对的int[]流,所以你只需要一个谓词来检查总和是否能被3整除
         * 就可以了:其结果是[(2, 4), (3, 3)]
         */
        List<int[]> pairs2 = test1s.stream()
                        .flatMap(i -> test2s.stream().filter(j -> (i + j) % 3 == 0).map(j -> new int[]{i, j}))
                        .collect(toList());
        System.out.println(pairs2);

    }

3、关键字:allMatch、anyMatch、allMatch、noneMatch、anyMatch、allMatch、noneMatch、findFirst、findAny、reduce、max、min、distinct、count

@GetMapping("/nodeTwo")
    public void FindAndMatching(){
        ArrayList<User> users = Lists.newArrayList();
        users.add(User.builder().uId(10).name("张三").build());
        users.add(User.builder().uId(11).name("张四").build());
        users.add(User.builder().uId(12).name("张五").build());

        /**
         * 1、allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词
         * 2、和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。
         * 3、anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本
         *
         * 短路求值
         * 有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用and连起来的大布
         * 尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式
         * 将返回false,所以用不着计算整个表达式。这就是短路。
         * 对于流而言,某些操作(例如allMatch、anyMatch、noneMatch、findFirst和findAny)
         * 不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个
         * 短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小
         * 的流的时候,这种操作就有用了:它们可以把无限流变成有限流。
         */
        // 判断是否所有的uid都大于11
        boolean isUidMax = users.stream().allMatch(user -> user.getUId() > 11);
        // 判断是否所有的uid都大于11 ==> false
        System.out.println("判断是否所有的uid都大于11 ==> " + isUidMax);

        // 如果集合中包含uid大于11的人,就打印出它的名字
        users.stream()
                .filter(user -> user.getUId() > 11)
                .findAny()
                .ifPresent(user -> System.out.println(user.getName()));

        // 查找第一个元素
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstValue =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findFirst();
        // findFirst ==> 9
        System.out.println("findFirst ==> " + firstValue.get());

        /**
         * 使用 isPresent() 从书的集合中找到以 S 字母开头的书
         * Optional<T>类中,isPresent()、 get() 方法的使用
         */
        List<String> listOfBooks = Arrays.asList("Effective Java", "Clean Code", "Test Driven");
        Optional<String> book = listOfBooks.stream().filter(b -> b.startsWith("S")).findFirst();
        String message = book.isPresent() ? book.get() : "Book Not Found";
        // message ==> Book Not Found
        System.out.println("message ==> " + message);

        /**
         * 了reduce操作是如何作用于一个流的:Lambda反复结合每个元素,直到流被归约成一个值。
         * int sum = numbers.stream().reduce(0, (a, b) -> a + b);
         */
        int asIntSum1 = users.stream()
                //返回数值流,减少拆箱封箱操作,避免占用内存  IntStream
                .mapToInt(User::getUId)
                .reduce((x, y) -> x += y)
                .getAsInt();
        // reduce asIntSum 1==> 33
        System.out.println("reduce asIntSum 1==> " + asIntSum1);

        // 给定一个初始值为10
        int asIntSum2 = users.stream().mapToInt(User::getUId).reduce(10, (x, y) -> x += y);
        // reduce asIntSum 2==> 43
        System.out.println("reduce asIntSum 2==> " + asIntSum2);

        /**
         * 最大值、最小值、总数量
         */
        Optional<Integer> max = users.stream().map(User::getUId).reduce(Integer::max);
        // max ==>12
        System.out.println("max ==>" + (max.orElse(0)));

        Optional<Integer> min = users.stream().map(User::getUId).reduce(Integer::min);
        // min ==>10
        System.out.println("min ==>" + (min.orElse(0)));

        // 找到最小值的用户
        Optional<User> minUser = users.stream().min(comparing(User::getUId));
        System.out.println("minUser ==>" + minUser.toString());

        long count = users.stream().map(User::getUId).count();
        // count ==> 3
        System.out.println("count ==> " + count);

        /**
         * 流操作:无状态和有状态
         * 你已经看到了很多的流操作。乍一看流操作简直是灵丹妙药,而且只要在从集合生成流的
         * 时候把Stream换成parallelStream就可以实现并行。
         * 当然,对于许多应用来说确实是这样,就像前面的那些例子。你可以把一张菜单变成流,
         * 用filter选出某一类的菜肴,然后对得到的流做map来对卡路里求和,最后reduce得到菜单
         * 的总热量。这个流计算甚至可以并行进行。但这些操作的特性并不相同。它们需要操作的内部
         * 状态还是有些问题的。
         * 诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。
         * 这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内
         * 部可变状态)。
         * 但诸如reduce、sum、max等操作需要内部状态来累积结果。在上面的情况下,内部状态
         * 很小。在我们的例子里就是一个int或double。不管流中有多少元素要处理,内部状态都是有界的。
         *
         * 相反,诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个
         * 流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知
         * 道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操
         * 作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题(把质数流倒序会做什么
         * 呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。
         *
         */
        // 去重的另一种方式
        Set<String> notUseDistinct = users.stream().map(User::getName).collect(toSet());

        // 代替reduce拼接的另一种方式
        String joiningStr = users.stream().map(User::getName).distinct().sorted()
                        //.reduce("", (n1, n2) -> n1 + n2);
                        .collect(joining());

        /**
         * Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和
         * LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每
         * 个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。
         * 此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复
         * 提取每项交易的交易额计算生成的流中的最大值通过反复比较每个交易的交易额,找出最小的交易
         * 杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
         */
        // 计算总值 mapToInt、mapToDouble、mapToLong
        int sum = users.stream().mapToInt(User::getUId).sum();
        /**
         * 转换回对象流
         * 同样,一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能
         * 产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个
         * IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream
         * 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个
         * Integer),可以使用boxed方法
         */
        IntStream intStream = users.stream().mapToInt(User::getUId);
        Stream<Integer> boxed = intStream.boxed();

        /**
         * 默认值OptionalInt
         * 求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最
         * 大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢?
         * 前面我们介绍了Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用
         * Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类
         * 型特化版本:OptionalInt、OptionalDouble和OptionalLong。
         * 例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
         */
        OptionalInt maxCalories = users.stream().mapToInt(User::getUId).max();
        int optionalMax = maxCalories.orElse(1);

        /**
         * 生成范围值、Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:
         * range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但
         * range是不包含结束值的,而rangeClosed则包含结束值。
         */
        IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
        // 从0到100的偶数数量
        System.out.println(evenNumbers.count());

        /**
         * 生成勾股数
         * flatMap又是怎么回事呢?首先,创建一个从1到100的数值范围来生成a的值。对每
         * 个给定的a值,创建一个三元数流。要是把a的值映射到三元数流的话,就会得到一个由流构成的
         * 流。flatMap方法在做映射的同时,还会把所有生成的三元数流扁平化成一个流。这样你就得到
         * 了一个三元数流。还要注意,我们把b的范围改成了a到100。没有必要再从1开始了,否则就会造
         * 成重复的三元数,例如(3,4,5)和(4,3,5)。
         */
        Stream<int[]> pythagoreanTriples =
                IntStream.rangeClosed(1, 100).boxed()
                        .flatMap(a -> IntStream.rangeClosed(a, 100)
                                        .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
                                        .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
                        );

        // 打印前5组
        // pythagoreanTriples.limit(5).forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
        // 全部打印 0到100勾股总数量 ==> 63
        pythagoreanTriples.forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
        //System.out.println("0到100勾股总数量 ==> " + pythagoreanTriples.count());

        /**
         * 由值创建流
         */
        Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
        stream.map(String::toUpperCase).forEach(System.out::println);
        // 你可以使用empty得到一个空流,如下所示:
        Stream<String> emptyStream = Stream.empty();

        /**
         * 由数组创建流
         * 你可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。例如,
         * 你可以将一个原始类型int的数组转换成一个IntStream,如下所示:
         */
        int[] numbers = {2, 3, 5, 7, 11, 13};
        int sumInt = Arrays.stream(numbers).sum();

        /**
         * 由文件生成流
         * Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。
         * 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) {

        }

        /**
         * 由函数生成流:创建无限流
         * Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。
         * 这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate
         * 和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,
         * 应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
         */
        Stream.iterate(0, n -> n + 2)
                .limit(10)
                .forEach(System.out::println);

        /**
         * iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的
         * Lambda(UnaryOperator<t>类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元
         * 素加上2。因此,iterate方法生成了一个所有正偶数的流:流的第一个元素是初始值0。然后加
         * 上2来生成新的值2,再加上2来得到新的值4,以此类推。这种iterate操作基本上是顺序的,
         * 因为结果取决于前一次应用。请注意,此操作将生成一个无限流——这个流没有结尾,因为值是
         * 按需计算的,可以永远计算下去。我们说这个流是无界的。正如我们前面所讨论的,这是流和集
         * 合之间的一个关键区别。我们使用limit方法来显式限制流的大小。这里只选择了前10个偶数。
         * 然后可以调用forEach终端操作来消费流,并分别打印每个元素。
         */

    }

猜你喜欢

转载自blog.csdn.net/amosjob/article/details/126334946