JAVA8新特性:Stream类


在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

1.Stream流的引入

需求:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

原始集合操作

package com.lb;

import java.util.ArrayList;
import java.util.List;

/**
 * 1. 首先筛选所有姓张的人;
 * 2. 然后筛选名字有三个字的人;
 * 3. 最后进行对结果进行打印输出。
 */
public class Demo02NormalFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        List<String> zhangList = new ArrayList<>();

        //首先筛选所有姓张的人
        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }

        //然后筛选名字有三个字的人
        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                shortList.add(name);
            }
        }

        //最后进行对结果进行打印输出
        for (String name : shortList) {
            System.out.println(name);
        }
    }
}

Stream流的操作

package com.lb;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.util.ArrayList;
import java.util.List;

/**
 * 1. 首先筛选所有姓张的人;
 * 2. 然后筛选名字有三个字的人;
 * 3. 最后进行对结果进行打印输出。
 */
public class Demo02NormalFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
    }
}

Stream流相对于原始集合操作的优点: 格式简洁, 逻辑清晰。

2.获取流

        // 集合  ->  stream()
        // List
        ArrayList<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        // Set
        HashSet<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        // Map
        HashMap<String, String> map = new HashMap<>();
        // 所有键的集合
        Stream<String> stream3 = map.keySet().stream();
        // 所有值的集合
        Stream<String> stream4 = map.values().stream();
        // 所有键值对的集合
        Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();


        // 数组 -> Stream.of   Arrays
        String[] arr = {"abc"};
        Stream<String> stream6 = Stream.of(arr);

        // 一堆数据 -> Stream.of
        Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5);

注意事项

  1. 获取数组对应的流, 数组需要是引用数据类型的数组
  2. Stream.of()传入多个参数的时候, 参数需要是同一种数据类型

3.常用方法

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder
    那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。
  • 非终结方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余 方法均为非终结方法。)

备注:本小节之外的更多方法,请自行参考API文档

3.1过滤:filte

对流中的数据进行判断, 如果结果为true, 就将这个数据留下; 如果结果为false, 就将这个数据从流中删除。

基本使用

public class StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
        //在这里通过Lambda表达式来指定了筛选的条件:必须姓张。
        Stream<String> result = original.filter(s -> s.startsWith("张"));
        
        //收集Stream结果到数组中并指定为String类型
        //同:String[] strings= result.toArray(s -> new String[s]);
        //String[] strings = result.toArray(String[]::new);
    }
}

3.2 统计个数:count

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:

long count()

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:

public class StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");

        //在这里通过Lambda表达式来指定了筛选的条件:必须姓张。
        Stream<String> result = original.filter(s -> s.startsWith("张"));

        //count() 该方法返回一个long值代表元素个数
        long count = result.count();
        
        //收集Stream结果到数组中并指定为String类型
        //同:String[] strings= result.toArray(s -> new String[s]);
        //String[] strings = result.toArray(String[]::new);
    }
}

3.3 取用前几个:limit

limit 方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxSize);

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:

  public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
        Stream<String> result = original.limit(2);
        System.out.println(result.count());//2
    }

3.4跳过前几个:skip

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

  public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "周芷若", "张三丰");
        //skip跳过前几个
        Stream<String> result = original.skip(2);
        System.out.println(result.count());//1
    }

3.5映射:map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:

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

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

复习Function接口

此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为:

R apply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

基本使用

Stream流中的 map 方法基本使用的代码如:

    public static void main(String[] args) {
		 Stream<String> stringStream = Stream.of("1", "2", "3");
        //同:Stream<Integer> integerStream = stringStream.map(Integer::new);
        Stream<Integer> integerStream = stringStream.map(s -> Integer.parseInt(s));
	}

3.6组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的

该方法的基本使用代码如:

 public static void main(String[] args) {
        Stream<String> streamA = Stream.of("1", "2", "3");
        Stream<String> streamB = Stream.of("4", "5", "6");
        Stream<String> result = Stream.concat(streamA, streamB);
        System.out.println(result.collect(Collectors.toList()));//[1, 2, 3, 4, 5, 6]
 }

3.7逐一处理:forEach

虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是
被有序执行的。

void forEach(Consumer<? super T> action);

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:

       public static void main(String[] args) {

        Stream<String> streamA = Stream.of("1", "2", "3");
        //同: streamA.forEach(s -> System.out.println(s));
        streamA.forEach(System.out::println);
     }   

在这里,方法引用 System.out::println 就是一个 Consumer 函数式接口的示例

3.8收集Stream结果(collect 方法)

对流操作完成之后,如果需要将其结果进行收集,例如获取对应的集合、数组等,如何操作?

收集到集合中

Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪种
集合中。幸运的是, java.util.stream.Collectors 类提供一些方法,可以作为 Collector 接口的实例:

public static <T> Collector<T, ?, List<T>> toList() :转换为 List 集合。
public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。

下面是这两个方法的基本使用代码:

  public static void main(String[] args) {
        Stream<String> streamA = Stream.of("1", "2", "3");
        List<String> list = streamA.collect(Collectors.toList());
        Set<String> set = streamA.collect(Collectors.toSet());
  }      

收集到数组中

Stream提供 toArray 方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:

Object[] toArray();

其使用场景如:

    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("1", "2", "3");
        //收集到数组中
        Object[] objects = streamA.toArray();
    }    
解决泛型数组问题

有了Lambda和方法引用之后,可以使用 toArray 方法的另一种重载形式传递一个 IntFunction<A[]> 的函数,继
而从外面指定泛型参数。方法签名:

<A> A[] toArray(IntFunction<A[]> generator);

有了它,上例代码中不再局限于 Object[] 结果,而可以得到 String[] 结果:

    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("1", "2", "3");
        //收集到数组中,指定类型
        //同:String[] strings = streamA.toArray(s->new String[s]);
        String[] strings = streamA.toArray(String[]::new);
    }    

既然数组也是有构造器的,那么传递一个数组的构造器引用即可。

备注:Java仍然没有泛型数组,原因同样是泛型擦除。

3.9并发流

当需要对存在于集合或数组中的若干元素进行并发操作时,简直就是噩梦!我们需要仔细考虑多线程环境下的原子
性、竞争甚至锁问题,即便是 java.util.concurrent.ConcurrentMap<K, V> 接口也必须谨慎地正确使用。
而对于Stream流来说,这很简单。

转换为并发流

Stream 的父接口 java.util.stream.BaseStream 中定义了一个 parallel 方法:

S parallel();

只需要在流上调用一下无参数的 parallel 方法,那么当前流即可变身成为支持并发操作的流,返回值仍然为
Stream 类型。例如:

public static void main(String[] args) {
	Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50).parallel();
}

直接获取并发流

在通过集合获取流时,也可以直接调用 parallelStream 方法来直接获取支持并发操作的流。方法定义为:

default Stream<E> parallelStream() {...}
public static void main(String[] args) {
    Collection<String> coll = new ArrayList<>();
    Stream<String> stream = coll.parallelStream();
}

使用并发流

多次执行下面这段代码,结果的顺序在很大概率上是不一定的:
forEach该方法并不保证元素的逐一消费动作在流中是
被有序执行的。

    public static void main(String[] args) {
        Stream.of(10,20,30,40,50,60,70,80,90,100).parallel().forEach(System.out::println);
        //同:
        //获取流
        //Stream<Integer> integerStream = Stream.of(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
        //转为并发流
        //Stream<Integer> parallel = integerStream.parallel();
        //遍历打印
        //parallel.forEach(s-> System.out.println(s));
     }

3.10 总结:函数拼接与终结方法

在上述介绍的各种方法中,凡是返回值仍然为 Stream 接口的为函数拼接方法,它们支持链式调用;而返回值不再
为 Stream 接口的为终结方法,不再支持链式调用。如下表所示:
在这里插入图片描述

4.使用案列

   List<User> users = new ArrayList();
   List<Long> list = users.stream().map(s -> s.getId()).collect(Collectors.toList());
     List<WechatReceptMsg> wechatReceptMsgs = wechatReceptMsgService.selectByOpenidForList(openid);
        //
        List<MsgDetails> kf = wechatReceptMsgs.stream().map(wechatReceptMsg -> {
            MsgDetails msgDetails = new MsgDetails();
            BeanUtils.copyProperties(wechatReceptMsg, msgDetails);
            msgDetails.setIdentification("me");
            msgDetails.setHeadimgurl("/admin/chat/img/icon01.png");
            msgDetails.setOpenid(openid);
            return msgDetails;
        }).collect(Collectors.toList());
     //去重
        ArrayList<AcctPaymentScheduleDO> acctPaymentSchedule = acctPaymentScheduleA.stream()
                .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(f -> f.getSerialNo()))), ArrayList::new));
        //提前结清需要返还当时使用免息券的金额(免息返还金额)
        return acctPaymentSchedule.stream().filter(t -> t.getInterestFreeReturnAmount() != null).map(AcctPaymentScheduleDO::getInterestFreeReturnAmount).reduce((a, b) -> {
                return a.add(b);
        }).get();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值