本文图解了Java8 Stream的几个常用方法:forEach(), map(), filter(), sorted()。

Stream流是Java8新增的一个特性,可以让你以声明的方式处理数据,像SQL语句一样,对数据进行筛选、排序和聚合等操作,而不对源数据进行更改。

Stream API可以简洁我们的代码,大大提升可读性。 咱们先看几个方法感受一下。

Stream.forEach()

看方法名,大概能猜出来,是对流进行遍历。其方法签名void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作。

  1. public static void main(String[] args) {
  2. List<User> userList = getUserList();
  3. // userId=1
  4. // userId=2
  5. // userId=3
  6. userList.forEach(e -> System.out.println("userId=" + e.getUserId()));
  7. }
  8. private static List<User> getUserList() {
  9. return Arrays.asList(new User().setUserId(1),
  10. new User().setUserId(2),
  11. new User().setUserId(3));
  12. }

Stream.map()

可以理解为是一个转换函数,对每个元素按照某种操作进行转换。
Stream-map.png

比如需要把List转化成List,然后返给前端展示

Java8之前

  1. List<User> userList = getUserList();
  2. List<UserVO> userVOList = new ArrayList<>();
  3. for (User user : userList) {
  4. UserVO userVO = new UserVO();
  5. userVO.setUserId(user.getUserId());
  6. // 省略set方法
  7. userVOList.add(userVO);
  8. }

使用Java8 Stream

List<User> userList = getUserList();

List<UserVO> userVOList = userList.stream().map(user -> {
    UserVO userVO = new UserVO();
    userVO.setUserId(user.getUserId());
    // 省略set方法
    return userVO;
}).collect(Collectors.toList());
  • .stream()将一个集合转换为Stream流
  • .collect()用于结果的收集
  • Collectors 收集器将流转换成集合和聚合元素

与Java8之前的区别在于,省略了new ArrayList<>()的过程,使用map函数,将User映射为UserVO。

Stream.filter()

函数的签名为 Stream filter(Predicate<? super T> predicate),作用是返回一个只包含满足predicate条件元素的Stream。也就是说filter是一个过滤函数。

这里要解释一下predicate,其中文意思是谓语,是一个布尔值函数,可以理解为条件。
Stream-filter.png
比如需要获取集合中userId为奇数的集合:

List<User> userList = getUserList();
userList.stream()
    .filter(user -> user.getUserId() % 2 == 1)
    .forEach(user -> System.out.println("userId=" + user.getUserId()));

// 控制台输出
// userId=1
// userId=3

Stream.sorted()

sorted的作用是对流中的元素进行排序排序,它有两个方法签名:Stream sorted()和Stream sorted(Comparator<? super T> comparator),其中前者要求流中的元素需要实现Comparable接口,否则会抛出ClassCastException异常。
Stream-sorted.png
比如把集合按照userId倒序排序:

private static List<User> getUserList() {
    return Arrays.asList(new User().setUserId(1), 
             new User().setUserId(3), 
             new User().setUserId(2));
}

public static void main(String[] args) {
    // User没有实现Comparable接口
    List<User> userList = getUserList();
    userList.stream()
        // Exception in thread "main" java.lang.ClassCastException: 
        // User cannot be cast to java.lang.Comparable
        .sorted()
        .forEach(user -> System.out.println("userId=" + user.getUserId()));
}

public static void main(String[] args) {
    List<User> userList = getUserList();
    userList.stream()
        .sorted((u1, u2) -> u2.getUserId() - u1.getUserId())
        .forEach(user -> System.out.println("userId=" + user.getUserId()));
}

// 控制台输出
// userId=3
// userId=2
// userId=1

小结

细心的同学可能会发现,forEach()没有返回值,而map(), filter(), sorted()的返回值还是一个Stream。实际上,对Stream的操作分为为两类:

  • 中间操作(intermediate operations):总是会惰式执行
  • 结束操作(terminal operations):会触发实际计算

可以这么理解,返回值为Stream的,都是中间操作,那什么是惰式执行呢?就是说中间操作是不会立即执行的,直到遇到结束操作,才会执行。比如下面一段代码是不会执行的:

Stream.of("a", "b", "c", "d", "e")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

这样的话,我们可以通过多个中间操作 + 1个结束操作,对数据进行筛选、排序、转换和收集等操作。

那这多个中间操作的顺序有什么讲究吗?可以留言谈谈你的观点。