Java 8 Stream

958 阅读9分钟

java 8 lambda 精简入门

Java Stream

在学习Java Stream API之前,让我们看看为什么需要它。假设我们需要遍历一个整数列表,并找出所有大于10的整数之和。

在Java 8之前,我们会这样写:

  private static int sumIterator(List<Integer> list) {
    int sum = 0;
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
      int item = iterator.next();
      if (item > 10) {
        sum += item;
      }
    }
    return sum;
  }

上述方法存在三个主要问题:

  1. 我们只想知道大于10的整数的总和,但我们还必须提供迭代,这也称为外部迭代。
  2. 该程序本质上是按顺序执行的,我们无法轻松地并行执行此操作。
  3. 需要很多代码才可以完成一个简单的任务

为了克服上述所有缺点,引入了Java 8 Stream API。 我们可以使用Java Stream API来实现内部迭代,这样会更好,因为Java在帮我们控制着迭代。

内部迭代提供了一些功能,例如顺序和并行执行,基于给定条件的过滤,映射等。

大多数Java 8 Stream API方法参数都是函数式接口,因此lambda表达式可以很好地与它们一起使用。 让我们看看如何使用Java Stream 在单行语句中编写以上逻辑。

  private static int sumIterator(List<Integer> list) {
    return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
  }

以上程序利用了Java框架的迭代,过滤,和映射方法,提高了效率。

首先,我们将研究Java 8 Stream API的核心概念,然后我们将通过一些示例来了解最常用的方法。

Collections and Java Stream

集合是用于保存值的内存数据结构,在开始使用集合之前,所有值都应已填充。 而Java Stream是按需计算的数据结构。

Java Stream 不存储数据,而是对源数据结构(集合和数组)进行操作,并生成可以使用并执行特定操作的流水线数据。 例如,我们可以从列表创建流并根据条件对其进行过滤。

Java Stream 操作使用函数式接口,这使其非常适合使用lambda表达式的功能编程。 如您在上面的示例中看到的那样,使用lambda表达式使我们的代码可读性强且简短。

Java 8 Stream内部迭代原理有助于实现某些流操作中的延迟查找。 例如,可以延迟实施过滤,映射或重复删除,从而实现更高的性能和优化范围。

Java stream 流是可消耗的,因此无法创建流引用以供将来使用。 由于数据是按需提供的,因此无法多次重复使用同一数据流。

Java 8 Stream支持顺序以及并行处理,并行处理对于实现大型集合的高性能非常有帮助。

所有Java Stream API接口和类都在java.util.stream包中。 由于我们可以使用自动装箱在集合中使用int之类的原始数据类型,并且这些操作可能会花费很多时间,因此有一些针对原始类型的特定类-IntStream,LongStream和DoubleStream。

Functional Interfaces in Java 8 Stream

Java 8 Stream API方法中一些常用的功能接口是:

Function and BiFunction

Function表示一个函数,它接受一种类型的参数并返回另一种类型的参数。 Function <T,R>是通用形式,其中T是函数输入的类型,R是函数结果的类型。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

BiFunction表示一个函数,它接受两种类型的参数并返回另一种类型的参数。 Function <T,U,R>是通用形式,其中T,U是函数输入的类型,R是函数结果的类型。

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

为了处理原始类型,有特定的函数接口-ToIntFunctionToLongFunctionToDoubleFunctionToIntBiFunctionToLongBiFunctionToDoubleBiFunctionLongToIntFunctionLongToDoubleFunctionIntToLongFunctionIntToDoubleFunction等。

使用Function原始类型转化相关的Function接口的Stream方法包括:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
<A> A[] toArray(IntFunction<A[]> generator);
<U> U reduce(U identity,
    BiFunction<U, ? super T, U> accumulator,
    BinaryOperator<U> combiner);

Predicate and BiPredicate 断言

Predicate表示一个函数,它接受一种类型的参数,返回一个布尔值,用于从Java Stream中过滤元素

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Function一样,为了处理原始类型,它也有特定的函数接口-IntPredicate, DoubePredicate, LongPredicate

使用Predicate或BiPredicate作为参数的一些Stream方法是:

Stream<T> filter(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

Consumer and BiConsumer 消费者

Consumer表示一个接受单个输入参数且不返回结果的操作。 它可用于对Java Stream的所有元素执行某些操作。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

使用Consumer,BiConsumer作为参数的Java 8 Stream方法包括:

Stream<T> peek(Consumer<? super T> action);
void forEach(Consumer<? super T> action);
void forEachOrdered(Consumer<? super T> action);

Supplier 供应者

Supplier代表一种操作,通过该操作我们可以在流中生成新值。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Stream中带有Supplier参数的一些方法是:

public static<T> Stream<T> generate(Supplier<T> s)
<R> R collect(Supplier<R> supplier,
            BiConsumer<R, ? super T> accumulator,
            BiConsumer<R, R> combiner);

Java Optional

Java Optional是一个容器对象,可能包含也可能不包含非null值。 如果存在值,则isPresent() 将返回true,而get()将返回该值。 流终端操作返回Optional对象。 其中一些方法是:

Optional<T> reduce(BinaryOperator<T> accumulator)
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
Optional<T> findFirst()
Optional<T> findAny()

Java Stream Intermediate and Terminal Operations

中间操作: 返回新Stream的Java Stream API操作称为中间操作。 在大多数情况下,这些操作本质上都是惰性的,因此中间操作只在终端操作出现时才会执行。 中间操作不会生成最终结果。 常用的中间操作是filtermap

终端操作:返回结果或产生副作用的Java 8 Stream API操作。 一旦在流上调用了终端方法,该流就会被消耗,此后我们将无法使用该流。 终端操作会在返回结果之前处理流中的所有元素。 常用的终端方法是forEachtoArrayminmaxfindFirstanyMatchallMatch等。您可以从返回类型中识别终端方法,它们永远不会返回Stream。

Java Stream Short Circuiting Operations

  • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。例如limit()和skip()是两个短路中间操作

  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。 例如anyMatchallMatchnoneMatchfindFirstfindAny是短路终端操作。

Java Stream Examples

创建java stream

我们可以通过几种方法从数组和集合创建Java流。 让我们用简单的例子来研究它们。

  1. 我们可以使用Stream.of()从相似类型的数据创建流。 例如,我们可以从一组int或Integer对象创建Java整数流。
Stream<Integer> stream = Stream.of(1,2,3,4);
  1. 我们可以使用Stream.of()从对象数组返回流。 请注意,它不支持自动装箱,因此我们无法传递基本类型数组。
Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); 
//works fine

Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); 
//Compile time error, Type mismatch: cannot convert from Stream<int[]> to Stream<Integer>
  1. 我们可以使用Collection stream()创建顺序流,并使用parallelStream()创建并行流。
List<Integer> myList = new ArrayList<>();
for(int i=0; i<100; i++) myList.add(i);
		
//sequential stream
Stream<Integer> sequentialStream = myList.stream();
		
//parallel stream
Stream<Integer> parallelStream = myList.parallelStream();
  1. 我们可以使用Stream.generate()和Stream.iterate()方法创建Stream。
Stream<String> stream1 = Stream.generate(() -> {return "abc";});
Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
  1. 使用Arrays.stream()和String.chars()方法。
LongStream is = Arrays.stream(new long[]{1,2,3,4});
IntStream is2 = "abc".chars();

将Java Stream转换为Collection或Array

  1. 我们可以使用java Stream collect()方法从流中获取List,Map或Set。
Stream<Integer> intStream = Stream.of(1,2,3,4);
List<Integer> intList = intStream.collect(Collectors.toList());
System.out.println(intList); //prints [1, 2, 3, 4]

intStream = Stream.of(1,2,3,4); //stream is closed, so we need to create it again
Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
System.out.println(intMap); //prints {1=11, 2=12, 3=13, 4=14}
  1. 我们可以使用流toArray()方法从流中创建一个数组。
Stream<Integer> intStream = Stream.of(1,2,3,4);
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]

流的组成

image.png

流操作的分类

image.png

流的使用

image.png

java stream 中间操作

  1. filter(): 筛选出符合条件的元素生成新的stream
List<Integer> myList = new ArrayList<>();
for(int i=0; i<100; i++) myList.add(i);
Stream<Integer> sequentialStream = myList.stream();

Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); //filter numbers greater than 90
System.out.print("High Nums greater than 90=");
highNums.forEach(p -> System.out.print(p+" "));
//prints "High Nums greater than 90=91 92 93 94 95 96 97 98 99 "
  1. map(): 将流中的元素映射成新的类型,并生成新的stream
Stream<String> names = Stream.of("aBc", "d", "ef");
System.out.println(names.map(s -> {
		return s.toUpperCase();
	}).collect(Collectors.toList()));
//prints [ABC, D, EF]
  1. sorted(): 我们可以使用sorted()通过传递Comparator参数来对流元素进行排序
Stream<String> names2 = Stream.of("aBc", "d", "ef", "123456");
List<String> reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
System.out.println(reverseSorted); // [ef, d, aBc, 123456]

Stream<String> names3 = Stream.of("aBc", "d", "ef", "123456");
List<String> naturalSorted = names3.sorted().collect(Collectors.toList());
System.out.println(naturalSorted); //[123456, aBc, d, ef]
  1. flatMap(): flatMap 把 输入流中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 输出的新Stream
Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
System.out.println(outputStream.collect(Collectors.toList()));
// [1, 2, 3, 4, 5, 6]

java stream 终端操作

  1. 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。

// 字符串连接,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, 有起始值, 起始值为10
int sumValue = Stream.of(1, 2, 3, 4).reduce(10, 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);
  1. count(): 我们可以使用此终端操作来计算流中的项目数。
Stream<Integer> numbers1 = Stream.of(1,2,3,4,5);
   	
System.out.println("Number of elements in stream="+numbers1.count()); //5
  1. forEach():可用于迭代流。 我们可以用它代替迭代器
Stream<Integer> numbers2 = Stream.of(1,2,3,4,5);
numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
  1. match():让我们看一些Stream API中匹配方法的示例
Stream<Integer> numbers3 = Stream.of(1,2,3,4,5);
System.out.println("Stream contains 4? "+numbers3.anyMatch(i -> i==4));
//Stream contains 4? true

Stream<Integer> numbers4 = Stream.of(1,2,3,4,5);
System.out.println("Stream contains all elements less than 10? "+numbers4.allMatch(i -> i<10));
//Stream contains all elements less than 10? true

Stream<Integer> numbers5 = Stream.of(1,2,3,4,5);
System.out.println("Stream doesn't contain 10? "+numbers5.noneMatch(i -> i==10));
//Stream doesn't contain 10? true