java8系列02——Stream流

2022-10-26 17:39:41 浏览数 (2)

Stream流

目录
  • Stream流
    • 1 快速入门
    • 2 创建流
    • 3 中间操作
    • 3.1 filter
    • 3.2 map
    • 3.3 distinct
    • 3.4 sorted
    • 3.5 limit
    • 3.6 skip
    • 3.7 flatMap
    • 4 终结操作
    • 4.1 foreach
    • 4.2 count
    • 4.3 max&min
    • 4.4 collect
    • 4.5 anyMatch
    • 4.6 allMatch
    • 4.7 noneMatch
    • 4.8 findAny&findFirst
    • 4.9 reduce
    • 5 注意事项

stream可以对数组和集合的元素以流的形式进行操作,可以让我们更方便的操作数组、集合元素。

1 快速入门

先做案例数据准备。

依赖

代码语言:javascript复制
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

Author

代码语言:javascript复制
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期去重使用
public class Author {
    private Long id;
    private String name;
    private int age;
    private String info;
    private List<Book> books;
}

Book

代码语言:javascript复制
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期去重使用
public class Book {
    private Long id;
    private String name;
    private String category;
    private String intro;
    private Integer score;
}

demo方法。

代码语言:javascript复制
 private static List<Author> getAuthors() {
        Author author1 = new Author(1L, "半旧", 12, "爱学习的半旧", null);
        Author author2 = new Author(2L, "粥粥", 19, "喝粥粥", null);
        Author author3 = new Author(3L, "小米", 17, "小米最棒", null);
        Author author4 = new Author(3L, "小米", 17, "小米最棒", null);


        List<Book> books1 = new ArrayList<>();
        List<Book> books2 = new ArrayList<>();
        List<Book> books3 = new ArrayList<>();


        books1.add(new Book(1L, "追风筝的人", "小说","温馨治愈的人物小说", 9 ));
        books1.add(new Book(2L, "疯狂java讲义", "互联网","java入门枕边书", 7 ));

        books2.add(new Book(3L, "中国哲学简史", "哲学, 历史","儒释道", 9 ));
        books2.add(new Book(3L, "中国哲学简史", "哲学, 历史","儒释道", 9 ));
        books2.add(new Book(4L, "编码", "互联网","作者是大神,举重若轻", 10 ));

        books3.add(new Book(5L, "数学之美", "数学","数学之趣味、美好", 8 ));
        books3.add(new Book(6L, "封神演绎", "小说","古典文学", 8 ));
        books3.add(new Book(6L, "封神演绎", "小说","古典文学", 8 ));

        author1.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
     	author4.setBooks(books3);
        List<Author> authorList = Arrays.asList(author1, author2, author3, author4);
        return authorList;
    }

现在需求如下:获取作家,打印所有年龄小于18岁的作家姓名并去重。实现如下。

代码语言:javascript复制
  List<Author> authors = getAuthors();
        authors.stream() // 把集合转换成流
                .distinct()//去重
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return author.getAge() < 18;
                    }
                }
                .forEach(new Consumer<Author>() {
                    @Override
                    public void accept(Author author) {
                        System.out.println(author.getName());
                    }
                });

使用Lambda优化下。

代码语言:javascript复制
 authors.stream() // 把集合转换成流
                .distinct()//去重
                .filter(author -> author.getAge() < 18)
                .forEach(author -> System.out.println(author.getName()));

想要知道这个过程中每个方法到底做了什么,可以通过加断点,很清晰的跟踪到各个过程的数据变化情况.

上面的操作简直是太优雅了.如果没有stream流做上面的操作特别繁琐.

2 创建流

流的操作包含三个阶段:创建,中间操作,终结操作.先来介绍流的创建.

(1)单列集合

代码语言:javascript复制
 List<Author> authors = getAuthors();
 authors.stream();

(2)数组

代码语言:javascript复制
 private static void test02() {
        Integer[] arr = {1, 2, 3 ,4 , 4, 5};
        Arrays.stream(arr)
                .filter(integer -> integer > 3)
                .distinct()
                .forEach(integer -> System.out.println(integer));
  }

也可以使用Streamof()方法.

代码语言:javascript复制
 Stream.of(arr);

(3)双列集合

先转成单列集合再转为流.

代码语言:javascript复制
private static void test03() {
        Map<String, Integer> map = new HashMap<>();
        map.put("火影忍者", 18);
        map.put("忍者神龟", 16);
        map.put("小黄人", 15);

        Stream<Map.Entry<String, Integer>> mapEntry = map.entrySet().stream();
        mapEntry.filter(stringIntegerEntry -> stringIntegerEntry.getValue() < 18)
                .forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry.getKey()   "=="   stringIntegerEntry.getValue()));
    }
3 中间操作
3.1 filter

条件过滤

代码语言:javascript复制
 private static void test04() {
        getAuthors().stream()
                .filter(author -> author.getName().length() > 1)
                .forEach(author -> System.out.println(author.getName()));
    }

顺带一提,终结操作必须加上。后续会解释原因。

3.2 map

把流中的元素进行计算或者数据类型的转换。

需求: 打印所有作家的姓名

法1:

代码语言:javascript复制
getAuthors().stream()
                .forEach(author -> System.out.println(author.getName()));

法2:

代码语言:javascript复制
 getAuthors().stream()
                .map(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) {
                        return author.getName();
                    }
                })
                 .forEach(new Consumer<String>() {
                    @Override
                    public void accept(String s) {
                        System.out.println(s);
                    }
                });

简化下。

代码语言:javascript复制
 getAuthors().stream()
                .map(author -> author.getName())
                .forEach(s -> System.out.println(s));

这里我们就清楚map()的作用了,原来它可以映射集合元素,把它的元素替换成其它类型,比如这里就全部替换成了author的姓名,后续操作的就都是name属性了。

map()还可以对流中元素进行计算,也就是进行一些加工处理,参考如下例子。

代码语言:javascript复制
getAuthors().stream()
                .map(author -> author.getName()   ",")
                .forEach(s -> System.out.print(s));
3.3 distinct

去重。判断元素重复的依据是Object的equals()方法,如果是自定义的数据类型要重写equals()方法。

实际上,我们之前加再实体类上的注解@EqualsAndHashCode就相当于重写了equals()与hashcode()方法:当所有元素的值相同时判断对象为同一个。它的作用与如下代码等同。

代码语言:javascript复制
 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Author author = (Author) o;
        return Objects.equals(id, author.id) && Objects.equals(name, author.name) && Objects.equals(age, author.age) && Objects.equals(intro, author.intro) && Objects.equals(books, author.books);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, intro, books);
    }

demo如下。

代码语言:javascript复制
 List<Author> authors = getAuthors();
        authors.stream() // 把集合转换成流
                .distinct()//去重
                .forEach(author -> System.out.println(author.getName()));
3.4 sorted

对流中的元素进行排序。下面对author进行年龄降序排序,并去重输出。

代码语言:javascript复制
 getAuthors().stream()
                .sorted(new Comparator<Author>() {
                    @Override
                    public int compare(Author o1, Author o2) {
                        return o2.getAge() - o1.getAge();
                    }
                })
                .distinct()
                .forEach(author -> System.out.println(author.getName()   " is "   author.getAge()));

简化如下。

代码语言:javascript复制
 private static void test06() {
        getAuthors().stream()
                .sorted((o1, o2) -> o2.getAge() - o1.getAge())
                .distinct()
                .forEach(author -> System.out.println(author.getName()   " is "   author.getAge()));
    }

sorted()还有空参的重载方法,当author实现了Comparable()接口并且重写了抽象方法时,可以使用空参版本。

3.5 limit

可以对流的长度进行限制,超出的部分将被抛弃。

对author进行年龄降序排序,去重,输出年龄最大的两位作家。

代码语言:javascript复制
 private static void test07() {
        getAuthors().stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge() - o1.getAge())
                .limit(2)
                .forEach(author -> System.out.println(author.getName()   " is "   author.getAge()));
    }
3.6 skip

跳过流中前n个元素。实际上应用中常见的有去掉最低分、最高分求平均分等操作可以使用到。这里我们仅实现一个demo需求演示。

对author进行年龄降序排序,去重,输出年龄最大的作家外的其它作家。

代码语言:javascript复制
 private static void test08() {
        getAuthors().stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge() - o1.getAge())
                .skip(1)
                .forEach(author -> System.out.println(author.getName()   " is "   author.getAge()));
    }
3.7 flatMap

现有需求:打印所有书籍的名字。聪明的你可能会这么做。

代码语言:javascript复制
 private static void test09() {
        getAuthors().stream()
                .map(author -> author.getBooks())
                .forEach(new Consumer<List<Book>>() {
                    @Override
                    public void accept(List<Book> books) {
                        for(Book book : books) {
                            System.out.println(book);
                        }
                    }
                });
    }

不过你发现,打印出来的book似乎有重复的元素,如何对他们去重似乎成了难题,而且代码一点都不优雅。flatMap提供了另外的解决方案。

代码语言:javascript复制
private static void test10() {
        getAuthors().stream()
                .flatMap(new Function<Author, Stream<Book>>() {
                    @Override
                    public Stream<Book> apply(Author author) {
                        return author.getBooks().stream();
                    }
                })
                .distinct()
                .forEach(new Consumer<Book>() {
                    @Override
                    public void accept(Book book) {
                        System.out.println(book);
                    }
                });
    }

优化。

代码语言:javascript复制
  private static void test10() {
        getAuthors().stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(book -> System.out.println(book));
    }

下面再举一例加深印像。

打印所有书籍分类,并且去重,不要出现类似哲学,爱情这样的格式。

代码语言:javascript复制
 private static void test12() {
        getAuthors().stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
                .distinct()
                .forEach(category -> System.out.println(category));
    }
4 终结操作

Stream流一定要有终结操作,因为如果没有终结操作,stream流的代码都不会被执行。

4.1 foreach

例:打印所有作家名字。

代码语言:javascript复制
getAuthors().stream()
                .forEach(author -> System.out.println(author.getName()));
4.2 count

获取流中元素个数。

例:获取书籍数量,去重。

代码语言:javascript复制
 private static void test14() {
         long count = getAuthors().stream()
                .distinct()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .count();
        System.out.println(count);
    }
4.3 max&min

求流的最值。

例:输出书籍的最高分、最低分,打印。

代码语言:javascript复制
 private static void test15() {
        Optional<Integer> max = getAuthors().stream()
                .distinct()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .map(book -> book.getScore())
                .max((a, b) -> a - b);
        System.out.println(max.get());
    }

最低分略。

4.4 collect

将流中的元素转换为集合。

例:获取存放所有作者名字的List集合。

代码语言:javascript复制
   private static void test16() {
        List<String> authorNames =getAuthors().stream()
                .map(author -> author.getName())
                .distinct()
                .collect(Collectors.toList());
        for (String authorName : authorNames) {
            System.out.println(authorName);
        }
    }

collect的传参我们并没有使用匿名内部类来实现,因为这样会比较复杂(读者可以自己尝试下),而是使用java.util.stream.Collectors工具类的方法。

例:获取所有书名的Set集合。

代码语言:javascript复制
 private static void test17() {
        Set bookNames = getAuthors().stream()
                .flatMap(author -> author.getBooks().stream())
                .map(book -> book.getName())
                .distinct()
                .collect(Collectors.toSet());
        System.out.println(bookNames);
    }

例:获取一个map集合,key为作者名,value为List.

代码语言:javascript复制
 private static void test18() {
        Map<String, List<Book>> authorAndBooks = getAuthors().stream()
                .distinct()
                .collect(Collectors.toMap(
                        new Function<Author, String>() {
                            @Override
                            public String apply(Author author) {
                                return author.getName();
                            }
                        },
                        new Function<Author, List<Book>>() {
                            @Override
                            public List<Book> apply(Author author) {
                                return author.getBooks();
                            }
                        }
                ));
        System.out.println(authorAndBooks);
    }

上面的toMap()有两个参数,分别对应keyvalue.改良下。

代码语言:javascript复制
private static void test18() {
    Map<String, List<Book>> authorAndBooks = getAuthors().stream()
            .distinct()
            .collect(Collectors.toMap(
                    author -> author.getName(),
                    author -> author.getBooks()
            ));
    System.out.println(authorAndBooks);
}
4.5 anyMatch

例:判断是否有年龄小于14岁的作家。

代码语言:javascript复制
private static void test19() {
    boolean isSmallAuthorExits = getAuthors().stream()
            .anyMatch(author -> author.getAge() < 14);
    System.out.println(isSmallAuthorExits);
}
4.6 allMatch

例:判断是否所有作家都是未成年人。

代码语言:javascript复制
 private static void test20() {
        boolean flag = getAuthors().stream()
                .allMatch(author -> author.getAge() < 18);
        System.out.println(flag);
    }
4.7 noneMatch

判断作家中是否没有成年人。

代码语言:javascript复制
 private static void test21() {
        boolean flag = getAuthors().stream()
                .noneMatch(author -> author.getAge() >= 18);
        System.out.println(flag);
    }
4.8 findAny&findFirst

如果存在,获取任意一个未成年作家的名字。

代码语言:javascript复制
 private static void test21() {
        Optional<Author> authorOptional = getAuthors().stream()
                .filter(author1 -> author1.getAge() > 1)
                .findAny();
        authorOptional.ifPresent(author ->System.out.println(author.getName()));
    }

笔者这里测试的结果是固定,并且debug在filter前就只有一个数据了,如果有大佬能够解释下万分感谢。

findFirst会查找第一个元素,略。

4.9 reduce

归并,对流中的数据,按照指定的计算方式得到一个结果。(缩减操作)

其实现的逻辑等价于如下代码:

代码语言:javascript复制
T result = identity;
for(T element : this stream)
    result = accumulator.apply(result, element)
return result;

是不是觉得头大了,没关系,看看下面的代码。是不是有相似之处了。

代码语言:javascript复制
int [] arr = {1,2,3,4,5};
        int sum = 0;
        for (int i : arr) {
            sum  = i;
        }
 return sum;

实际上,很有可能另外一个方法需要求累乘,那么我们可以上面的操作改成累乘,然后输入累乘的计算。如果还有其它运算呢?cv,cv,cv…这种繁琐的操作令人厌倦,reduce就把核心的逻辑抽取了出来,我们只需要定义初始值identity和运算规则即可。我们就可以更轻松的改变输入数据集和运算规则,实现不同的运算了。

例:求所有作者年龄的和。

代码语言:javascript复制
 getAuthors().stream()
                .map(author -> author.getAge())
                .reduce(0, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer element) {
                        return result   element;
                    }
});

简化如下。

代码语言:javascript复制
 getAuthors().stream()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result   element);

例:使用reduce求所有作家中年龄最大的值。

代码语言:javascript复制
  private static void test24() {
        Integer max = getAuthors().stream()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> Math.max(result, element));
        System.out.println(max);

    }

实际上,之前我们学习的minmax方法源码就是使用的reduce方法。

reduce还有一个参数的重载方法。其逻辑等价于如下代码。

代码语言:javascript复制
   boolean foundAny = false;
     T result = null;
     for (T element : this stream) {
         if (!foundAny) {
             foundAny = true;
             result = element;
         }
         else
             result = accumulator.apply(result, element);
     }
     return foundAny ? Optional.of(result) : Optional.empty();

其实就是把第一个元素作为初始化值。

代码语言:javascript复制
private static void test25() {
    Optional<Integer> min = getAuthors().stream()
            .map(author -> author.getAge())
            .reduce((result, element) -> Math.min(result, element));
    min.ifPresent(System.out::println);
}
5 注意事项

1.惰性求值:一个流在进行终结操作前不会进行计算。

2.一次性:一个流被终结后不能再次使用。

3.不会影响源数据(非正常情况除外,参考如下)。

代码语言:javascript复制
 private static void test26() {
        getAuthors().stream()
                .map(author -> {
                    author.setAge(10);
                    return author.getAge();
                })
                .forEach(System.out::println);
    }

0 人点赞