浅谈 Java8 的 Stream

对比 Collection 的新旧式操作

Updated on 2018-05-10 17:50 (Created on: 2017-09-24 02:03)

我在浏览关于 Java Collection 的一篇博文的时候,对那篇博文的一些内容持有不同的看法, 内容如下:

其实这种写法在 Java 8 发布之前是比较好的写法,但是在 Java 8 之后就有了更优雅高效的写法。 所以我打算谈谈 Java8 中关于 Collection 的操作。

Java函数式编程

Java8 引进了函数式编程的新特性,让Java的开发人员也可以享受函数式编程的美妙,已经有 很多的文章介绍函数式了,珠玉在前,我就不赘言了。来说说Java 的Lambda吧:Java8 对 核心类库进行了改进,只要包括集合类的API和新引入的流(Stream), 而流可以让开发者站在更高的 抽象层次对集合进行操作

流(Stream)

Stream不是集合元素,它也不是数据结构、不能保存数据,它更像一个更高级的 Interator。 Stream提供了强大的数据集合操作功能,并被深入整合到现有的集合类和其它的JDK类型中。 流的操作可以被组合成流水线(Pipeline)

惰性求值

在谈及流的具体操作之前,先来了解一个概念。我们经常会遇到一些需要延迟计算的情形, 比如某些运算非常消耗资源,如果提前算出来却没有用到,会得不偿失。在计算机科学中, 有个专门的术语形容它:惰性求值。惰性求值是一种求值策略,也就是把求值延迟到真正需 要的时候。而 Stream 常见的惰性求值操作有:filter, map, flatmap 等

及早求值

与及早求值相对的就是惰性求值,因为 Stream并不支持对其元素进行直接操作和直接访问, 而只支持通过声明式操作在其之上进行运算后得到结果,所以要想在 Stream 进行相应操作 之后得出结果,就必须执行及早求值操作。Stream 常见的及早求值操作有 count, collect 等

collect(toList())

collect(toList()) 方法可以由Stream 值生成一个List,而Stream 的of方法可以使用 初始值生成新的Stream.

     List<String> collected= Stream.of("this","is","a","list").collect(Collectors.toList());

map

如果有一个函数可以将一种类型的值转换成另外一种类型,map 操作就可以使用该函数,将一个流 中的值转换成一个新的流,图示:

map 例子

将字符变成大写格式 如果用没有Lambda 时的模式编程

      List<String> oldStyle=new ArrayList<>();
      for(String string : Arrays.asList("a","b","c")){
          String uppercaseString=string.toUpperCase();
          oldStyle.add(uppercaseString);
      }

但是如果你有了Lambda

      List<String> lambdaStyle=Stream.of("a","b","c").map(string -> string.toUpperCase())
          .collect(Collectors.toList());

真的有种说不出的优雅

filter

在 Stream 中遍历数据并检查其中的元素,图示:

filter 例子

有一个User类,然后我想找出年龄大于30岁的用户

      private static List<User> users = Arrays.asList(
                                                      new User(1, "Steve", "Vai", 40),
                                                      new User(4, "Joe", "Smith", 32),
                                                      new User(3, "Steve", "Johnson", 57),
                                                      new User(9, "Mike", "Stevens", 18),
                                                      new User(10, "George", "Armstrong", 24),
                                                      new User(2, "Jim", "Smith", 40),
                                                      new User(8, "Chuck", "Schneider", 34),
                                                      new User(5, "Jorje", "Gonzales", 22),
                                                      new User(6, "Jane", "Michaels", 47),
                                                      new User(7, "Kim", "Berlie", 60)
                                                      );

非函数式编程(旧式):

      List<User> olderUsers = new ArrayList<User>();
      for (User u : users) {
          if (u.age > 30) {
              olderUsers.add(u);
          }
      }

函数式编程:

      List<User> olderUsers = users.stream().filter(u -> u.age > 30).collect(Collectors.toList());

flatMap

flatMap 方法可用Stream 替换值,然后将多个Stream 连接成一个Stream, 图示:

flatMap 例子

假设有一个包含多个列表的流,希望得到所有数字的序列

      List<Integer> together=Stream.of(Arrays.asList(1,2),Arrays.asList(3,4))
          .flatMap(numbers->numbers.stream()).collect(Collectors.toList());

还有其他常用的操作,我就不一一列举了,官方Quick Start有更详细的介绍,但是使用我谈到的 几种操作,就可以优雅过滤一个Collection :

      List<String> filterExample=Stream.of("a","b","c").filter(string->string.equals("a")).collect(Collectors.toList());

小结

Java Lambda的特性如果不经常使用,很容易又忘了,本文就当是对Java Lambda 的一次review吧 不过,函数式的引用的确让Java 焕发出新的活力,记得之前我的一位导师吐嘈Java语法太啰嗦, 有了 Lambda 之后,现在前辈应该会用得舒心一点吧


备注:上面的图都是来自 Java8 函数式编程 一书

参考:Java8 函数式编程