JAVA 8 Stream 4

接着上一篇,我们继续介绍stream 中Terminal相关的api。

1、forEach

forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

roster.stream()
 .filter(p -> p.getGender() == Person.Sex.MALE)
 .forEach(p -> System.out.println(p.getName()));

可以看出来,forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时 forEach 本身的实现不需要调整,而 Java8 以前的 for 循环 code 可能需要加入额外的多线程逻辑。但一般认为,forEach 和常规 for 循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。

另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。下面的代码是错误的:

stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));

相反,具有相似功能的 intermediate 操作 peek 可以达到上述目的。如下是出现在该 api javadoc 上的一个示例。

Stream.of("one", "two", "three", "four")
 .filter(e -> e.length() > 3)
 .peek(e -> System.out.println("Filtered value: " + e))
 .map(String::toUpperCase)
 .peek(e -> System.out.println("Mapped value: " + e))
 .collect(Collectors.toList());

forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环

2、forEachOrdered

parallel().sorted()之后,不能直接使用forEach(),要使用forEachOrdered()。并行Stream和sorted()并不会冲突。

Arrays.asList(1,4,5,2,3,6,8,9,7)
      .stream()
      .parallel()
      .sorted()
      .forEach(System.out::print);

输出:378692415

Arrays.asList(1,4,5,2,3,6,8,9,7)
      .stream()
      .parallel()
      .sorted()
      .forEach(System.out::print);

输出:123456789

3、collect、toArray:

将Stream中的数据转成集合或者数组,这个在之前已经详细介绍过了

4、reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于

Integer sum = integers.reduce(0, (a, b) -> a+b); 或
Integer sum = integers.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。reduce的定义如下:

T reduce(T identity, BinaryOperator<T> accumulator)

identity:它允许用户提供一个循环计算的初始值。accumulator:计算的累加器,其方法签名为apply(T t,U u),在该reduce方法中第一个参数t为上次函数计算的返回值,第二个参数u为Stream中的元素,这个函数把这两个值计算apply,得到的和会被赋值给下次执行这个方法的第一个参数。有点绕看代码:

int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
Assert.assertSame(value, 110);
/* 或者使用方法引用 */
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);
这个例子中100即为计算初始值,每次相加计算值都会传递到下一次计算的第一个参数。reduce还有其它两个重载方法:
  • Optional<T> reduce(BinaryOperator<T> accumulator):与上面定义基本一样,无计算初始值,所以他返回的是一个Optional。
  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner):与前面两个参数的reduce方法几乎一致,你只要注意到BinaryOperator其实实现了BiFunction和BinaryOperator两个接口。
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);

5、max、min、count:

min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

找出最长一行的长度:

BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines().
 mapToInt(String::length).
 max().
 getAsInt();
br.close();
System.out.println(longest);

count示例:

long totalMatched = list.stream().filter((s) -> s.startsWith("A")).count();
6、 findFirst

这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。这里比较重点的是它的返回值类型:Optional。这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

注:Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。

List<User> list = new ArrayList<User>(){
			private static final long serialVersionUID = 1L;
			{
				add(new User(2L,"test2",0,2));
				add(new User(1L,"test2",0,12));
				add(new User(3L,"test3",1,23));
			}
		};
		User user = list.stream().filter(u -> {return u.getName().equals("test2");}).findFirst()
			.get();
		System.out.println(user);

当找不到时,返回的OPtional对象需要做一个判断,否则会报错,如下:

Optional<User> findFirst = list.stream().filter(u -> {return u.getName().equals("a");}).findFirst();
		User user = findFirst.get();
		System.out.println(user.toString());

报错信息:(findFirst.get()的时候)

Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at j8.Stream2Test.findFistTest(Stream2Test.java:90)
	at j8.Stream2Test.main(Stream2Test.java:19)

改写:

Optional<User> findFirst = list.stream().filter(u -> {return u.getName().equals("a");}).findFirst();
		/*User user = findFirst.get();
		System.out.println(user.toString());*/
		System.out.println(Optional.ofNullable(findFirst).get().toString());

输出:Optional.empty

7、match:

Stream 有三个 match 方法,从语义上说:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().
 allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
 anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);

8、iterator:

iterate方法有两个参数,第一个是seed也可以称作种子,第二个是一个UnaryOperator,UnaryOperator实际上是Function的一个子接口,和Funciton区别就是参数和返回类型都是同一种类型

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

}
iterate方法第一次生成的元素是UnaryOperator对seed执行apply后的返回值,之后所有生成的元素都是UnaryOperator对上一个apply的返回值再执行apply,不断循环。

f(f(f(f(f(f(n))))))......

从1开始,每个元素比前一个元素大2,最多生成10个元素:

Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);

我们在使用stream api时也要注意一些陷阱,比如下面这个例子:

IntStream.iterate(0,i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);
Stream陷阱 distinct()会一直等待产生的结果去重,将distinct()和limit(6)调换位置,先限制结果集再去重就可以了。





猜你喜欢

转载自blog.csdn.net/liuxiao723846/article/details/81065624