首页 > 编程语言 >JAVA基础之六-Stream(流)简介

JAVA基础之六-Stream(流)简介

时间:2024-09-15 16:25:49浏览次数:9  
标签:Predicate JAVA Collectors Stream 接口 之六 java Collector

我在别的篇幅已经说过:流这个东西偶尔可以用用,但我个人最大的学习动力(目前)仅仅是为了有助于阅读spring越发繁复的源码

 

本文主要介绍Stream接口(包括主要的方法)和相关的几个主要接口:Predicate、Consumer、Supplier

还有Collector接口,Collectors工具类。

由于网上已经有太多的文章介绍,所以,本文侧重点在于简单介绍基本的概念,并不会罗列所有的接口成员的说明(这些可以看javaDoc或者

官方的javaDoc)。

 

虽然主要说的是流,但也顺便说了一些其它东西:函数式接口之类

 

注:本文所相关代码和图是在J17环境下获得的。

一、重要接口、类及其定义

1.1、流直接相关接口/类

仅介绍部分:

  1. Stream
  2. Predicate
  3. Consumer
  4. Supplier
  5. xxxOperator
  6. xxxFunction
  7. Collector
  8. Collectors

Stream,Collector,Collectors是java.util.stream之下的,剩下的都是在java.util.function之下

 

1.Stream(普通接口)

流接口的类层次结构如下图:

可以看到Stream继承自BaseStream,后者则继承了AutoCloseable。顺便提一句,并不是什么都可以自动关闭,如果是文件流需要自己关闭,Stream的javaDoc有提到。

此外,有可以看到,还有几个继承自BaseStream的其它Stream,包括DoubleStream,IntStream,LongStream,..

有点不太明白的是,为什么java17中没有提供其它可以聚集的数据类的Stream,例如为什么没有BigDecimalStream,BigIntStream? 有空再补充。

 

BaseStream重要定义了和计算不太相干的一些行为(并发、迭代等),如下图:

 

Stream自己则专注于计算/转换有关的,如下图:

如上图,都是诸如min,max,count,distinct之类的熟悉字眼,还有份额不小的各种mapXXX。

基本上,需要流做的,或者善于让流做的事情,都定义好了。

 

2.Predicate(函数式接口)

Predicate ,中文有许多翻译:谓词、断言、表明、预测、断定等。

不过结合大量的方法定义,我觉得翻译为“断定”可能更好理解,一个根据输入确定是否真伪的对象,好似人类的推官、断事官、审判员。

为了便于行文,后面都会写成Predicate或者断定

这个断定接口定义了几个重要的方法:

可以看到,只有一个test是需要实现的,其它几个都是默认的或者是静态的。

这个接口要实现类返回一个布尔值,断定传入的参数/内容是否合乎某个标准。

 

3.Consumer(函数式接口)

Consumer-消费者,这个翻译没有异议。

所以消费者接口比较简单,就两个方法:

其中的accept不返回任何东西,重要的是andThen,可以让人一直andThen下去,只要没有异常。

也就是说,消费者可以一直消费,只要愿意。

 

4.Supplier(函数式接口)

和消费者对应的是供应者

和消费者不同的是,供应者只有一个get方法。

所以供应者提供了之后,就只能等着,然后消费者可以一直消费。

 

5.各种Operator

Operator有的地方翻译为操作符,有的地方翻译为操作数。

结合已有的各种xxxOperator:BinaryOperator、DoubleUnaryOperator、UnaryOperator..

翻译为"操作者",以应合提供者、消费者。

各种操作者都是有返回的。  JCP大概把难于清晰界定其作用的各种函数式接口定义为操作者。

 

6..Bi,Binary是什么鬼?

Bi

在java.util.function下带有许多Bi开头的,例如BiFunction,BiConsumer,BiPredicate,xxxBixxx。

Bi是一个前缀,源自拉丁bis,表示两个、两次、两倍,总之就是2xxx。

所以带Bi开头的都是指有2个参数,而不带Bi的对应的都是一个参数为主(目前)。看看Predicate,BiPredicate就明白了:

@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u);
}

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

注:为了节省篇幅,没有复制完整的源码和注释。

 

可以看到,Predicate带一个参数,BiPredicate有2个参数。

Jcp定义了不少Bixxx,大概是因为两个参数的场景也不少,否则为什么不定义三个,四个,...n个的Predicate等接口?

为了便于交流,我有的时候称呼Bixxx之类的函数为双参函数/方法,避免有些同事不知道这是什么玩意

 

Binary

一般想到的是二进制,不过在这里Binary兼有Bi一致的含义,合起来就是2个参数,且参数类型一致(包括返回也是一样)。

拿非常典型的java.util.function.BinaryOperator<T>看下:

public interface BinaryOperator<T> extends BiFunction<T,T,T>

BiFunciton的定义(局部):

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

BiFunction必然是两个参数,但它不要求三个参数一致,而BinaryOperator则要求一致。

所以,这里Binary可以理解为三参一致,即两个入参和一个出参(返回值)一致,或者是两个入仓和返回值一致。

按照这个思维,其实JCP等可以考虑把这个命名为:triple

 

7.Collector(常规接口)

收集者 public interface Collector<T, A, R>

这是一个比较奇怪的接口,这是因为除了两个工具类的函数(of),其它的公共抽象方法的命名有背常规。

一般而言,方法的命名规范为:动词、动词+名词、形容词

例如 do(),doSomething(),isFinished()

但这里都是名词:supplier、accumulator、combiner、finisher、characteristics。

分别表示提供者、累积者、合并者、完成者、特征(注意,这里是一个集合)

Collector也提供了两个公共静态函数of,用于返回一个新的Collector实现。

在of函数中,其实即使使用Collectors.CollectorImpl 来实现。

所以,如果不想开发新的Collector实现,基本上用Collectors或者Collect.of即可,但本质上都是用Collectors.CollectorImpl

这也应该是JCP所期望的吧!

 

8.Collectors(实现Collector接口为主的工具类)

这个类非常有用。

当我们使用流处理的时候,常常会有转换的需要。当转换完成后需要输出另外一个格式的时候,就需要使用到收集器。

Collectors就是一个收集器集合,包含了各种各种的收集函数,实现诸如分组、转换(目标是list,map等)等操作

收集器集合中所有的公共方法方法返回的都是收集者(Collector)类型。

当我们查看源码,可以发现一个非常重要的地方:凡是返回Collector的public static 方法,最后都会调用:

return new CollectorImpl<>(supplier, accumulator, merger, finisher, characteristics);

CollectorImpl是Collector接口的实现,属于私有静态类(由于Collectors是final导致的)

java这些写的意图应该是这样的:你们不要费心(包括继承,扩展等等),就用Collectors的具体方法即可.

以常用toList()为例:

public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>(ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

Collector类型的返回,可以被实现了Stream.collect利用,从而完成输出。

Stream的实现有很多,如前,最常见的是java.util.stream.ReferencePipeline<P_IN, P_OUT>

 

对于一般的工程师而言,想要Collect,可以这么做:

1.利用Collector.of构建一个,或者利用Colelctors的现货

2.把构建好的Collector当做入参,塞给Stream.collect方法.

3.完成

 

两个名词

mutable-可变的

immutable-不可变的

阅读stream代码的时候,需要格外注意哪些参数是可变的,还是不可变的。

特别注意:对于管道操作、流操作,要求部分参数不可变是一个很常见的要求。

 

1.2、java对其它接口/类的改造

主要是对集合类型的改造(从1.8开发)。

1.2.1、Colletion改造

集合类型的基类Collection新增了几个接口(J8开始):

default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
}

@Override
default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
}

default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
}

上面这个Splitrator(可分割迭代器) 主要也是为了服务于并行流。如果不是采用并行流,那么也会用到,只不过不执行分隔而已(存疑)。

JCP都提供了默认实现,并为这些默认实现提供了对应的静态工具类:StreamSupport,Spliterators.

如此,所有Collection的实现类都可以使用steam,parallelStream方法。

1.2.2、Map?

Map中没有和流有关的改造,主要是对函数式编程的改造,或者具体一点就是增加了郎打的实现。

default void forEach(BiConsumer<? super K, ? super V> action) 

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) 

default V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)

default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)

default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction)

注意:这几个都是默认函数,意味着子子孙孙类都不用特别覆盖,不用特别实现,可以直接用了。

这是是顺便提一下,和流没有什么直接关系,但他们都和函数式接口相关。

 

1.3、java提供的一些新工具类

这个比较多,包括前面提到的Collectors,StreamSupport,Spliterators等,更多的可以看java.util.stream,java.util.function,java.util下有关代码。

二、简单的Collector示例

基本上,要做流处理,工程师大部分情况下都可以不要写多余代码就能实现,这是因为这些操作有共性,JAVA

提供了基本的实现,这些实现又基本覆盖了可以想象的地方。

 

如前,新增的工具类和对现有Collection的改造,使得工程师基本只要学习郎打表达式写法、工具类的使用,即可完成流操作。

在很大部分情况下,其实就是简化为对郎打语法、Collectors的学习-掌握了结果,基本在想用的时候就能用起来。

要实现Stream是如何完成Collect等,可以看Stream的标准实现类:java.util.stream.ReferencePipeline<P_IN, P_OUT>

 

示例-模仿java.util.stream.Collectors.CollectorImpl实现toList

public class ChineseArticles {
    private static List<ArticleRecord> ARTICLES;
    static {
        ARTICLES=new ArrayList<>();
        ARTICLES.add(new ArticleRecord("唐","李白","侠客行","""
                十步杀一人 千里不留行 事了拂衣去 深藏身与名
                """));        
        ARTICLES.add(new ArticleRecord("唐","李贺","马诗","大漠沙如雪"));        
    }
    @SuppressWarnings("rawtypes")
    public static ArrayList createArrayList() {
        return new ArrayList();
    }
    
    public static  void add(ArrayList<ArticleRecord> t,ArticleRecord item) {
        t.add(item);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) {
        List<ArticleRecord> list = getStream().collect(Collectors.toList());
        System.out.println(list.toString());
        Collector co = new ArticleCollector(
                ChineseArticles::createArrayList,// 供应者,类型参数是 ArrayList<ArticleRecord>
                ChineseArticles::add, // 聚集着,双参数,分别是 ArrayList<ArticleRecord>,ArticleRecord
                null, // 合并者,双参数,可以不要
                null  // 完成者- 在非并行的情况下,可以不要
                );
        Stream<ArticleRecord> articleRecordStream = getStream();
        List<ArticleRecord> listUdf = (List<ArticleRecord>) articleRecordStream.collect(co);
        System.out.println(listUdf);
        
    }

    private static Stream<ArticleRecord> getStream() {
        return ARTICLES.stream().filter(article -> {
            return article.getDynasty().equals("唐") && article.getAuthor().equals("李贺");
        });
    }
}    

在这个非常简单的例子中,中间步骤是一个 filter,收尾操作是一个collect。

fitler接受Predicate的接口实现,由于Predicate是函数式接口,所以可以用郎打的方式写 :

article->{return article.getDynasty().equals("唐") && article.getAuthor().equals("李贺");}

无需单独定义一个Predicate实现类。

ArticleCollector是完全模仿CollectorImpl创建的一个Collectors.toList实现。

只不过属性characteristics=[Characteristics.IDENTITY_FINISH]

可以直接利用Collector的静态方法来返回,不必要大费周章

Collector.of(ChineseArticles::createArrayList, ChineseArticles::add, null, null,
                new Characteristics[] {Characteristics.IDENTITY_FINISH})

 

三、小结

1.java已经提供了比较完善的函数处理有关类和接口,理论上能够满足绝大部分的流处理场景

2.大部分时候,工程师只需要注意阅读Collectors,Stream,ReferencePipeline的代码,即可大体了解流是如何操作的

3.不鼓励工程师耗费大量的时间去阅读Stream的实现代码,比较复杂,对于一般的CRUD没有太大帮助。不过如果是为了提升专业技能还是有一定帮助的。

4.要熟练编写函数式代码,需要掌握函数式接口的几种实现方式  ,具体可见 JAVA基础之四-函数式接口和流的简介

或者JAVA基础之五-函数式接口的实现  (后面这个更全一些)

 

标签:Predicate,JAVA,Collectors,Stream,接口,之六,java,Collector
From: https://www.cnblogs.com/lzfhope/p/18399051

相关文章

  • Java 中图片转换为 Base64
    importjava.io.File;importjava.io.FileInputStream;importjava.io.IOException;importjava.util.Base64;publicclassImageToBase64Converter{publicstaticvoidmain(String[]args){//指定要转换的图片路径StringimagePath="path......
  • 【JAVA开源】基于Vue和SpringBoot的房产销售系统
    本文项目编号T028,文末自助获取源码\color{red}{T028,文末自助获取源码}......
  • 【JAVA开源】基于Vue和SpringBoot的在线视频教育平台
    本文项目编号T027,文末自助获取源码\color{red}{T027,文末自助获取源码}......
  • Java抽象类和接口的学习了解
    目录1.抽象类1.1抽象类概念1.2例子1.3 抽象类语法1.被abstract修饰的类--抽象类2.抽象类中被abstract修饰的方法--抽象方法,该方法不用给出具体的实现体3.当一个类中含有抽象方法时,该类必须要abstract修饰4.抽象类也是类,内部可以包含普通方法和属性,甚至构造方法......
  • [1018]基于JAVA的外贸服装智慧管理系统的设计与实现
    毕业设计(论文)开题报告表姓名学院专业班级题目基于JAVA的外贸服装智慧管理系统的设计与实现指导老师(一)选题的背景和意义背景部分:在全球经济一体化的大背景下,我国对外贸易行业持续繁荣发展,尤其是服装外贸领域,由于其更新迭代快、市场需求多样以及交易环节复杂等特点,对信......
  • [1008]基于JAVA的外卖数据智慧管理系统的设计与实现
    毕业设计(论文)开题报告表姓名学院专业班级题目基于JAVA的外卖数据智慧管理系统的设计与实现指导老师(一)选题的背景和意义选题背景与意义:在当今社会,随着互联网技术的快速发展以及生活节奏的加快,外卖行业已逐渐成为现代人生活中不可或缺的一部分。基于Java的外卖数据智慧......
  • [0989]基于JAVA的墙纸销售智慧管理系统的设计与实现
    毕业设计(论文)开题报告表姓名学院专业班级题目基于JAVA的墙纸销售智慧管理系统的设计与实现指导老师(一)选题的背景和意义选题背景与意义:随着信息技术的飞速发展和市场需求的日益精细化,企业对内部管理效率和决策精准性的要求不断提高。墙纸销售行业作......
  • [0980]基于JAVA的塑料膜进销存智慧管理系统的设计与实现
    毕业设计(论文)开题报告表姓名学院专业班级题目基于JAVA的塑料膜进销存智慧管理系统的设计与实现指导老师(一)选题的背景和意义选题背景与意义:随着我国工业生产和商品流通领域的快速发展,企业对供应链管理的需求日益增强,尤其是对于塑料膜这类广泛应用在......
  • javaScripe
    javaScripe:负责网页的行为。与Java不同的是,它不需要编译。1.js引入方式<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metanam......
  • java基础之集合框架 ArrayList和LinkedList的使用
     ArrayList和LinkedList的使用Link是接口,ArrayList和LinkedList是去实现这个接口,List接口没有LinkedList类的方法,如果需要LinkedList的方法就不能写成Listlist=newLinkedList(),只能LinkedListlist=newLinkedList()1.ArrayList的使用        1.1.ArrayList......