JAVA8专题-Stream流操作详解

JAVA8专题-Stream流操作

在JAVA中,涉及到对数组、Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理。

例如,现在有这么一个需求

shell 复制代码
从给定语句中返回单词长度大于3的单词列表,按长度倒序输出,最多返回3个

在JAVA7及之前的代码中,我们会可以照如下的方式进行实现:

java 复制代码
public List<String> sortGetTop3LongWords(@NotNull String sentenceStr) {
    // 先切割句子,获取具体的单词信息
    String[] words = sentenceStr.split(" ");
    List<String> wordList = new ArrayList<>();
    // 循环判断单词的长度,先过滤出符合长度要求的单词
    for (String word : words) {
        if (word.length() > 3) {
            wordList.add(word);
        }
    }
    // 对符合条件的列表按照长度进行排序
    wordList.sort((o1, o2) -> o2.length() - o1.length());
    // 判断list结果长度,如果大于3则截取前三个数据的子list返回
    if (wordList.size() > 3) {
        wordList = wordList.subList(0, 3);
    }
    return wordList;
}

在JAVA8及之后的版本中,借助Stream流,我们可以更加优雅的写出如下代码

java 复制代码
public List<String> sortGetTop3LongWordsByStream(@NotNull String sentenceStr) {
    return Arrays.stream(sentenceStr.split(" "))
            .filter(word -> word.length() > 5)
            .sorted((o1, o2) -> o2.length() - o1.length())
            .limit(3)
            .collect(Collectors.toList());
}

Stream的实现方式代码更加简洁、一气呵成。很多的同学在代码中也经常使用Stream流,但是对Stream流的认知往往也是仅限于会一些简单的filter、map、collect等操作,但JAVA的Stream可以适用的场景与能力远不止这些。

Stream简介

可以将Stream流操作分为3种类型:

  • 创建Stream
  • Stream中间处理
  • 终止Steam
    每个Stream管道操作类型都包含若干API方法,先列举下各个API方法的功能介绍。
    主要负责新建一个Stream流,或者基于现有的数组、List、Set、Map等集合类型对象创建出新的Stream流。

创建Stream

API 功能说明
stream() 创建出一个新的stream串行流对象
parallelStream() 创建出一个可并行执行的stream流对象
Stream.of() 通过给定的一系列元素创建一个新的Stream串行流对象

Stream中间处理

负责对Stream进行处理操作,并返回一个新的Stream对象,中间管道操作可以进行叠加。

API 功能说明
filter() 按照条件过滤符合要求的元素, 返回新的stream流
map() 将已有元素转换为另一个对象类型,一对一逻辑,返回新的stream流
flatMap() 将已有元素转换为另一个对象类型,一对多逻辑,即原来一个元素对象可能会转换为1个或者多个新类型的元素,返回新的stream流
limit() 保留集合前面指定个数的元素,返回新的stream流
skip() 跳过集合前面指定个数的元素,返回新的stream流
concat() 将两个流的数据合并起来为一个新的流,返回新的stream流
distinct() 对Stream中所有元素进行去重,返回新的stream流
sorted() 对stream中所有的元素按照指定规则进行排序,返回新的stream流
peek() 对stream流中的每个元素进行逐个遍历处理,返回处理后的stream流

终止Steam

通过终止管道操作之后,Stream流将会结束,最后可能会执行某些逻辑处理,或者是按照要求返回某些执行后的结果数据。

API 功能说明
count() 返回stream处理后最终的元素个数
max() 返回stream处理后的元素最大值
min() 返回stream处理后的元素最小值
findFirst() 找到第一个符合条件的元素时则终止流处理
findAny() 找到任何一个符合条件的元素时则退出流处理,这个对于串行流时与findFirst相同,对于并行流时比较高效,任何分片中找到都会终止后续计算逻辑
anyMatch() 返回一个boolean值,类似于isContains(),用于判断是否有符合条件的元素
allMatch() 返回一个boolean值,用于判断是否所有元素都符合条件
noneMatch() 返回一个boolean值, 用于判断是否所有元素都不符合条件
collect() 将流转换为指定的类型,通过Collectors进行指定
toArray() 将流转换为数组
iterator() 将流转换为Iterator对象
foreach() 无返回值,对元素进行逐个遍历,然后执行给定的处理逻辑

Stream使用示例

map使用:一对一转换

java 复制代码
public void stringToIntMap() {
    List<String> ids = Arrays.asList("1", "2", "3", "4", "5", "6", "7");
    // 使用流操作返回list集合
    List<User> resultList = ids.stream()
            .map(id -> {
                User user = new User();
                user.setId(id);
                return user;
            })
            .collect(Collectors.toList());
    System.out.println(resultList);
}

[User{id='1'}, 
 User{id='2'},
 User{id='3'}, 
 User{id='4'}, 
 User{id='5'}, 
 User{id='6'}, 
 User{id='7'}]

flatMap使用

一个语句,需要将句子中每个单词都提取出来得到一个所有单词列表。这种情况用map就搞不定了,需要flatMap

java 复制代码
    public static void main(String[] args) {
        List<String> sentenceList = Arrays.asList("hello world","Xiao Yao Ge Ge");
        // 使用流操作
        List<String> resultList = sentenceList.stream()
                .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
                .collect(Collectors.toList());
        System.out.println(resultList);
    }
/结果:[hello, world, Xiao, Yao, Ge, Ge]

filter、sorted、distinct、limit 使用

这几个都是常用的Stream的中间操作方法,具体的方法的含义在上面的表格里面有说明。具体使用的时候,可以根据需要选择一个或者多个进行组合使用,或者同时使用多个相同方法的组合。

java 复制代码
    public static void main(String[] args) {

        List<String> ids = Arrays.asList("aaa", "bb", "ccc", "ddd");
        // 使用流操作
        List<User> results = ids.stream()
                .filter(s -> s.length() > 2)
                .distinct()
                .map(Integer::valueOf)
                .sorted(Comparator.comparingInt(o -> o))
                .limit(3)
                .map(id -> new User(id))
                .collect(Collectors.toList());
        System.out.println(results);
    }
  //结果:[User{id=aaa},  User{id=ccc},  User{id=ddd}]  
  1. 使用filter过滤掉不符合条件的数据
  2. 通过distinct对存量元素进行去重操作
  3. 通过map操作将字符串转成整数类型
  4. 借助sorted指定按照数字大小正序排列
  5. 使用limit截取排在前3位的元素
  6. 又一次使用map将id转为Dept对象类型
  7. 使用collect终止操作将最终处理后的数据收集到list中

简单结果终止方法

终止方法里面像count、max、min、findAny、findFirst、anyMatch、allMatch、nonneMatch等方法,均属于这里说的简单结果终止方法。所谓简单,指的是其结果形式是数字、布尔值或者Optional对象值等。

java 复制代码
    public static void main(String[] args) {
        List<String> ids = Arrays.asList("111", "22", "333", "444", "55", "66", "77");
        // 统计stream操作后剩余的元素个数
        long idsCount = ids.stream().filter(s -> s.length() > 2).count();
        System.out.println("操作后剩余的元素个数=" + idsCount);
        // 判断是否有元素值等于222
        boolean res = ids.stream().filter(s -> s.length() > 2).anyMatch("222"::equals);
        System.out.println("否有元素值等于222=" + res);
        // findFirst操作
        ids.stream().filter(s -> s.length() > 2)
                .findFirst()
                .ifPresent(s -> System.out.println("findFirst:" + s));
    }
    //结果:
    操作后剩余的元素个数=3
	否有元素值等于222=false
	findFirst:111

因为Stream主要用于对集合数据的处理场景,所以除了上面几种获取简单结果的终止方法之外,更多的场景是获取一个集合类的结果对象,比如List、Set或者HashMap等。

java 复制代码
    public static void main(String[] args) {
        List<User> ids = Arrays.asList(new User(12), new User(25), new User(25));
        //list
        List<User> collectList = ids.stream().filter(user -> user.getId() > 20)
                .collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //set
        Set<User> collectSet = ids.stream().filter(user -> user.getId() > 20)
                .collect(Collectors.toSet());
        System.out.println("collectSet:" + collectSet);
        //collect成HashMap,key为id,value为User对象
        Map<Integer, User> collectMap = ids.stream().filter(user -> user.getId() > 20)
                .collect(Collectors.toMap(User::getId, user -> user));
        System.out.println("collectMap:" + collectMap);
    }

生成拼接字符串

java8以前写法

java 复制代码
    public static void main(String[] args) {
        List<String> strList = Arrays.asList("a", "b", "v", "d", "e", "r", "f", "g");
        StringBuilder builder = new StringBuilder();
        for (String str : strList) {
            builder.append(str).append(',');
        }
        // 去掉末尾多拼接的逗号
        builder.deleteCharAt(builder.length() - 1);
        System.out.println("拼接后:" + builder.toString());
        //结果:拼接后:a,b,v,d,e,r,f,g
    }

java8

java 复制代码
 	public static void main(String[] args) {
        List<String> strList = Arrays.asList("a", "b", "v", "d", "e", "r", "f", "g");
        String joinResult = strList.stream().collect(Collectors.joining(","));
        System.out.println("拼接后:" + joinResult);
		//结果:拼接后:a,b,v,d,e,r,f,g
    }

两种方式都可以得到完全相同的结果,但Stream的方式更优雅

Stream相较于传统的foreach的方式处理stream,到底有啥优势

  • 代码更简洁、偏声明式的编码风格,更容易体现出代码的逻辑意图
  • 逻辑间解耦,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可
  • 并行流场景效率会比迭代器逐个循环更高
  • 函数式接口,延迟执行的特性,中间管道操作不管有多少步骤都不会立即执行,只有遇到终止操作的时候才会开始执行,可以避免一些中间不必要的操作消耗

Stream弊端:

  • 代码可读性不是很好
  • 代码调测debug不便
  • 程序员从历史写法切换到Stream时,需要一定的适应时间
相关推荐
Swift社区33 分钟前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT1 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy1 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss3 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续3 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0443 小时前
ReAct模式解读
java·ai
轮到我狗叫了4 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
Volunteer Technology5 小时前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~5 小时前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar