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;
}
上述方法存在三个主要问题:
- 我们只想知道大于10的整数的总和,但我们还必须提供迭代,这也称为外部迭代。
- 该程序本质上是按顺序执行的,我们无法轻松地并行执行此操作。
- 需要很多代码才可以完成一个简单的任务
为了克服上述所有缺点,引入了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);
}
为了处理原始类型,有特定的函数接口-ToIntFunction
,ToLongFunction
,ToDoubleFunction
,ToIntBiFunction
,ToLongBiFunction
,ToDoubleBiFunction
,LongToIntFunction
,LongToDoubleFunction
,IntToLongFunction
,IntToDoubleFunction
等。
使用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操作称为中间操作。 在大多数情况下,这些操作本质上都是惰性的,因此中间操作只在终端操作出现时才会执行。 中间操作不会生成最终结果。 常用的中间操作是filter
和map
。
终端操作:返回结果或产生副作用的Java 8 Stream API操作。 一旦在流上调用了终端方法,该流就会被消耗,此后我们将无法使用该流。 终端操作会在返回结果之前处理流中的所有元素。 常用的终端方法是forEach
,toArray
,min
,max
,findFirst
,anyMatch
,allMatch
等。您可以从返回类型中识别终端方法,它们永远不会返回Stream。
Java Stream Short Circuiting Operations
-
对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。例如limit()和skip()是两个短路中间操作
-
对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。 例如
anyMatch
,allMatch
,noneMatch
,findFirst
和findAny
是短路终端操作。
Java Stream Examples
创建java stream
我们可以通过几种方法从数组和集合创建Java流。 让我们用简单的例子来研究它们。
- 我们可以使用Stream.of()从相似类型的数据创建流。 例如,我们可以从一组int或Integer对象创建Java整数流。
Stream<Integer> stream = Stream.of(1,2,3,4);
- 我们可以使用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>
- 我们可以使用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();
- 我们可以使用Stream.generate()和Stream.iterate()方法创建Stream。
Stream<String> stream1 = Stream.generate(() -> {return "abc";});
Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
- 使用Arrays.stream()和String.chars()方法。
LongStream is = Arrays.stream(new long[]{1,2,3,4});
IntStream is2 = "abc".chars();
将Java Stream转换为Collection或Array
- 我们可以使用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}
- 我们可以使用流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]
流的组成
流操作的分类
流的使用
java stream 中间操作
- 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 "
- 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]
- 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]
- 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 终端操作
- 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);
- count(): 我们可以使用此终端操作来计算流中的项目数。
Stream<Integer> numbers1 = Stream.of(1,2,3,4,5);
System.out.println("Number of elements in stream="+numbers1.count()); //5
- forEach():可用于迭代流。 我们可以用它代替迭代器
Stream<Integer> numbers2 = Stream.of(1,2,3,4,5);
numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
- 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