Java8 Stream高级使用

By | 2016年5月15日

1. Stream只能被使用一次

  • 转换操作:把Stream转换成另一个Stream(例如map方法),该Stream会被标注为operated
  • 终止操作:对stream应用终止操作(例如count方法),改Stream会被标注为closed

不管是应用转换操作还是终止操作,改Stream都不能再用了,再用就会抛出运行时异常:java.lang.IllegalStateException: stream has already been operated upon or closed。

Stream<String> words = Stream.of("a", "aa", "aaa", "aaaa");
Stream<String> result = words.filter(w -> w.length() > 2);
long count = words.count();  // Throw exception as stream is already been used.

反过来也一样,如果先使用了count方法,再调用filter方法,也会抛出java.lang.IllegalStateException。

一次性的卫生筷子,用完后还可以将就再用用,但Stream用完就不能再用了。故个人认为不适合作为类的成员变量,适合作为方法的参数,返回值或者局部变量。

class Depart {
  // Once stream is used, it will exists like a garbage and cannot be collect by GC.
  private Stream<Integer> stream = Stream.of(1, 2, 3);

  private Stream<String> doSomething(Stream<Integer> input) {
    Stream<Integer> distintInput = input.distinct();
    return distintInput.map(n -> String.valueOf(n));
  }
}

可以调用close方法,主动终结一个Stream。

Stream<Integer> stream = ...
stream.close();

2. Stream – flatMap()方法

// Get team members of given team name.
// retrieveTeamMembers(String team): Stream<String> 
Stream<String> teams = Stream.of("GUI", "Server", "SQA", "PEG");
Stream<Stream<String>> result = teams.map(team -> retrieveTeamMembers(team));

result是个Stream,其数据类型是Stream<String>:[[“Alex”, “Jason”, …], [“Luc”, “Emma”, …], […]]。

使用flatMap,可以将子Stream展开成一个Stream:[“Alex”, “Jason”, …, “Luc”, “Emma”, …]。

Stream<String> result = teams.flatMap(team -> retrieveTeamMembers(team));
// Then we can apply action on each element.
result.forEach(System.out::println);

使用场合:当map操作返回的是Stream<Stream>,而又需要对结果中每个Stream中的元素进行相同的处理,应当使用flatMap将所有的Stream展开成一个Stream。

3. Stream – peek()方法

peek方法会产生一个与原Stram具有相同元素的Stream,但是每次获取一个元素时,都会调用一个函数。

Optional<String> maxString = Stream.of("a", "aaaa", "aa", "aaaaaa")
      .peek(s -> s = s + "!")
      .map(s -> s.toUpperCase())
      .peek(s -> Logger.logMsg(Logger.INFO, s))
      .max(Comparator.comparing(String::length));

上面代码中的两个peek方法存在与否,对结果没有任何影响。利用peek的这个特性,我么可以在添加测试代码,如上面的Logger.logMsg()方法。

4. Stream – 元素排序

Collections.sort()方法会对原来的集合进行排序,而Stream的排序是生成一个新的排序的Stream。

去除重复元素 – distinct

对于基本数据类型,使用相等(==)来判断元素是否重复。

Stream<String> days = Stream.of("Monday", "Monday", "Tuesday" );
long count = days.distinct().count();

对于对象类型,根据是否引用同一个对象,来判断是否重复。想要去除equals相等的重复对象,distinct()做不到。

排序 – sorted

Stream中的元素,实现了Comparable接口,直接使用sorted()方法。

class Session implements Comparable<Session> {
  private int id = 0;
  // Constructor, setter and getter
}

Stream<Session> sessions = Stream.of(new Session(88), new Session(45), new Session(55), new Session(23));
Stream<Session> sortedSessions = sessions.sorted();

Stream中的元素,没有实现Comparable接口,需要传入一个Comparator对象,调用Comparator的reversed方法就是逆序排序。最简单的,就是使用Stream中元素某个方法的返回值,进行排序。

// Sort session according to it's id.
Stream<Session> sortedSessions = sessions.sorted(Comparator.comparing(Session::getId));

Stream<String> words = ...
words.sorted(Comparator.comparing(String::length));
words.sorted(Comparator.comparing(String::length).reversed());

某些情况下,无法从直接从Stream的Element中获取排序关键字。需要先构造排序的key,然后指定根据key的排序规则。看下面的Scenario:

有一个Student的Stream,使用student的ID可以获得Student的所有成绩Grade对象,Grade对象包含学生所有的成绩。根据学生的数学成绩排序。

// GradeUtil::getGrade(int), get a student's Grade according it's id.
// Grade::mathGrade(), get a student's math grade.
Stream<Student> students = ...
students.sorted(Comparator.comparing(
    student -> {
      return GradeUtil.getGrade(student.getId());
    }, Comparator.comparing(Grade::mathGrade)));

Comparator里面还有很多好用的方法,如nullsFirst(),把null元素排在前面;nullsLast(),把null元素排在后面。

5. Stream – 元素检索

从Stream中检索出来的元素,都会封装在Optional<T>里,类似ObjectHolder<T>。调用get()方法返回封装数据,如果没有,则抛出NoSuchElementException。

方法名

用途

findFirst(): Optional<T>

返回集合的第一个元素

findAny(): Optional<T>

返回集合中的任何一个元素

anyMath(Predicate): boolean

有一个元素匹配,就返回True

allMatch(Predicate): boolean

全部匹配时,返回True

noneMatch(Predicate): boolean

都不匹配时,返回True

findFirst()findAny()通常与filter()结合使用,先过滤再查找。元素检索,为了提高效率,通常采用并行Stream

List<String> words = ...
Optional<String> result1 = 
    words.parallelStream().filter(s -> s.startsWith("Q")).parallel().findFirst();
Optional<String> result2 = 
    words.parallelStream().filter(s -> s.endsWith(".txt")).parallel().findAny();
boolean anyMatch = words.parallelStream().anyMatch(s -> s.startsWith("."));
boolean allMatch = words.parallelStream().allMatch(s -> s.startsWith("."));
boolean noneMatch = words.parallelStream().noneMatch(s -> s.startsWith("."));

max()min() 方法,需要传入一个Comparator,检索出最大元素和最小元素。

class Book {
  private long id;
  private int pages;
  private double price;

  // Getter and setter
}

List<Book> books = ...
Optional<Book> thickBook = 
    books.parallelStream().max(Comparator.comparing(Book::getPages));
Optional<Book> cheapBook = 
    books.parallelStream().min(Comparator.comparing(Book::getPrice));

6. Stream.empty()

生成一个空的Stream,多用于方法的返回值,避免返回null。