JAVA-- 带你重温Java8 Stream的流式操作及注意事项

2023-05-16

Stream API是一组功能强大但易于理解的处理元素序列的工具。如果使用得当,它可以让程序员减少大量的样板代码,创建更具可读性的程序,并提高应用程序的生产力。

流使用的注意事项

  • 流实例是不能重用的 Java 8 streams can't be reused.,否则会抛出异常:java.lang.IllegalStateException: stream has already been operated upon or closed. 这种行为是合乎逻辑的,设计流的目的是:以函数式风格对元素源的有限序列进行操作,而不是存储元素。
    @Test
        public void testStream(){
            Stream<String> stream =
                Stream.of("a", "abc","b", "c").filter(element -> element.contains("b"));
            Optional<String> anyElement = stream.findAny();
            //再次使用
            System.out.println(stream.findAny().get());
        }
    
  • Intermediate operations are lazy. they will be invoked only if it is necessary for the terminal operation execution. 中间操作是懒惰的。这意味着只有在执行终端操作时才会调用它们。如list.stream().skip(1).map(element -> element.substring(0, 3)).sorted().collect(Collectors.joining(";")) 只有在执行collect操作时,前面的skip、map、sorted这些操作才会执行。
  • 执行顺序 Order of Execution: 从性能的角度来看,正确的顺序是流管道中链接操作最重要的方面之一:
    • 减少流大小的中间操作应该放在应用于每个元素的操作之前,【 intermediate operations which reduce the size of the stream should be placed before operations which are applying to each element】
    • 如要先执行skip,filter,distinct等操作,再执行map等转换操作
  • 在真正的应用程序中,不要让实例化的流未被使用,因为这会导致内存泄漏

流的创建

有许多方法可以创建不同源的流实例。一旦创建,实例将不会修改其源,因此允许从单个源创建多个实例。

创建Stream of Generics(泛型流)

//Empty Stream
Stream<String> streamEmpty = Stream.empty();

//Stream of Collection
Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

//Stream of Array
Stream<String> streamOfArray = Stream.of("a", "b", "c");
String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);

//Stream.builder()
Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();

//Stream.generate() or iterate()
Stream<String> streamGenerated =
Stream.generate(() -> "element").limit(10);

Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);

创建Stream of Primitives(基本类型【原语流】)

Java 8提供了用三种基本类型创建流的可能性:int、long和double。由于Stream是一个泛型接口,并且无法将原语【基本类型】用作泛型的类型参数,因此创建了三个新的特殊接口:IntStream、LongStream、DoubleStream。

  • range(int startInclusive, int endExclusive)
  • rangeClosed(int startInclusive, int endInclusive)
  • Random类提供了大量生成原语流的方法。例如,如代码random.doubles(3)创建了一个DoubleStream,它有三个随机产生的元素
IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);

Random random = new Random();
DoubleStream doubleStream = random.doubles(3);

创建Stream of String

IntStream streamOfChars = "abc".chars();

Stream<String> streamOfString = Pattern.compile(", ").splitAsStream("a, b, c");

创建Stream of File

Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));

流的引用 Referencing a Stream

实例化一个流,并有一个对它的可访问引用,只要只调用中间转化操作【 intermediate operations】。执行结束操作【terminal operation】将使流不可访问。

如何正确的引用流实例?

Java 8 streams can’t be reused.流实例不能重用,所以正确引用流实例的做法如下:

List<String> elements =
  Stream.of("a", "b", "c").filter(element -> element.contains("b"))
    .collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Optional<String> firstElement = elements.stream().findFirst();

Stream Pipeline操作

要对数据源的元素执行一系列操作并聚合其结果,我们需要三个部分:
source, intermediate operation(s) and a terminal operation. 示例代码如下:

List<String> list = Arrays.asList("abc1", "abc2", "abc3");
long size = list.stream().skip(1).map(element -> element.substring(0, 3)).sorted().count();

//or
list.stream().skip(1).map(element -> element.substring(0, 3)).sorted().collect(Collectors.joining(";"))

隋性调用 Lazy Invocation

结果日志显示程序调用了两次filter()方法,调用了一次map()方法。这是因为管道是垂直执行的。
在下面的例子中,流的第一个元素不满足过滤器的谓词。然后程序为第二个元素调用filter()方法,它通过了过滤器。但程序没有为第三个元素调用filter(),而是通过管道向下访问map()方法。
findFirst()操作只满足一个元素,因此,在这个特定的示例中,隋性调用允许程序避免两个方法调用,一个用于filter(),另一个用于map()。

public class StreamTest {
    @Test
    public void testStream(){
        List<String> list = Arrays.asList("abc1","abc2","abc3");
        Optional<String> stream = list.stream().filter(element -> {
            print("filter() was called");
            return element.contains("2");
        }).map(element -> {
            print("map() was called");
            return element.toUpperCase();
        }).findFirst();
    }

    private static void print(String msg){
        System.out.println(msg);
    }
}

程序的输出结果是:

filter() was called
filter() was called
map() was called

流聚合操作 Stream Reduction

内置的聚合操作

API有许多终端操作,可以将流聚合为类型或原语:count()、max()、min()和sum()。

如何自定义聚合行为?

The reduce() Method

  • identity: 累加器的初始值,如果流为空且没有需要累加,则为默认值
  • accumulator: 元素聚合逻辑的函数。
  • combiner:accumulator结果聚合逻辑的函数
public class StreamTest {
    @Test
    public void testStream(){
        OptionalInt reduced = IntStream.range(1, 4).reduce((a, b) -> a + b);
        print(reduced.getAsInt()+"");

        int reducedTwoParams = IntStream.range(1, 4).reduce(10, (a, b) -> a + b);
        print(reducedTwoParams+"");

        int reducedParams = Arrays.asList(1, 2, 3).parallelStream().reduce(10,
            (a, b) -> a + b,
            (a, b) -> {
                print("combiner was called");
                return a + b;
            });
        print(reducedParams+"");
    }

    private static void print(String msg){
        System.out.println(msg);
    }
}

输出结果是:

6
16
combiner was called
combiner was called
36

The collect() Method

方法averagingXX(), summingXX()和summarizingXX()可以使用原语(int, long, double)和它们的包装类(Integer, Long, Double)。
这些方法的一个更强大的特性是提供映射。因此,开发人员不需要在collect()方法之前使用额外的map()操作。

  • 通过collect操作,转化为List、String、 统计结果
    public class StreamTest {
        @Test
        public void testStream(){
            List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                new Product(14, "orange"), new Product(13, "lemon"),
                new Product(23, "bread"), new Product(13, "sugar"));
            List<String> collectorCollection =
                productList.stream().map(Product::getName).collect(Collectors.toList());
    
            String listToString = productList.stream().map(Product::getName)
                .collect(Collectors.joining(", ", "[", "]"));
            print(listToString);
    
            double averagePrice = productList.stream()
                .collect(Collectors.averagingInt(Product::getPrice));
            print(averagePrice+"");
    
            int summingPrice = productList.stream()
                .collect(Collectors.summingInt(Product::getPrice));
    
            IntSummaryStatistics statistics = productList.stream()
                .collect(Collectors.summarizingInt(Product::getPrice));
            print(statistics.toString());
        }
    
        private static void print(String msg){
            System.out.println(msg);
        }
    }
    @Data
    @AllArgsConstructor
    class Product {
        private int price;
        private String name;
    }
    
  • 通过collect的groupby,转化为Map、Set
    public void testStream(){
            List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                new Product(14, "orange"), new Product(13, "lemon"),
                new Product(23, "bread"), new Product(13, "sugar"));
    
            Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
                .collect(Collectors.groupingBy(Product::getPrice));
    
            Map<Boolean, List<Product>> mapPartioned = productList.stream()
                .collect(Collectors.partitioningBy(element -> element.getPrice() > 15));
    
            Set<Product> unmodifiableSet = productList.stream()
                .collect(Collectors.collectingAndThen(Collectors.toSet(),
                    Collections::unmodifiableSet));
        }   
    
  • 自定义Collector
    @Test
        public void testStream(){
            List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                new Product(14, "orange"), new Product(13, "lemon"),
                new Product(23, "bread"), new Product(13, "sugar"));
    
            Collector<Product, LinkedList<Product>, LinkedList<Product>> toLinkedList =
                Collector.of(LinkedList::new, LinkedList::add,
                    (first, second) -> {
                        first.addAll(second);
                        return first;
                    });
    
            LinkedList<Product> linkedListOfPersons =
                productList.stream().collect(toLinkedList);
            print(linkedListOfPersons.toString());
        }
    

Parallel Streams 并行流

在Java 8之前,并行化非常复杂。ExecutorService和ForkJoin的出现略微简化了开发人员的工作,但仍然需要记住如何创建特定的executor及如何运行它等等。Java 8引入了一种以函数式风格实现并行的方法。

API允许程序创建并行流,以并行模式执行操作。

  • 当流的源是一个Collection或数组时,可以通过parallelStream()方法来实现:
  • 当流的源不是Collection或数组,则应该使用parallel()方法:
  • 并行模式的流可以通过使用sequential()方法转换回顺序模式:
Stream<Product> streamOfCollection = productList.parallelStream();
boolean isParallel = streamOfCollection.isParallel();
boolean bigPrice = streamOfCollection
  .map(product -> product.getPrice() * 12)
  .anyMatch(price -> price > 200);


IntStream intStreamParallel = IntStream.range(1, 150).parallel();
boolean isParallel = intStreamParallel.isParallel();

IntStream intStreamSequential = intStreamParallel.sequential();
boolean isParallel = intStreamSequential.isParallel();

在底层,Stream API自动使用ForkJoin框架并行执行操作。默认情况下,将使用公共线程池,并且没有办法(至少目前)将一些自定义线程池分配给它。这可以通过使用一组自定义的并行收集器来解决。

当以并行模式使用流时,避免阻塞操作。当任务需要相同的时间来执行时,最好使用并行模式。如果一个任务比另一个任务持续的时间长得多,它会减慢整个应用程序的工作流程。

有用的代码片段

如何向流中添加单个元素

  • 添加到最前面
  • 添加到最后面
  • 添加到指定位置
@Test
public void givenStream_whenPrependingObject_thenPrepended() {
    Stream<Integer> anStream = Stream.of(1, 2, 3, 4, 5);
    Stream<Integer> newStream = Stream.concat(Stream.of(99), anStream);
    assertEquals(newStream.findFirst().get(), (Integer) 99);
}

@Test
public void givenStream_whenAppendingObject_thenAppended() {
    Stream<String> anStream = Stream.of("a", "b", "c", "d", "e");
    Stream<String> newStream = Stream.concat(anStream, Stream.of("A"));
    List<String> resultList = newStream.collect(Collectors.toList());
    assertEquals(resultList.get(resultList.size() - 1), "A");
}

 private static <T> Stream insertInStream(Stream stream, T elem, int index) {
        Spliterator spliterator = stream.spliterator();
        Iterator iterator = Spliterators.iterator(spliterator);

        return Stream.concat(Stream.concat(Stream.generate(iterator::next)
            .limit(index), Stream.of(elem)), StreamSupport.stream(spliterator, false));
    }

    @Test
    public void givenStream_whenInsertingObject_thenInserted() {
        Stream<Double> anStream = Stream.of(1.1, 2.2, 3.3);
        Stream<Double> newStream = insertInStream(anStream, 9.9D, 3);

        List<Double> resultList = newStream.collect(Collectors.toList());

        assertEquals(resultList.get(3), (Double) 9.9);
    }

如何将List 转化为Map

    @Test
    public void testStream(){
        List<Map<String,Object>> rows = Lists.list(
            Map.of("name","张三","addr","广州"),
            Map.of("name","李四","addr","上海"));
        Map<String, String> map = rows.stream()
            .map(r->{
                return Pair.of(r.get("name").toString(),r.get("addr").toString());
            })
            .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
        print(map.toString());
    }

key是对象中的某个属性值,value是对象本身,当key冲突时选择第二个key值覆盖第一个key值。

Map<String, User> userMap4 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity(), (oldValue, newValue) -> newValue));

参考

The Java 8 Stream API Tutorial
core-java-streams-2

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA-- 带你重温Java8 Stream的流式操作及注意事项 的相关文章

  • ubantu系统服务器系统搭建详情及配置步骤(DELL poweredge r730服务器)

    实验室新到了一批显卡 xff0c 周末跟着几位师兄在机房混迹学着ubantu系统系统服务的部署 xff0c 配置 xff0c 搭建 xff0c 现记录于博客备忘 准备 xff1a ubantu镜像文件 ubuntu 16 04 3 serv
  • 树莓派升级(安装)Python3.6

    如果你的树莓派或者其他Linux主机上安装的Python版本比较低 xff0c 那么在安装Homeassisant等软件时 xff0c 会出现一些故障导致无法安装 xff0c 所以本文讲解如何在python版本比较低的树莓派上升级树莓派的版
  • 研究了几天CEF的感受

    cef搞到现在 xff0c 用已经勉强用上了 xff0c 性能也很好 xff0c 但是最大的问题是js在render进程里跑 xff0c 这一点和Electron是一样的 xff0c 也是chrome这类浏览器标准做法 xff0c 其实是不
  • Jenkins安装过程

    一 什么是自动化测试平台 前言 xff1a 在一些做嵌入式产品的公司里 xff0c 为了确保开发主线代码的稳定 xff0c 在做版本升级的时候要考虑到代码改动对主线功能造成的影响 xff0c 避免造成严重的版本问题 xff0c 需要在每次代
  • ldd nm strip strings readelf file查看信息

    ldd lt 可执行文件名 gt 查看可执行文件链接了哪些 系统动态链接库 nm lt 可执行文件名 gt 查看可执行文件里面有哪些符号 strip lt 可执行文件名 gt 去除符号表可以给可执行文件瘦身 strip后使用nm查看不到符号
  • linux console输出重定向到串口ttyS0

    在linux系统中使用virsh创建和管理虚拟机时 xff0c 除了使用ssh 连接虚拟机 xff0c 还可以使用 virsh console 虚机名 的方式连接虚拟机 xff0c 不过需要在虚机镜像中开启将console重定向到串口的设置
  • 最新综述 | 图数据上的对抗攻击与防御

    Lichao Sun and Yingtong Dou and Carl Yang and Ji Wang and Philip S Yu and Bo Li Adversarial Attack and Defense on Graph
  • 记一次硬件调试经历

    产品信息 系统 xff1a SONiC系统 xff08 linux 4 9 110 xff09 xff0c ONIE xff08 4 1 38 xff09 项目背景 xff1a 此项目是一个网络流量设备 xff0c 作为流量转发器 xff0
  • VNC 的应用及灰屏鼠标变X问题

    Ubuntu中vnc服务器端的安装很简单 xff0c 运行如下命令 xff1a sudo apt get install vnc4server 第一次启动vncserver后 xff0c 在用户家目录中会生成 vnc 目录 xff0c 注意
  • 质量—弹簧—阻尼系统的建模分析

    质量 弹簧 阻尼系统的建模分析 本文介绍如何使用数轴建模法对质量 弹簧 阻尼系统进行建模分析 这里涉及的质量块 弹簧 阻尼均为理想器件 注 xff1a 实际弹簧还拥有阻尼器的效果 xff0c 即实际弹簧应该是一个弹簧 阻尼系统 在分析质量
  • ubuntu16.04上samba服务器的安装和配置

    大家好 xff0c 我是加摩斯 xff0c 觉得文章有帮助的小伙伴 xff0c 记得一键三连哟 xff5e 申明 xff1a 原创 xff0c 转载前请与我沟通 samba服务器的介绍可以查看鸟哥私房菜服务篇中的文件服务器之二 xff0c
  • 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对

    答 xff1a 不对 xff0c 有相同的 hash code 这是java语言的定义 xff1a 1 对象相等则hashCode一定相等 xff1b 2 hashCode相等对象未必相等 hashCode 的常规协定是 xff1a 1 在
  • 关于下载Keil5无法打开keil4文件的问题解决方案

    关于下载Keil5无法打开keil4文件的问题解决方案 1 拖拽工程文件到keil4中 xff0c 发现相应问题2 打开工程目录 xff0c 删除缓存文件3 将uvproj文件拖到keil4程序上 本人在下载完keil4后下载了keil5
  • linux: flameshot 快捷键设置

    如果有一次启动后 xff0c 系统说 flameshot 崩溃了 xff0c 然后你的 快捷键 xff0c 就会不好用了 xff0c 那么此时 重启flameshot 即可
  • latex, 两个图并排怎么弄

    想要这样 的效果 俩图并排 xff0c 底下还有 一个 图的caption 你使用 如下 latex begin figure h subfloat label picd includegraphics width 61 6 77cm pi
  • 【TouchGFX实战】中文打印与滚动文本框

    TouchGFX实战 中文打印与滚动文本框 本文涉及到的TouchGFX版本基于TouchGFX Designer 4 19 1 xff0c 已成功应用到实际项目 xff0c 如有疑问请向作者留言咨询 效果演示 xff1a 整体效果如下 x
  • 调整VMware虚拟机硬盘容量大小

    xfeff xfeff 使用在VMware安装目录下就有一个vmware vdiskmanager exe程序 xff0c 它是一个命令行工具 xff0c 可用来修改虚拟机硬盘的大小 命令格式如下 vmware vdiskmanager x
  • 人脸识别之损失函数Softmax

    这次我想和各位童鞋分享下人脸识别中的损失函数 xff0c 我认为根据损失函数的不同可以把人脸识别看做classification和metric learning两种或者两者的结合 下面我分享下我训练中踩的一些坑 xff0c 如有纰漏欢迎童鞋
  • 无线网卡MT7601U驱动的移植

    1 mt7601u无线网卡驱动 xff1a 官网 xff1a http www mediatek com zh CN downloads mt7601u usb 或者 xff1a http download csdn net detail
  • 树莓派安装中文输入法

    树莓派安装中文输入法 1 更新终端2 安装中文字库3 刷新字库缓存4 打开配置界面5 安装中文输入法 scim 首先安装树莓派官方系统 xff1a raspbian 1 更新终端 sudo apt get update 2 安装中文字库 由

随机推荐