JDK8新特性——流(Stream)

Stream是什么?

Stream是Java8中新加入的api,更准确的说:

Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作 。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性.

Stream特性

  • 不存储数据
  • 不改变与数据
  • 延迟执行

Stream优点

  • 简化代码
  • 使用并行流可以利用多核特性,提升效率

Stream操作

stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。

常用api操作如下

Streamapi

中间操作

方法介绍
filter过滤流,过滤流中的元素,返回一个符合条件的Stream
map转换流,将一种类型的流转换为另外一种流。(mapToInt、mapToLong、mapToDouble 返回int、long、double基本类型对应的Stream)
flatMap一个或多个流合并成一个新流。(flatMapToInt、flatMapToLong、flatMapToDouble 返回对应的IntStream、LongStream、DoubleStream流)
distinct返回去重的Stream
sorted返回一个排序的Stream
peek主要用来查看流中元素的数据状态
limit返回前n个元素数据组成的Stream。属于短路操作
skip返回第n个元素后面数据组成的Stream

结束操作

方法介绍
forEach循环操作Stream中数据
toArray返回流中元素对应的数组对象
reduce聚合操作,用来做统计
collect聚合操作,封装目标数据
min、max、count聚合操作,最小值,最大值,总数量
anyMatch短路操作,有一个符合条件返回true
allMatch/noneMatch所有数据都符合条件返回true/所有数据都不符合条件返回true。
findFirst/findAny短路操作,获取第一个元素/短路操作,获取任一元素
forEachOrdered暗元素顺序执行循环操作

管道Stream使用示例

Stream的获取

  1. 通过集合Collection获取

    List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    Stream<Integer> stream = list.stream();
    
  2. 通过数组获取

    String[] array = {"are", "you", "ok"};
    Stream<String> stream = Arrays.stream(array);
    //对于基本类型数组的处理
    int[] array = {1, 2, 3, 4, 5};
    Stream<Integer> stream = Arrays.stream(array).boxed();
    //Arrays.stream(array)获取的是一个IntStream对象,boxed 方法用于将目前 Stream 中的基本类型装箱
    
  3. 直接通过值获取

    Stream<String> stream = Stream.of("are", "you", "ok");
    

Stream常用管道操作

  1. 筛选filter

    filter函数接收一个Lambda表达式作为参数,该表达式返回 boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为 true 的元素;

    //筛选出列表中的非空项
    List<String> list = Arrays.asList("are", "you", "", "ok");
    List<String> filted = list.stream()
        .filter(x -> !x.isEmpty())
        .collect(Collectors.toList());
    
  2. 去重diatinct

    //去除列表中的重复元素
    List<String> list = Arrays.asList("are", "you", "you", "ok");
    List<String> distincted = list.stream()
        .distinct()
        .collect(Collectors.toList());
    
  3. 截取limit

    截取流的前N个元素

    //获取Stream的前3个值
    List<String> list = Arrays.asList("are", "you", "fucking", "ok");
    List<String> distincted = list.stream()
        .limit(3)
        .collect(Collectors.toList());
    
  4. 跳过skip

    跳过流的前n个元素

    List<String> list = Arrays.asList("are", "you", "fucking", "ok");
    List<String> distincted = list.stream()
        .skip(2)
        .collect(Collectors.toList());
    
  5. 映射map

    对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
    如:将 list 中每一个 Integer类型元素自增后转化为 String类型。

    //将集合中的每一个元素+1,并转为字符串
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    List<String> result = list.stream()
        .map(x -> String.valueOf(++x))
        .collect(Collectors.toList());
    
  6. 合并多个流flatMap

    List<String> list1 = .....
    List<String> list2 = ...
    List<List<String>> list = Arrays.asList(list1,list2); 
    //将list中的list1,list2合并为一个List<String>
    List<String> listsum = list.stream()
                               .flatMap(List::stream)
                               .collect(Collectors.toList());
    

    以下一个实际的应用例子:列出 list 中各不相同的单词;

    List<String> list = new ArrayList<String>();
    list.add("I am a boy");
    list.add("Variety is the spice of life");
    list.add("There is no royal road to learning");
    
    list.stream().map(line -> line.split(" "))    //将每一个项分词,并映射为数组
        .flatMap(Arrays::stream)       //将每一个分项数组组合并到主流中,形成一个包含所有分项数组的									 //总数组流
        .distinct()                   //去重
        .forEach(System.out::println);   //打印
    
  7. 匹配元素anyMatchallMatch

  • 是否匹配任意元素antMatch

    anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。

    //判断流中是否含有>10的项
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    boolean result = list.stream()
        .anyMatch(x -> x > 10);
    
  • 是否匹配所有元素allMatch

    allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。

    //判断流中是否全部>5
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    boolean result = list.stream().allMatch(x -> x > 5);
    
  • 是否未匹配所有元素noneMatch

    noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:

    //判断流中是否 全部不满足 >5
    List<Integer> list = Arrays.asList(1,2,3,4,5,6);
    boolean result = list.stream()
                         .noneMatch(x->x>5);
    
  1. 获取元素

    • 获取任意元素findAny

    findAny从流中随机选出 一个元素出来,它返回一个Optional类型的元素。

            List<Integer> list = Arrays.asList(1,2,3,4,5,6);
            Optional<Integer> result = list.stream().findAny();
    
            if(result.isPresent()){
                Integer randValue = result.get();
            }
            //or:
            Integer randValue1 = result.orElse(0);
    
            //合并的调用方式
            Integer randValue2 = list.stream().findAny().orElse(0);
    
    • 获取第一个元素findFirst
    Optional<Integer> result = list.stream().findFirst();
    
  2. 归约

    归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出;

    //获取一个整型列表的最大值,最小值
    Random random = new Random();
    List<Integer> list = random.ints(1, 100).limit(50).boxed().collect(Collectors.toCollection(ArrayList::new));
    int max = list.stream().max(Integer::compare).orElse(-1);   //获取最大值
    int min = list.stream().min(Integer::compare).orElse(-1);   //获取最小值
    System.out.println(max);
    System.out.println(min);
    
    //使用基于数据流的方式,将流装载相应的 SummaryStatistics 来进行归约计算,可以实现更多的操作;
    IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
    int max1 = stats.getMax();        //获取最大值
    int min1 = stats.getMin();        //获取最小值
    double sum = stats.getSum();    //获取总值
    double avg = stats.getAverage();  //获取平均值
    long count = stats.getCount();     //获取总数量
    List<? extends Number> numbers = Arrays.asList(max1, min1, sum, avg, count);
    numbers.forEach(System.out::println);
    
  3. 遍历流forEach

    据流提供了新的forEach方法遍历该流中的每个元素,方法参数为一个Lambda表达式,用于对每一个遍历的元素执行的操作;

    //输出10个随机数
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    
  4. 排序sorted

    sorted方法用来流排序,默认升序排布,可以使用一个 Comparator 作为参数来实现自定义排序;

    //输出10个排序好的随机数
    Random random = new Random();
    random.ints().limit(10).sorted().forEach(System.out::println);   //升序
    random.ints().limit(10).sorted(x1,x2 -> Integer.compare(x2, x1)).forEach(System.out::println);   //降序
    

Stream转换为Collection、Array、String

Stream 可以通过 Collector 收集器,将其转化为 Array,Collection,Map,String;

  1. Stream->数组

    //普通转换
    Stream<String> stream = Stream.of("are","you","ok");
    String[] array = stream().toArray();
    
    //涉及拆箱、装箱操作的转换
    Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    int[] array = stream.mapToInt(x->x).toArray();
    Integer[] array = stream.toArray(Integer[]::new);
    
    //将 List<Inetegr> 转化为 String[]
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    String[] array = list.stream().map(String::valueOf).toArray(String[]::new);
    
  2. Stream -> List

    List<Integer> list1 = stream.collect(Collectors.toList());
    List<Integer> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    
  3. Stream ->Set

    Set<Integer> set = stream.collect(Collectors.toCollection(Set::new));
    
  4. Stream ->Stack

    Stack<Integer> stack = stream.collect(Collectors.toCollection(Stack::new));
    
  5. Stream ->Map

    Map<Integer, String> map = Stream.of("are","you","ok").collect(Collectors.toMap( s -> s.hashCode(), s -> s));
    
  6. tream -> String

    Stream 可以很方便转化为 String,利用这一个特性,可以十分方便地将一个 Collection(List,Set等)转化为使用某个标点符号分隔的字符串;

    //将 List 转化为使用 “,” 分隔的字符串
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
    String str = list.stream().map(x->x.toString()).collect(Collectors.joining(","));
    System.out.println(str);
    //输出: 1,2,3,4,5,6,7
    

对Map使用Stream

虽然 JDK8 的 Stream API 不直接支持 Map,但是我们可以通过对 Map 的 entrySet,keySet,valueColletion 生成 Stream 来进行曲线救国,如下示例:

Map<Integer,String> map = new HashMap<Integer,String>(){{
       put(1,"are");
       put(2,"you");
       put(3,"ok"); }};
 
map.entrySet().forEach(System.out::println);  //遍历key-value
int randomKey = map.keySet().stream().findAny().orElse(-1);  //随机取出一个key
String values = map.values().stream().distinct().collect(Collectors.joining(","));  //将value去重后组装成使用“,”分隔的字符串

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议