正如我们在Java Streams:流创建中所学到的,流管道由源、零个或多个中间操作和一个终端操作组成。
我们还了解到,streams流是懒惰的;仅当终端操作启动时,才对源数据执行计算。
在本文中,我们将进一步探讨streams流操作。
streams流操作
流操作可以是中间操作,也可以是终端操作。中间操作产生另一个流。同样,终端操作也会产生结果或副作用。
让我们看看Java流提供的一些操作。
过滤流
您可以通过将predicate (T -> boolean)
传递给filter
方法来过滤流。过滤器是返回过滤流的中间操作。如果您熟悉SQL,可以将谓词视为where
子句。
List<Book> javaBooks =
books.stream()
.filter(book -> book.getCategory().equals(JAVA))
.collect(Collectors.toList());
这里根据类别过滤图书流。谓词函数是一个lambda函数book->book.getCategory().equals(JAVA)
。
时断时续
假设你需要找到所有价格低于42美元的书。您可以执行以下操作:
List<Book> lessThan42 =
books.stream()
.filter(book -> book.getPrice() < 42)
.collect(Collectors.toList());
上述方法的一个问题是,即使对书籍进行了排序,过滤器也会遍历整个列表。我们可以使用takeWhile as
将流短路:
List<Book> priceLessThan42 =
books.stream()
.takeWhile(book -> book.getPrice() < 42)
.collect(Collectors.toList());
takeWhile
是在Java9中引入的。
类似地,您可以使用dropWhile
删除流中与给定条件匹配的元素。此操作是takeWhile
的补充。
List<Book> priceGreaterThan42 =
books.stream()
.dropWhile(book -> book.getPrice() < 42)
.collect(Collectors.toList());
如果流无序,并且该流的某些(但不是全部)元素与给定谓词匹配,则takeWhile
/dropWhile
操作的行为是不确定的。在这种情况下,它可以返回匹配元素的任何子集,包括空流。
查找流的不同元素
distinct()
方法通过应用equals()
方法返回由distinct
元素组成的流。这是一个有状态的中间操作。
List<String> publisher =
books.stream()
.map(book -> book.getPublisher())
.distinct()
.collect(Collectors.toList());
有状态操作的问题
如果传递给流操作的函数是有状态的,则流管道结果可能是不确定的或不正确的。有状态操作的结果取决于流管道执行期间可能更改的任何状态。
有状态lambda的一个示例是map()
的参数:
Set<Integer> seen = Collections
.synchronizedSet(new HashSet<>());
stream.parallel()
.map(e -> { if (seen.add(e)) return 0; else return e; })...
这里,如果并行执行映射操作,由于线程调度的差异,相同输入的结果可能会因运行而异。然而,对于无状态lambda表达式,结果总是相同的。
截流
您可以通过对流使用限制操作使流短路来截断流。限制是一个有状态的操作。这是一个非常便宜的顺序流操作。但是,在有序并行流上可能非常昂贵。
books.stream().limit(2)
.collect(Collectors.toList());
可以使用skip(n)
操作放弃流的前n个元素。与limit
类似,在有序并行管道上,这可能是一个非常昂贵的操作。
books.stream().skip(2)
.collect(Collectors.toList());
映射流
可以使用映射函数将一个流映射到另一个流。
map
我们可以应用map
函数将一个流转换为另一个流。例如,将Stream<Book>
映射到作者的Stream<String>
可以作为books.Stream().map(Book::getAuthor)
完成。map
操作的结果是在流的每个元素上应用函数function (T) -> R
之后的另一个流。
static List<String> mapToAuthors(List<Book> books) {
return books.stream()
.map(Book::getAuthor)
.collect(Collectors.toList());
}
传递给映射操作的函数应该是无干扰和无状态的;否则,可能会出现不希望出现的行为,并有可能在将来破坏代码。
FlatMap
flatMap
操作将一对多转换应用于流的元素,并将结果元素展平为新的流。
<R> Stream<R> flatMap(Function<? super T
, ? extends Stream<? extends R>> mapper);
与map
一样,传递给flatMap
操作的mapper
函数应该是无干扰和无状态的。
Set<String> s =
Stream.of(set1, set2)
.flatMap(Set::stream)
.collect(Collectors.toSet());
map
和flatMap
操作之间的区别是–map仅应用变换。然而,flatMap
也会使流变平。
static void flatMap() {
List<Integer> primeNumbers =
Arrays.asList(2, 3, 5, 7, 11, 13);
List<Integer> evenNumbers =
Arrays.asList(2, 4, 6, 8);
List<List<Integer>> evenOrPrime =
Arrays.asList(primeNumbers, evenNumbers);
List<Integer> numbers =
evenOrPrime.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
log.info("Flattened map : {}", numbers);
}
上面的示例打印–展平地图:[2,3,5,7,11,13,2,4,6,8]。如您所见,flatMap将List<Integer>流展平为Integer流。
映射到原始流
通过分别调用方法mapToInt、mapToDouble和mapToLong,可以将流<T>映射到专用的基本流IntStream、DoubleStream和LongStream。
static DoubleStream mapToPrice(List<Book> books) {
return books.stream()
.mapToDouble(Book::getPrice);
}
通过调用boxed as,可以将专用基本流转换回非专用流:
static Stream<Double> box(DoubleStream doubleStream) {
return doubleStream.boxed();
}
流的匹配元素
Stream类提供了在流中查找匹配元素的方法。有两种方法。返回布尔值,例如anyMatch。另一个返回可选的,例如findAny。
Any Match
如果找到给定谓词的任何匹配项,anyMatch
方法将返回true。这是一种短路终端操作。对于空流,此操作返回false,并且不计算流。
static boolean hasAnyJavaBook(List<Book> books) {
return books.stream()
.anyMatch(book -> book.getCategory().equals(JAVA));
}
All Match
当流的元素与给定谓词匹配时,allMatch
方法返回true。这是一种短路终端操作。对于空流,此操作返回true,并且不计算流。
static boolean hasAllJavaBook(List<Book> books) {
return books.stream()
.allMatch(book -> book.getCategory().equals(JAVA));
}
None Match
如果流中没有与给定谓词匹配的元素,则noneMatch
方法返回true。这是一种短路终端操作。对于空流,此操作返回true,并且不计算流。
static boolean hasNoJavaBook(List<Book> books) {
return books.stream()
.noneMatch(book -> book.getCategory().equals(JAVA));
}
查找流的元素
Find Any
findAny
方法返回描述流的任意元素的可选值,如果流为空,则返回空可选值。这是一种短路终端操作。此操作的行为是不确定的;它可以返回流的任何元素。
在并行流的情况下,不确定性行为允许实现最佳性能。
static Optional<Book> findAnyJavaBook(List<Book> books) {
return books.stream()
.filter(book -> book.getCategory().equals(JAVA))
.findAny();
}
Optional<T>类(java.util.Optional)是一个容器对象,用于表示值T的存在或不存在。如果findAny没有找到任何元素,那么它将返回空的Optional而不是null。
Find First
findFirst
方法返回表示流的第一个元素的可选值,如果流为空,则返回空可选值。如果流没有遇到顺序,则可以返回任何元素。
static Optional<Book> findFirstJavaBook(List<Book> books) {
return books.stream()
.filter(book -> book.getCategory().equals(Category.JAVA))
.findFirst();
}
在并行流中查找第一个元素是昂贵的。如果您不关心返回哪个元素,我们应该使用findAny。
还原操作
还原操作(也称为折叠)通过重复应用组合操作,获取一系列输入元素并将它们组合成单个结果。流类有一些通用的缩减操作,如reduce()和collect(),还有一些专门的缩减操作,如min()、max()等。
Reduce
reduce方法T reduce(T identity,BinaryOperator<T>累加器)使用基于初始标识值的关联累加器函数(BinaryOperator:T,U->R)对流执行reduce操作。这是一个终端操作。
static double sum() {
List<Integer> numbers =
List.of(1, 2, 3, 4, 5);
return numbers.stream()
.reduce(0, (a, b) -> a + b);
}
或者使用方法引用:
private static double sum() {
List<Integer> numbers =
List.of(1, 2, 3, 4, 5);
return numbers.stream()
.reduce(0, Integer::sum);
}
如果流为空,则返回标识(初始)值。reduce方法的另一个变体是'Optional<T>reduce(BinaryOperator<T>累加器)'返回Optional<T>(对于空流)。
Min和Max
“减少”操作可用于查找最小值和最大值,如下所示:
List<Integer> numbers =
List.of(1,2,3,4,5);
Optional<Integer> min =
numbers.stream().reduce(Integer::min);
Optional<Integer> max =
numbers.stream().reduce(Integer::max);
总结
流操作可以是中间操作,也可以是终端操作。中间操作产生另一个流。同样,终端操作也会产生结果或副作用。
- 您可以使用
filter
、distinct
、takeWhile
、dropWhile
、skip
和limit
方法对流进行过滤和切片。 - 可以使用“
map
”和“flatMap
”方法变换流的元素。flatMap
操作也会使流变平。 - 您可以使用
findFirst
和findAny
方法在流中查找元素。 - 可以通过对给定谓词使用
allMatch
、anyMatch
和anyMatch
方法来匹配流。 - 您可以使用
reduce
组合流的元素。
原文地址:https://techdozo.dev/java-streams-operation/
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2407.html
暂无评论