当前位置: 首页 > article >正文

Java流(Stream)详解

1. Java8对流的支持

流是一个与任何特定的存储机制都没有关系的元素序列。不同于在集合中遍历元素,使用流的时候,我们是从一个管道中抽取元素,并对它们逬行操作。这些管道通常会被串联到一起,形成这个流上的一个操作管线。

流的一个核心优点是,它们能使我们的程序更小,也更好理解。当配合流使用时,Lambda 表达式和方法引用就发挥出其威力了。流大大提升了 Java 8 的吸引力。

假设我们想按照有序方式显示随机选择的5~20范围内的,不重复的整数,借助流我们只需要说明想做什么即可实现:

package stream;

import java.util.Random;

public class Randoms {
    public static void main(String[] args) {
        new Random(47)
                .ints(5, 20)
                .distinct().limit(5)
                .sorted()
                .forEach(System.out::println);

        /*
         * 6
         * 10
         * 13
         * 16
         * 18
         */
    }
}

我们先为 Random 对象设置一个随机种子,ints() 方法会生成一个流,该方法有多个重载版本,其中两个参数的版本可以设置所生成值的上下界。使用中间流操作 distinct() 去掉重复的值,再使用 limit() 选择前5个值,sorted() 表示元素是有序的,最后我们想显示每一个条目,所以使用了 forEach(),它会根据我们传递的函数,在每个流对象上执行一个操作。这里我们传递了一个方法引用 System.out::println,用于将每个条目显示在控制台上。

使用流实现的代码我们看不到任何显式的迭代机制,因此成为内部迭代,这是流编程的一个核心特性。

2. 流的创建

使用 Stream.of(),可以轻松地将一组条目变成一个流:

package stream;

import java.util.stream.Stream;

public class StreamOf {
    public static void main(String[] args) {
        Stream.of(4, 1, 7).forEach(System.out::println);
        Stream.of("Hello ", "World ", "AsanoSaki").forEach(System.out::print);

        /*
         * 4
         * 1
         * 7
         * Hello World AsanoSaki
         */
    }
}

此外,每个 Collection 都可以使用 stream() 方法来生成一个流:

package stream;

import java.util.*;
import java.util.stream.Collectors;

class A {
    int x;

    A(int x) { this.x = x; }
}

public class CollectionToStream {
    public static void main(String[] args) {
        List<A> listA = Arrays.asList(new A(1), new A(2), new A(3));
        System.out.println(listA.stream().mapToInt(a -> a.x).sum());

        Set<String> st = new TreeSet<>(Arrays.asList("Hello world and java".split(" ")));
        System.out.println(st.stream().map(String::toUpperCase).collect(Collectors.joining(" ")));

        Map<String, Double> mp = new HashMap<>();
        mp.put("PI", 3.14);
        mp.put("E", 2.718);
        mp.put("PHI", 1.618);
        mp.entrySet().stream()
                .map(e -> e.getKey() + ": " + e.getValue())
                .forEach(System.out::println);

        /*
         * 6
         * HELLO AND JAVA WORLD
         * PHI: 1.618
         * E: 2.718
         * PI: 3.14
         */
    }
}

在创建了一个 List<A> 之后,只需要调用一下 stream() 这个所有集合类都有的方法。中间的 map() 操作接受流中的毎个元素,在其上应用一个操作来创建一个新的元素,然后将这个新元素沿着流继续传递下去,这里的 mapToInt() 将一个对象流转变成了一个包含 IntegerIntStream。对于 FloatDouble 也有名字类似的操作。

collect() 操作会根据其参数将所有的流元素组合起来,当我们使用 Collectors.joining() 时,得到的结果是一个 String,每个元素都会以 joining() 的参数分隔,还有其他很多 Collectors,可以生成不同的结果。

为了从 Map 集合生成一个流,我们首先调用 entrySet() 来生成一个对象流,其中每个对象都包含着一个键和与其关联的值,然后再使用 getKey()getValue() 将其分开。

2.1 随机数流

Ramdom 类在 Java 8 引入了流,有一组可以生成流的方法:

package stream;

import java.util.Random;
import java.util.stream.Stream;

public class RandomGenerators {
    public static <T> void show(Stream<T> stream) {
        stream.limit(3).map(x -> x + " ").forEach(System.out::print);
        System.out.println();
    }

    public static void main(String[] args) {
        Random rand = new Random(47);
        show(rand.ints().boxed());
        show(rand.doubles().boxed());
        show(rand.ints(10, 20).boxed());  // 设置上下边界
        show(rand.ints(2).boxed());  // 设置流的大小
        show(rand.ints(2, 10, 20).boxed());  // 设置流的大小与上下边界

        /*
         * -1172028779 1717241110 -2014573909
         * 0.053412216308810656 0.5779976127815049 0.4170137422770571
         * 17 18 18
         * 1122537102 491149179
         * 19 18
         */
    }
}

为消除冗余代码,上面的示例创建了泛型方法 show(Stream<T> stream),这个特性在之后会讲。类型参数 T 可以是任何东西,所以使用 IntegerLongDouble 都可以。然而,Random 类只会生成 intlongdouble 等基本类型的值。幸运的是,boxed() 流操作会自动将基本类型转换为其对应的包装器类型,使得 show() 能够接受这个流。

2.2 整型的区间范围

IntStream 类提供了一个 range() 方法,可以生成一个流(由 int 值组成的序列),在编写循环时非常方便:

package stream;

import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class IntStreamRange {
    public static void main(String[] args) {
        // for-in循环
        for (int i: IntStream.range(1, 5).toArray())
            System.out.print(i + " ");  // 1 2 3 4
        System.out.println();

        // Stream
        System.out.println(IntStream.range(1, 5).boxed().map(Object::toString).collect(Collectors.joining(" ")));  // 1 2 3 4
        IntStream.range(1, 5).boxed().forEach(x -> System.out.print(x + " "));  // 1 2 3 4
        System.out.println();
    }
}

现在我们编写一个 repeat() 工具函数取代简单的 for 循环:

package stream;

import java.util.stream.IntStream;

public class Repeat {
    static void repeat(int n, Runnable action) {
        IntStream.range(0, n).forEach(i -> action.run());
    }

    static void hello() {
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        repeat(3, () -> System.out.println("Lambda"));
        repeat(2, Repeat::hello);

        /*
         * Lambda
         * Lambda
         * Lambda
         * Hello
         * Hello
         */
    }
}

2.3 Stream.generate()

Stream.generate() 可以接受任何的 Supplier<T>java.util.function 中的接口),并生成一个由 T 类型的对象组成的流。如果想创建一个由完全相同的对象组成的流,只需要将一个生成这些对象的 Lambda 表达式传给 generate()

package stream;

import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Generator implements Supplier<String> {
    Random rand = new Random(47);
    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    @Override
    public String get() {
        return String.valueOf(letters[rand.nextInt(letters.length)]);
    }

    public static void main(String[] args) {
        String str = Stream.generate(new Generator()).limit(10).collect(Collectors.joining());
        System.out.println(str);  // YNZBRNYGCF

        Stream.generate(() -> "AsanoSaki").limit(2).forEach(s -> System.out.print(s + " "));  // AsanoSaki AsanoSaki
    }
}

http://www.kler.cn/news/107350.html

相关文章:

  • tomcat必要的配置
  • Centos使用tomcat部署jenkins
  • cola架构:有限状态机(FSM)源码分析
  • jenkins如何安装?
  • 基于SpringBoot+Vue的服装销售系统
  • Python深度学习实战-基于Sequential方法搭建BP神经网络实现分类任务(附源码和实现效果)
  • 基于GPIO子系统编写LED驱动,编写应用程序进行测试设置定时器,5秒钟打印一次hello world
  • 软考 系统架构设计师系列知识点之设计模式(4)
  • GoLong的学习之路(十四)语法之标准库 time(时间包)的使用
  • MySQL语言分类
  • 论文阅读 - Hidden messages: mapping nations’ media campaigns
  • Android原生项目集成uniMPSDK(Uniapp)遇到的报错总结
  • 在 macOS 上的多个 PHP 版本之间切换
  • 李沐——论文阅读——VIT(VIsionTransformer)
  • 使用Gateway解决跨域问题时配置文件不生效的情况之一
  • CTF-php特性绕过
  • 一次不接受官方建议导致的事故
  • 软考高项-计算题(3)
  • 【LeetCode】5. 最长回文子串
  • 10月28日,每日信息差
  • HarmonyOS开发:探索组件化模式开发
  • Flink CDC 2.0 主要是借鉴 DBLog 算法
  • PostgreSQL basebackup备份和恢复
  • 闲聊一下写技术博客的一些感想
  • Dijkstra算法基础详解,附有练习题
  • OpenAI大模型项目计划表(InsCode AI 创作助手)
  • Android Studio 查看Framework源码
  • 基于LCC的Buck谐振变换器研究
  • arcgis js api FeatureLayer加载时返回数据带*问题
  • 针对多分类问题,使用深度学习--Keras进行微调提升性能