Java8新特性-说说Stream的应用

Java技术迷

共 6606字,需浏览 14分钟

 · 2022-01-01

点击关注公众号,Java干货及时送达


JDK1.8新特性中较为重要的一块内容就是Stream API了,通过Stream API,我们改变了传统的集合操作,使得对集合的操作更加简单和可控。

初体验

在Stream出现以前,我们是如何对集合进行处理的呢?

比如现在有一个需求,从一个字符串集合中找出长度大于5的字符串,该如何实现呢?代码如下:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    for (int i = 0; i < list.size(); i++) {
        if (list.get(i).length() > 5) {
            System.out.println(list.get(i));
        }
    }
}

这段代码非常基础,你也可以使用增强for循环实现这一需求:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    for (String str : list) {
        if (str.length() > 5) {
            System.out.println(str);
        }
    }
}

如果要你将字符串长度大于5的字符串收集起来存入一个新的集合,你就得新建一个集合,然后在循环里进行添加:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    List newList = new ArrayList<>();
    for (String str : list) {
        if (str.length() > 5) {
            newList.add(str);
        }
    }
    System.out.println(newList);
}

上述的这些操作,在Stream API中将会非常容易实现,先看一个简单的例子:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    list.forEach(System.out::println);
}

遍历一个集合变得非常简单,虽然这并不是Steam的API,但这一特性仍然是在JDK1.8之后才支持的,接下来我们就用Steam实现一下将字符串长度大于5的字符串收集成为一个新集合的需求:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    List newList = list.stream().map(str -> {
        if (str.length() > 5) {
            return str;
        }
        return null;
    }).collect(Collectors.toList());
    System.out.println(newList);
}

我们首先不必纠结为什么这么写,先来看看它的效果:

[null, banana, null, orange, null, watermelon]

新集合中虽然都是长度大于5的字符串,但多了一些null值,所以,使用Stream的filter显然更适合这一需求:

public static void main(String[] args) {
    List list = Arrays.asList("apple""banana""pear""orange""peach""watermelon");
    List newList = list.stream().filter(str -> {
        return str.length() > 5;
    }).collect(Collectors.toList());
    System.out.println(newList);
}

运行结果:

[banana, orange, watermelon]

熟悉Lambda表达式的同学肯定能发现,这一写法还能能进行简化:

List newList = list.stream().filter(str -> str.length() > 5).collect(Collectors.toList());

Stream的创建

见识到了Stream API的广大神通了吧,接下来我们回到最基本的问题,如何创建一个Stream?

public static void main(String[] args) {
    Stream stream = Stream.of(123);
}

最简单的方式便是使用of方法,通过of方法可以创建一个由指定元素组成的Stream,但通常情况下我们并不会使用到该方法,而是使用下面的几个方法:

private static void method(List list,String[] array) {
    Stream listStream = list.stream();
    Stream arrayStream = Arrays.stream(array);
}

Stream还支持从指定范围的数组中创建一个流(集合不支持):

private static void method(List list,String[] array) {
    Stream stream = Arrays.stream(array, 05);
}

创建一个不包含任何元素的流:

public static void main(String[] args) {
    Stream emptyStream = Stream.empty();
}

使用generate方法创建流:

public static void main(String[] args) {
    Stream stream = Stream.generate(Math::random);
}

创建流的方式还有很多,这里就不一一列举了。

map方法

在最初的例子中,我们用到了map和filter方法,它们的作用分别是什么以及它们的用法是如何的呢?先来所说map方法,map方法类似于遍历操作,它会得到Stream中每个元素的映射,并对其一一生效,比如:

public static void main(String[] args) {
    Stream stream = Stream.of(12345);
    List newList = stream.map(num -> {
        return num + 1;
    }).collect(Collectors.toList());
    System.out.println(newList);
}

这段程序的作用就是对流中的每个元素都做加1操作,运行结果如下:

[23456]

对于那些需要对集合/流中的每个元素都要做相同操作的需求,就非常适合使用map方法,比如前后端分离开发中,后端经常需要从数据库中查询出数据,然后将其封装成VO传递给前端,这一需求就能够使用map方法实现:

private static void method() {
    List userList = userMapper.findAll();
    List userVOList = userList.stream().map(user -> {
        UserVO userVO = new UserVO();
        // 拷贝相同的属性
        BeanUtils.copyProperties(user, userVO);
        // 处理前端需要的额外属性
        userVO.setSex(user.getSex == 1 ? "男" : "女");
        return userVO;
    }).collect(Collectors.toList());
    System.out.println(userVOList);
}

filter方法

filter方法与map类似,它也会作用于流中的每个元素,但与其不同的是,filter方法是用来做过滤操作的,在filter中我们需要返回一个boolean类型的值,如果返回为true,则说明该值是我们需要的,如果为false,该值就会被抛弃掉,比如:

public static void main(String[] args) {
    Stream stream = Stream.of(1357435545736);
    List newList = stream.filter(num -> {
        return num > 30;
    }).collect(Collectors.toList());
    System.out.println(newList);
}

这段程序的作用是取出流中数值大于30的数,运行结果如下:

[35545736]

filter可以用来对流中的数据做过滤处理,比如只想要性别为男的数据;只想要工资超过2万的员工信息等等。

其它操作

Stream的神奇操作远不止这些,下面再介绍一些比较常用的功能,比如找出一个集合中的最大值:

public static void main(String[] args) {
    List list = Arrays.asList(1123245634614);
    int max = 0;
    for (Integer num : list) {
        if(num > max){
            max = num;
        }
    }
    System.out.println(max);
}

而如果使用Stream,它将变得非常简单:

public static void main(String[] args) {
    List list = Arrays.asList(1123245634614);
    Optional max = list.stream().max(Comparator.naturalOrder());
    System.out.println(max.get());
}

求最小值,只需使用min方法即可:

public static void main(String[] args) {
    List list = Arrays.asList(1123245634614);
    Optional max = list.stream().min(Comparator.naturalOrder());
    System.out.println(max.get());
}

求和:

public static void main(String[] args) {
    List list = Arrays.asList(1123245634614);
    Optional sum = list.stream().reduce(Integer::sum);
    System.out.println(sum.get());
}

注意事项

在使用Strema API时需要注意的地方就是Lambda表达式的编写,如:

List list = wordList.stream().filter(word -> {
    return word.startsWith("p");
list.forEach(System.out::println);

对于这样的一段程序,因为Lambda表达式体中仅包含了一条返回语句,所以表达式可以简写:

List list = wordList.stream().
    filter(word -> word.startsWith("p")).collect(Collectors.toList());
list.forEach(System.out::println);

又比如:

List numList = Arrays.asList(12345678910);
Optional sum = numList.stream().reduce((x, y) -> {
    return x + y;
});
System.out.println(sum);

首先因为Lambda表达式体仅包含一条返回语句,所以可以简写:

Optional sum = numList.stream().reduce((x, y) -> x + y);

我们也可以调用Integer类的静态方法sum进行求和:

Optional sum = numList.stream().reduce((x, y) -> Integer.sum(x, y));

又因为Lambda表达式体中仅包含一条语句,且该语句调用了一个绝对存在的方法,则可以简写为:

Optional sum = numList.stream().reduce(Integer::sum);

这就是Lambda表达式中的方法引用,具体细节可以去了解一下Lambda的相关内容。

最后就是Stream的一些其他类型,当你使用of方法创建一个流时:

Stream stream = Stream.of(12345678910);

调用Stream类的of方法传入的是基本类型数据,但是会得到一个包装类型的Stream,我们知道,基本类型需要装箱成为包装类型,这一操作在数据量庞大的情况下是比较低效的,Stream API也考虑到这一点,所以提供了对应包装类型的Strema,如下:

IntStream intStream = IntStream.of(12'a''z');
LongStream longStream = LongStream.of(10L);
DoubleStream doubleStream = DoubleStream.of(1.1f2.2);


1、Log4j2维护者吐槽没工资还要挨骂,GO安全负责人建议开源作者向公司收费

2、太难了!让程序员崩溃的8个瞬间

3、2021年程序员们都在用的神级数据库

4、Windows重要功能被阉割,全球用户怒喷数月后微软终于悔改

5、牛逼!国产开源的远程桌面火了,只有9MB 支持自建中继器!

6、摔到老三的 Java,未来在哪?

7、真香!用 IDEA 神器看源码,效率真高!

点分享

点收藏

点点赞

点在看

浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报