代码简洁之路——还在使用fori循环么?尝试用Java 的Stream来处理集合吧

新的循环

上一篇介绍过函数式接口(@FunctionalInterface)和Lambda表达式。而伴随他们出现Java 8提供了一个强大的工具来实现对集合的操作——流(Stream)。配合流我们以前需要很多行代码才能完成的集合操作现在只需要三五行甚至一行就能完成。

流 stream

关于流JAVA提供了两种创建方式使用stream()创建串行流和使用parallelStream()创建并行流。这里都是基于串行流的内容了。关于并行流对于有些功能需要额外操作这里就没时间介绍了。

就像之前介绍方法引用,既然可以将方法作为参数进行传递,在相同的方法因为我们不同的方法逻辑能实现更加复杂的业务逻辑,这里我也只介绍了我自己使用过的简单的逻辑。假如之前了解过函数式接口(@FunctionalInterface)的使用会很轻松的发现不同参数的使用方式,所以这里希望大家都能去了解学习下函数式接口的用法,不要拘泥于固定的使用方式。

什么是流

流(Stream)是Java 8 API添加了一个新的抽象。Stream提供的大量对集合操作的支持,尤其是配合函数式接口和Lambda表达式,将之前繁琐的循环操作进行了压缩。让循环变得干净和简洁。

流如何处理数据

使用流处理数据的时候将处理的过程看成是集合中的数据在管道中流动的操作,通过不一样的管道对上一级传递过来的数据进行不同的处理。

整个元素在管道中需要经过中间操作(intermediate operation)的处理然后由最终操作(terminal operation)输出结果。

经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

graph LR
集合==>流

subgraph 管道

流 --> filter

filter --> sorted

sorted --> collect
end
collect==>输出结果

通过流对集合中元素据的筛选操作: stream().filter

filter 方法用于通过设置的条件过滤出元素。

此方法需要提供一个实现了Predicate接口的逻辑,在此逻辑中执行过滤业务

Stream<T> filter(Predicate<? super T> predicate)

下面代码可以过滤出name=test的数据。最终返回一个符合设置的过滤规则的集合。

    public static List<DataBean> filter(List<DataBean> list) {
        List<DataBean> beanList =
            list.stream().filter(item -> "test".equals(item.getName())).collect(Collectors.toList());
        return beanList;
    }

使用流获取集合中指定部分的数据: stream().skip和stream().limit

skip和limit的配合可以实现类似分页操作中获取指定页码内数据方式,skip是将指定索引位置之后的数据加入流中,limit是将指定条目数的数据加入流中。当然两个参数也可以单独使用实现自己的业务逻辑

这两个方法都只需要设置一个数字即可实现逻辑

    Stream<T> skip(long n);
    
    Stream<T> limit(long maxSize)

下面的代码可以获取索引2(索引从0开始)开始的两个元素

    public static List<DataBean> skipAndLimit(List<DataBean> list) {
        List<DataBean> beanList =
            list.stream().skip(2).limit(2).collect(Collectors.toList());
        return beanList;
    }

通过流获取集合中的极值:stream().max 和 stream().min

max 和 min 根据指定的规则获取元素中最大或者最小的元素

max 和 min方法都需要提供Comparator接口的实现,用来判断相关大小的逻辑,同时需要注意此接口返回的不再是Stream对象

Optional<T> min(Comparator<? super T> comparator);

Optional<T> max(Comparator<? super T> comparator);

下面代码实现了根据getAge()方法的结果,来获取值最大的元素

    public static DataBean max(List<DataBean> list) {
        DataBean dataBean = list.stream().max(Comparator.comparingInt(DataBean::getAge)).get();
        return dataBean;
    }

下面代码实现了根据getAge()方法的结果,来获取值最小的元素

    public static DataBean min(List<DataBean> list) {
        DataBean dataBean = list.stream().min(Comparator.comparingInt(DataBean::getAge)).get();
        return dataBean;
    }

通过流获取集合中元素进行转换

stream().map

map实现的是数据的映射,此方法允许我们将集合中元素根据自定义的逻辑映射到我们希望的类型中。使用此方法我们可以很简单的将元素中数据提取出来,或者转换为另外一种类型进行输出。

map方法需要提供一个进行数据映射的Function接口实现

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

下面代码会获取集合中对象的getName()方法返回值的集合

    public static List<String> map(List<DataBean> list) {
        List<String> strings = list.stream().map(item -> item.getName()).collect(Collectors.toList());
        return strings;
    }

stream().toArray

toArray可以将流中数据很简单的转换为数组

toArray提供了两个方法,而其中无参的方法再其实现类中代码为toArray(Object[]::new)最终返回的时候object的数组。

    Object[] toArray();

    <A> A[] toArray(IntFunction<A[]> generator);
    public static DataBean[] toArray(List<DataBean> list) {
        DataBean[] objects = list.stream().toArray(DataBean[]::new);
        return objects;
    }

通过流对集合中元素进行排序:stream().sorted

sorted方法使流可以根据指定的规则对数据进行排序

sorted同样提供了两个方法

    Stream<T> sorted();

    Stream<T> sorted(Comparator<? super T> comparator);

而无参的排序方法最终使用下面的排序逻辑

// 返回按照大小写字母排序的Comparator
Comparator<? super T> comp = (Comparator<? super T>) Comparator.naturalOrder()

下面的代码会将数据根据getType()方法返回值大小进行排序

    public static List<DataBean> sorted(List<DataBean> list) {
        List<DataBean> beanList =
            list.stream().sorted(Comparator.comparingInt(DataBean::getType)).collect(Collectors.toList());
        return beanList;
    }

ps.对于排序规则可以使用方法引用这种变得更加简洁,当然对于一些很少使用此特性的人使用起来可能会有些难以识别,使用下面也是可以的

        List<DataBean> beanList =
            list.stream().sorted((o1,o2) -> {return o1.getType() - o2.getType();}).collect(Collectors.toList());

通过流对集合中元素进行修改: stream().peek

很类似map的循环不过不同之处,map操作一般会希望修改流中元素的类型,而peek一般只是希望修改流中元素的内容而不尝试修改其类型。

peek方法需要提供一个Consumer接口实现来进行类操作逻辑

Stream<T> peek(Consumer<? super T> action);

下面的代码使用流中元素的setName()方法,参数为peek,来对元素进行修改

    public static List<DataBean> peek(List<DataBean> list) {
        List<DataBean> beanList =
            list.stream().peek(item -> item.setName("peek")).collect(Collectors.toList());
        return beanList;
    }

使用流对集合数据进行去重: stream().distinct

distinct 用来对流中的数据进行去重,但是需要注意的是distinct并没有提供支持自定义逻辑的参数,其默认使用hashcode和equals进行判断

distinct并未支持去重逻辑的自定义,其默认使用hashcode和equals进行判断

Stream<T> distinct();

下面的代码直接去除流中重复数据

    public static List<DataBean> distinct(List<DataBean> list) {
        List<DataBean> beanList =
            list.stream().distinct().collect(Collectors.toList());
        return beanList;
    }

使用流对集合数据进行检查: stream().match

*match提供了三个方法,分别是用来检测流中是否全部存在、部分存在、不存在指定规则的数据。

此三个接口都需要提供匹配规则进行判断

    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);

需要注意:stream().allMatch(…) 对于空集合,不管判断条件是什么,直接返回 true。

下面代码用来判断流中元素属性和test的匹配情况:allMatch完全匹配,anyMatch部分匹配,noneMatch完全不匹配

    public static boolean match(List<DataBean> list) {
        boolean allMatch = list.stream().allMatch(item -> "test".equals(item.getName()));
        boolean anyMatch = list.stream().anyMatch(item -> "test".equals(item.getName()));
        boolean noneMatch = list.stream().noneMatch(item -> "test".equals(item.getName()));
        return allMatch;
    }

使用流对集合数据进行归一操作: stream().reduce

reduce 方法允许开发者将集合中的数据通过设置的规则,最终返回唯一的结果。其使用的BinaryOperator接口作为参数,这样我们可以使用的自定义规则可以是相加、相见、最大、最小等操作

reduce是一个复杂的接口,每种参数都可以实现不同的归一操作,这里我只介绍了自己使用过的,其他归一操作,大家可以后面自己尝试下。

    T reduce(T identity, BinaryOperator<T> accumulator);
    
    Optional<T> reduce(BinaryOperator<T> accumulator);
    
    <U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

下面代码中将集合中元素的getAge()方法的返回值聚合起来,然后设置到外部创建的dataBean对象中

    public static DataBean reduceDataBean(List<DataBean> list) {
        DataBean dataBean = new DataBean();
        dataBean.setName("聚合结果");
        dataBean =
            list.stream().reduce(dataBean, (o1, o2) -> {
                o1.setAge(o1.getAge() + o2.getAge());
                return o1;
            });
        return dataBean;
    }

使用流的循环

stream()提供的循环,这两个和单独使用forEach和Iterator。在使用串行流的时候,他们在使用方面上其实差别不大。

stream().forEach

循环

    public static List<DataBean> forEach(List<DataBean> list) {
        list.stream().forEach(item -> item.setName("forEach"));
        return list;
    }

stream().iterator

迭代器

    public static void iterator(List<Integer> list) {
        Iterator<Integer> iterator = list.stream().iterator();
        while (iterator.hasNext()) {
            Integer next = iterator.next();
        }
    }

collectiors

Stream中的数据最终要生成我们使用的集合数据,所以在Stream提供了collect终止操作将流的数据进行输出

<R, A> R collect(Collector<? super T, A, R> collector)

其方法最终使用Collector进行数据输出,而collectiors就是Collector的实现,其提供了一系列功能来输出最终结果。

使用collectiors获取集合中的极值

Collectors.maxBy

Collectors.maxBy 可以获取指定规则下集合最大结果的元素

根据元素的getType()的结果,获取其最大元素

    public static DataBean maxBy(List<DataBean> list) {
        /*DataBean dataBean =
            list.stream().collect(Collectors.maxBy(((Comparator.comparingInt(DataBean::getType))))).get();*/
        DataBean dataBean = list.stream().collect(Collectors.maxBy((((o1, o2) -> o1.getType() - o2.getType())))).get();
        return dataBean;
    }

Collectors.minBy

Collectors.maxBy 可以获取指定规则下集合最小结果的元素

下面的代码根据元素的getType()的结果,获取其最小元素

   public static DataBean minBy(List<DataBean> list) {
        /*DataBean dataBean =
            list.stream().collect(Collectors.minBy(((Comparator.comparingInt(DataBean::getType))))).get();*/
        DataBean dataBean = list.stream().collect(Collectors.minBy((((o1, o2) -> o1.getType() - o2.getType())))).get();
        return dataBean;
    }

使用collectiors对集合中的数据进行分组聚合操作

Collectors.groupBy

Collectors.groupingBy 方法可以将流中的数据进行分组,根据开发者提供的分组规则得到的结果对数据进行分组。最终得到key为分组规则的值、value为符合此值的元素的集合的一个Map结果

Collectors.groupBy提供了多个方法,其基础方法需要提供一个进行排序的Function接口实现,其他则是对输出内容的处理

    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }
    
    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }
    
    public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {

下面的代码将流中数据根据getType()方法的结果进行分组然后获得结果

    public static Map<Integer, List<DataBean>> groupBy(List<DataBean> list) {
        Map<Integer, List<DataBean>> listMap =
            list.stream().collect(Collectors.groupingBy(DataBean::getType));
        return listMap;
    }

Collectors.partitioningBy

partitioningBy同样提供了多个方法,正常我们使用的时候一般只使用下面的内容,此时需要传递Predicate接口是实现,返回一个boolean类型。

    public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
    }

partitioningBy也会将集合进行分组,但是此方法会根据规则是否匹配将数据切分为两部分 key为false和key为true两部分

下面代码将集合中的元素根据"test".equals(item.getName()) 规则划分为两部分然后返回对应Map

    public static Map<Boolean, List<DataBean>> partitioningBy(List<DataBean> list) {
        Map<Boolean, List<DataBean>> booleanListMap =
            list.stream().collect(Collectors.partitioningBy(item -> "test".equals(item.getName())));
        return booleanListMap;
    }

Collectors.counting

Collectors.counting 用来计算集合中元素的数量,其单独使用collect(Collectors.counting())可以获取集合中元素的数量。配合groupingBy我们可以实现对分组中每组数据进行统计

下面的代码将流中数据根据getType()方法的结果进行分组然后使用Collectors.counting()获得每个分组内元素数量

    public static Map<Integer, Long> groupByCount(List<DataBean> list) {
        Map<Integer, Long> collect =
            list.stream().collect(Collectors.groupingBy(DataBean::getType, Collectors.counting()));
        return collect;
    }

Collectors.summingInt

Collectors.summingInt 可以将集合中的元素根据规则计算的结果进行求和,其单独使用collect(Collectors.summingInt())可以获取集合中元素的计算结果的和。配合groupingBy我们可以实现对分组中每组数据进行求和

下面的代码将流中数据根据getType()方法的结果进行分组然后使用Collectors.summingInt()将每个元素getAge()方法的返回结果进行相加

    public static Map<Integer, Integer> summingInt(List<DataBean> list) {
        Map<Integer, Integer> map =
            list.stream().collect(Collectors.groupingBy(DataBean::getType, Collectors.summingInt(DataBean::getAge)));
        return map;
    }

Collectors.averagingInt

Collectors.averagingInt 可以将集合中的元素根据规则计算的结果进行求平均,配合Collectors.groupingBy的嵌套使用,我们可以得到每个元素中指定规则下计算出来结果的平均值。

下面的代码将流中数据根据getType()方法的结果进行分组然后使用Collectors.summingInt()将每个元素getAge()方法的返回结果进行求平均

    public static Map<Integer, Double> averagingDouble(List<DataBean> list) {
        Map<Integer, Double> map =
            list.stream().collect(Collectors.groupingBy(DataBean::getType, Collectors.averagingInt(DataBean::getType)));
        return map;
    }

使用collectiors对集合进行归一操作

数据归一:Collectors.reducing

类似stream().reduce方法,Collectors.reducing实现将数据进行归一操作。

reducing提供了三种方法,这里只介绍下第三个方法,其接受三个参数,第一个参数为最终返回的对象,第二个参数为数据的映射处理,此步骤可以将原始数据转换为需要处理的目标类型,然后最后一个参数来确定被转换后的数据被处理的方式


    public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
        ...
    }
    
    public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {
            ...
    }

    public static <T, U> Collector<T, ?, U> reducing(U identity,Function<? super T, ? extends U> mapper,
    BinaryOperator<U> op){
        ...
    }

下面的代码将原始集合根据item.getType()进行分组,然后在reducing中调用元素的getSome方法返回一个数字,然后将数字求和,最终返回每个分组各个元素的getSome方法返回值的和

    public static Map<Integer, Integer> reducing(List<DataBean> list) {
        Map<Integer, Integer> collect = list.stream().collect(
            Collectors.groupingBy(item -> item.getType(),
            Collectors.reducing(0, DataBean::getSome, Integer::sum)
            ));
        return collect;
    }

字符串拼接:Collectors.joining

Collectors.joining 可以将集合中每个元素根据规则返回的结果进行字符串拼接

下面代码根据元素getName()方法返回的结果进行字符串拼接,并返回结果

    public static String join(List<DataBean> list) {
        String collect = list.stream().map(item -> item.getName())
            .collect(Collectors.joining(",", "", ""));
        return collect;
    }

使用collectiors获取集合中元素元素进行转换:Collectors.mapping

此方法类似于Map操作,将集合中元素,根据指定规则转换为需要的数据类型进行输出

下面代码获取集合元素的getName()方法的结果,然后收集为List返回

    public static List<String> mapping(List<DataBean> list) {
        List<String> collect =
            list.stream().collect(Collectors.mapping(item -> item.getName(), Collectors.toList()));
        return collect;
    }

Collectors.collectingAndThen

Collectors.collectingAndThen 允许在对流中数据根据规则进行收集之后,再次进行处理,先用map提取出集合中元素getAge()方法的返回结果,

下面代码使用Collectors.collectingAndThen,首先获取其最大结果,然后拼装结果语句

    public static String collectingAndThen(List<DataBean> list) {
        String str = list.stream().map(DataBean::getAge)
            .collect(Collectors.collectingAndThen(
                Collectors.maxBy((Comparator.comparingInt(item -> item))),
                item -> "最大的数字是:" + item.get())
            );
        return str;
    }

结果输出

Collectors.toList和Collectors.toSet

Collectors.toList 将流中数据输出到一个List中,默认使用ArrayList。如果需要更多地控制返回的List,请使用toCollection(Supplier)

    public static List<DataBean> toList(List<DataBean> list) {
        return list.stream().collect(Collectors.toList());
    }

Collectors.toMap

Collectors.toMap需要传递两个接口实现,分别是key创建的逻辑以及value创建的逻辑

toMap接收2个参数一个是来确定key生成的规则一个是用来确定value生成的规则

   public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

下面代码输出了一个key为getName输出值,value为数据本地的map

    public static Map<String, DataBean> toMap(List<DataBean> list) {
        Map<String, DataBean> collect = list.stream().collect(Collectors.toMap(o -> o.getName(), o -> o));
        return collect;
    }

后话

对于一个已经发布五六年的版本,今天再去整理这些内容显得有些晚了。Stream对集合的支持虽然已经存在很久。但是在我实际开发的感受即使已经发布了很久但是很多人这些新的特性尤其是Lambda表达式都敬而远之。可能很多人都担心代码越简洁,理解起来越困难的问题。但是我个人的体会,一个新功能你不去尝试它,那它永远都很难理解。而我平时开发的感受是:代码越多、问题越多。官方既然提供了这么好的工具,使用者就应该好好利用。在我看来代码要想做的简洁那就需要让每一行代码都更加有价值。


个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。

你可能感兴趣的:(JAVA,#,代码简洁之路)