在流上可以执行很多操作,这些操作分为中间操作(返回Stream)和终结操作(返回确定类型的结果),中间操作允许链式串接。要注意,流上的操作不会改变数据源。
如下例:
long count = list.stream().distinct().count();
这里的_distinct()_方法就是一个内部操作,会在之前流的基础上创建一个元素唯一的新流,而_count()_方法就是一个终结操作,会返回流的大小。
Stream 操作
迭代 Iterating
Stream API可以替换for、for-each、while循环,使用该方法,开发者可以专注于操作的逻辑,而无需关心元素序列的迭代。如:
for (String string : list) {
if (string.contains("a")) {
return true;
}
}
转换为Stream风格只需一行代码:
boolean isExist = list.stream().anyMatch(element -> element.contains("a"));
过滤 Filtering
filter()方法可用于挑选满足断言的流元素,举例来说,如果有一个这样的流:
ArrayList<String> list = new ArrayList<>();
list.add("One");
list.add("OneAndOnly");
list.add("Derek");
list.add("Change");
list.add("factory");
list.add("justBefore");
list.add("Italy");
list.add("Italy");
list.add("Thursday");
list.add("");
list.add("");
下面的代码会创建该列表对应的一个字符串流,找到流中所有包含字符“d”的元素,并将过滤出的元素组成一个新的流:
Stream<String> stream = list.stream().filter(element -> element.contains("d"));
映射 Mapping
如果需要对流中的元素执行特定的函数进行转换,并将转换后的新元素收集到新的流中,可以使用_map()_方法:
List<String> uris = new ArrayList<>();
uris.add("C:\\My.txt");
Stream<Path> stream = uris.stream().map(uri -> Paths.get(uri));
上面的代码会对初始流中的每个元素执行指定的lambda表达式,将Stream
如果有一个流,其中每个元素都包含其对应的一串元素序列,要根据所有内部元素创建一个新流,应该使用_flatMap()_方法:
List<Detail> details = new ArrayList<>();
details.add(new Detail());
Stream<String> stream = details.stream().flatMap(detail -> detail.getParts().stream());
在这个例子中,我们有一个元素为Detail类的列表,Detail类中包含字段PARTS,是一个字符串列表。通过使用flatMap()方法,字段PARTS中的每一个元素都被提取出来并添加到新的结果流中,之后,初始的Stream
匹配 Matching
Stream API提供了一组方便的工具来根据某些断言验证一系列元素,要实现该目标,可以使用以下三个方法之一:_anyMatch(), allMatch(), noneMatch(),_每个函数的功能都一目了然,这些都是返回布尔值的终结操作:
boolean isValid = list.stream().anyMatch(element -> element.contains("h")); // true
boolean isValidOne = list.stream().allMatch(element -> element.contains("h")); // false
boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h")); // false
如果是空流,对于任意给定的断言,_allMatch()_方法都会返回true:
Stream.empty().allMatch(Objects::nonNull); // true
这是一个合理的默认值,因为我们找不到任何不满足断言的元素。
同样的,对于空流,anyMatch() 方法一定会返回false:
Stream.empty().anyMatch(Objects::nonNull); // false
同样,这也是合理的,因为我们找不到满足该条件的元素。
归集 Reduction
Stream API中使用reduce()方法可以根据指定的方法将一系列元素归集为某个值,该方法接收两个参数:第一个是初始值,第二个是累加器函数。
假设您有一个整数列表,并且想要在某个初始值(这里使用23)基础上计算所有元素的总和,可以运行下面的代码,得到结果为26(23+1+1+1):
List<Integer> integers = Arrays.asList(1, 1, 1);
Integer reduced = integers.stream().reduce(23, (a, b) -> a + b);
收集 Collecting
归集操作也可以通过collect()方法实现。在将流转换为集合、映射或使用一个字符串表示一个流时,该操作非常方便。还有一个工具类Collectors,提供了几乎所有常用的收集操作,对于一些复杂的任务,额可以创建自定义收集器。
List<String> resultList = list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
该代码提供最后的collect()方法将字符串流转换为字符串列表。
搜索 Searching
在集合中的搜索意味着根据一个条件查找元素或验证元素的存在性,这个条件也叫做谓词或断言。搜索元素可能有返回值,也可能没有,所以接口返回的是一个Optional;验证元素存在性时返回是的一个布尔值。
下面的示例中,通过findAny()查找元素,通过anyMatch()检查是否存在满足条件的元素。
// searching for a element
Optional<Person> any = people.stream()
.filter(person -> person.getAge() < 20)
.findAny();
// searching for existence
boolean isAnyOneInGroupLessThan20Years = people.stream()
.anyMatch(person -> person.getAge() < 20);
重排序 Reordering
如果需要对集合中的元素进行排序,可以使用Stream中的sorted方法,该方法接收一个Comparator接口的实现类作为参数。可以使用Comparator中的comparing工厂方法来创建对应的实例。
在下面的代码中,结果就是按照Person的age属性降序排列后的集合。
List<Person> peopleSortedEldestToYoungest = people.stream()
.sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());
与我们之前讨论的其它操作不同,排序操作是有状态的。这也就意味着,在将排序结果传递给后续的中间操作或终结操作时,该操作方法必须处理流中的所有元素。还有另一个类似的操作,就是_distinct_。
汇总 Summarizing
有时我们需要从集合中提取一些信息。比如,提取所有用户的年龄的总和,在Stream API中,可以使用终结操作。reduce和collect就是为此目的提供的通用终结操作。还有一些在其基础上创建的高级运算符,如sum、count、summaryStatistics等。
// calculating sum using reduce terminal operator
people.stream()
.mapToInt(Person::getAge)
.reduce(0, (total, currentValue) -> total + currentValue);
// calculating sum using sum terminal operator
people.stream()
.mapToInt(Person::getAge)
.sum();
// calculating count using count terminal operator
people.stream()
.mapToInt(Person::getAge)
.count();
// calculating summary
IntSummaryStatistics ageStatistics = people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
ageStatistics.getAverage();
ageStatistics.getCount();
ageStatistics.getMax();
ageStatistics.getMin();
ageStatistics.getSum();
reduce和collect是归集操作,reduce用于不可变归集,而collect用于可变的归集。不可变归集是首选的方法,但是对于重视性能的场景,应该优先选择可变收集。
分组 Grouping
分组也可以称为分类。有时,我们希望将一个集合分成几个组,在这种情况下产生的数据结构是一个Map,其中key表示分组因子,值为各组对应的属性。Stream API对于此类场景提供了_Collectors.groupingBy_方法。
在下面的例子中,都是要性别对数据进行分组,区别之处在于值。第一个例子中,为每个组创建了Person集合;第二个例子中,通过_Collectors.mapping()_是提取每个用户的姓名,并创建姓名集合;第三个例子中,提取并计算每组的平均年龄。
// Grouping people by gender
Map<Gender, List<Person>> peopleByGender = people.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.toList()));
// Grouping person names by gender
Map<Gender, List<String>> nameByGender = people.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.mapping(Person::getName, Collectors.toList())));
// Grouping average age by gender
Map<Gender, Double> averageAgeByGender = people.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.averagingInt(Person::getAge)));
常见案例
在forEach循环中break
对一组元素进行遍历并对其中元素执行操作时,可以通过Stream中的forEach方法以干净、声明式的方式编写出代码。虽然这与循环是类似的,但是缺少了与break对应的终止迭代的语句。一个流可能很长,甚至是无限的,如果不需要继续对其进行处理,我们希望可以直接终止操作,而不是等到处理完所有元素。
使用自定义Spliterator
在Java 8中引入的Spliterator接口(可拆分迭代器)可用于对序列进行遍历和分区。它是流(尤其是并行流)的基本工具类。
tryAdvance()是单步遍历序列的主要方法。该方法接收一个Consumer作为参数,该消费者用于持续消费spliterator的元素,如果没有可遍历的元素则返回false。
我们可以创建一个自定义的Spliterator 作为_Stream.spliterator_的装饰器,并以此完成break操作。
首先,我们需要获取流的_Spliterator_并使用自定义的_CustomSpliterator_对其进行装饰,这里需要提供控制_break_行为的断言,最后我们再根据_CustomSpliterator_创建新流:
public class CustomTakeWhile {
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
return StreamSupport.stream(customSpliterator, false);
}
}
下面是_CustomSpliterator_的代码:
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
private Spliterator<T> splitr;
private Predicate<T> predicate;
private boolean isMatched = true;
public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
super(splitr.estimateSize(), 0);
this.splitr = splitr;
this.predicate = predicate;
}
@Override
public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem) && isMatched) {
consumer.accept(elem);
} else {
isMatched = false;
}
});
return hadNext && isMatched;
}
}
可以看到上面的tryAdvance()方法,自定义的拆分器处理了装饰的拆分器中的元素,只要断言为真并且初始流中还有元素,就会一直进行处理;如果两个条件中任意为false,拆分器就会break,流操作也会结束。
假设我们有一个字符串项流,只要其中元素的长度是奇数,我们就持续处理其元素。测试代码如下:
@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
List<String> result = CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
.collect(Collectors.toList());
assertEquals(asList("cat", "dog"), result);
}
使用自定义forEach
虽然提供嵌入的break机制可能很有用,但是只关注forEach操作可能会更简单。
我们可以直接使用Stream.spliterator:
public class CustomForEach {
public static class Breaker {
private boolean shouldBreak = false;
public void stop() {
shouldBreak = true;
}
boolean get() {
return shouldBreak;
}
}
public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {
Spliterator<T> spliterator = stream.spliterator();
boolean hadNext = true;
Breaker breaker = new Breaker();
while (hadNext && !breaker.get()) {
hadNext = spliterator.tryAdvance(elem -> {
consumer.accept(elem, breaker);
});
}
}
}
可以看到,自定义的forEach方法会调用Biconsumer来处理下一个元素和用于终止流程的breaker。
测试代码如下:
@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
List<String> result = new ArrayList<>();
CustomForEach.forEach(initialStream, (elem, breaker) -> {
if (elem.length() % 2 == 0) {
breaker.stop();
} else {
result.add(elem);
}
});
assertEquals(asList("cat", "dog"), result);
}
Stream.takeWhile() (Java 9)
如果使用的是Java 9,可以使用_Stream.takeWhile()_方法,如下:
Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
.takeWhile(n -> n.length() % 2 != 0)
.forEach(System.out::println);
运行结果为:
cat
dog
其等价的循环代码为:
List<String> list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck");
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
if (item.length() % 2 == 0) {
break;
}
System.out.println(item);
}
过滤Optionals流
这一节讨论一下如何过滤出Optionals流中的非空值呢?
假设我们有一个下面所示的流:
List<Optional<String>> listOfOptionals = Arrays.asList(Optional.empty(), Optional.of("foo"), Optional.empty(), Optional.of("bar"));
使用filter()
可以使用_Optional::isPresent_过滤所有包含值的_optionals,_如何通过map操作执行_Optional::get_提取出其中的值:
List<String> filteredList = listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
使用flatmap()
还有一种方式是将flatMap与lambda函数一起使用,函数会将空的Optional转换为空流,将非空的Optional转换为只包含一个元素的流,然后将流汇聚:
List<String> filteredList = listOfOptionals.stream()
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.collect(Collectors.toList());
也可以使用其它的转换方式来实现:
List<String> filteredList = listOfOptionals.stream()
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
Optional::stream(Java 9)
Java9中,向Optional加入了stream方法,简化了上面的操作,其实现与上面flatMap的方式类似,只是换成了系统提供的函数。
根据Optional中是否包含值,会将其对应转换为包含一个或零个元素的流。
List<String> filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
合并不同的流
使用Java原生接口
合并两个流的话,可以使用静态方法Stream.concat() :
@Test
public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() {
Stream<Integer> stream1 = Stream.of(1, 3, 5);
Stream<Integer> stream2 = Stream.of(2, 4, 6);
Stream<Integer> resultingStream = Stream.concat(stream1, stream2);
assertEquals(Arrays.asList(1, 3, 5, 2, 4, 6),
resultingStream.collect(Collectors.toList()));
}
如果要合并的流不止两个,这种方式就稍微复杂一点。可以采用的方法就是,先合并前两个流,然后依次合并后面的流直到全部合并完成。
@Test
public void given3Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream<Integer> stream1 = Stream.of(1, 3, 5);
Stream<Integer> stream2 = Stream.of(2, 4, 6);
Stream<Integer> stream3 = Stream.of(18, 15, 36);
Stream<Integer> resultingStream = Stream.concat(
Stream.concat(stream1, stream2), stream3);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36),
resultingStream.collect(Collectors.toList()));
}
可以看到,如果要合并的流比较多,这种方写出的代码是很不优雅的,当然也可以通过创建中间变量或者辅助方法使其更具可读性。
但是,我们还有更优雅的方式:
@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream<Integer> stream1 = Stream.of(1, 3, 5);
Stream<Integer> stream2 = Stream.of(2, 4, 6);
Stream<Integer> stream3 = Stream.of(18, 15, 36);
Stream<Integer> stream4 = Stream.of(99);
Stream<Integer> resultingStream = Stream.of(
stream1, stream2, stream3, stream4)
.flatMap(i -> i);
assertEquals(
Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
resultingStream.collect(Collectors.toList()));
}
这里主要分为两步:
首先创建包含四个流的新流,其结果为_Stream<Stream
>_。 然后使用_flatMap()_和恒定式将其转换为一个_Stream
_。 使用StreamEx
StreamEx是一个开源Java库,它对Java 8 中的Streams接口进行了扩展,其使用_StreamEx_类作为对JDK的流接口的增强。
StreamEx提供了_append()_方法来合并流:@Test public void given4Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> stream1 = Stream.of(1, 3, 5); Stream<Integer> stream2 = Stream.of(2, 4, 6); Stream<Integer> stream3 = Stream.of(18, 15, 36); Stream<Integer> stream4 = Stream.of(99); Stream<Integer> resultingStream = StreamEx.of(stream1) .append(stream2) .append(stream3) .append(stream4); assertEquals( Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99), resultingStream.collect(Collectors.toList())); }
如果这里的resultingStream类型是StreamEx,可以直接调用toList()方法创建元素列表。
StreamEx还提供了prepend()方法,可以将流中元素加在其它流之前:@Test public void given3Streams_whenPrepended_thenResultStreamContainsAllElements() { Stream<String> stream1 = Stream.of("foo", "bar"); Stream<String> openingBracketStream = Stream.of("["); Stream<String> closingBracketStream = Stream.of("]"); Stream<String> resultingStream = StreamEx.of(stream1) .append(closingBracketStream) .prepend(openingBracketStream); assertEquals( Arrays.asList("[", "foo", "bar", "]"), resultingStream.collect(Collectors.toList())); }
使用Jooλ
Jooλ是一个与JDK8兼容的扩展库,其中最重要的流抽象是Seq,表明这是有序流,因此调用_parallel()_方法是无效的。
与StreamEx一样,Jooλ中也提供了append()方法:@Test public void given2Streams_whenMerged_thenResultStreamContainsAllElements() { Stream<Integer> seq1 = Stream.of(1, 3, 5); Stream<Integer> seq2 = Stream.of(2, 4, 6); Stream<Integer> resultingSeq = Seq.ofType(seq1, Integer.class) .append(seq2); assertEquals( Arrays.asList(1, 3, 5, 2, 4, 6), resultingSeq.collect(Collectors.toList())); }
当然,既然有append()方法,Jooλ中也提供了prepend()方法:
@Test public void given3Streams_whenPrepending_thenResultStreamContainsAllElements() { Stream<String> seq = Stream.of("foo", "bar"); Stream<String> openingBracketSeq = Stream.of("["); Stream<String> closingBracketSeq = Stream.of("]"); Stream<String> resultingStream = Seq.ofType(seq, String.class) .append(closingBracketSeq) .prepend(openingBracketSeq); Assert.assertEquals( Arrays.asList("[", "foo", "bar", "]"), resultingStream.collect(Collectors.toList())); }
使用索引对流进行迭代
Java 8 Streams不是集合,因而无法使用索引来访问其中的元素,但是仍然有一些技巧可以实现这一点。
使用原生Java
由于原始元素位于可通过索引访问的数组或集合中,我们可以通过一定范围内的整数来访问流元素。
下面我们得到一个字符串数组,并且只选出被索引指向的字符串:public List<String> getEvenIndexedStrings(String[] names) { List<String> evenIndexedNames = IntStream .range(0, names.length) .filter(i -> i % 2 == 0) .mapToObj(i -> names[i]) .collect(Collectors.toList()); return evenIndexedNames; }
可以通过以下代码测试:
@Test public void whenCalled_thenReturnListOfEvenIndexedStrings() { String[] names = {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"}; List<String> expectedResult = Arrays.asList("Afrim", "Besim", "Durim"); List<String> actualResult = StreamIndices.getEvenIndexedStrings(names); assertEquals(expectedResult, actualResult); }
使用 StreamUtils
使用索引进行迭代的另一种方式是使用proton-pack库中的StreamUtils工具类,其中的zipWithIndex()方法(可以在这里找到最新版本)。
早pom文件中增加以下配置导入依赖:<dependency> <groupId>com.codepoetics</groupId> <artifactId>protonpack</artifactId> <version>1.13</version> </dependency>
使用方式如下:
public List<Indexed<String>> getEvenIndexedStrings(List<String> names) { List<Indexed<String>> list = StreamUtils .zipWithIndex(names.stream()) .filter(i -> i.getIndex() % 2 == 0) .collect(Collectors.toList()); return list; }
测试:
@Test public void whenCalled_thenReturnListOfEvenIndexedStrings() { List<String> names = Arrays.asList( "Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"); List<Indexed<String>> expectedResult = Arrays.asList( Indexed.index(0, "Afrim"), Indexed.index(2, "Besim"), Indexed.index(4, "Durim")); List<Indexed<String>> actualResult = StreamIndices.getEvenIndexedStrings(names); assertEquals(expectedResult, actualResult); }
使用 StreamEx
使用StreamEx中EntryStream提供的方法filterKeyValue() 可以依照索引进行迭代。
pom依赖:<dependency> <groupId>one.util</groupId> <artifactId>streamex</artifactId> <version>0.6.5</version> </dependency>
实现与前面演示相同功能:
public List<String> getEvenIndexedStringsVersionTwo(List<String> names) { return EntryStream.of(names) .filterKeyValue((index, name) -> index % 2 == 0) .values() .toList(); }
对其进行测试:
@Test public void whenCalled_thenReturnListOfEvenIndexedStringsVersionTwo() { String[] names = {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"}; List<String> expectedResult = Arrays.asList("Afrim", "Besim", "Durim"); List<String> actualResult = StreamIndices.getEvenIndexedStrings(names); assertEquals(expectedResult, actualResult); }
使用 Vavr中的Stream
还可以使用Vavr的Stream类中的zipWithIndex() 方法。
引入依赖:<dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.9.0</version> </dependency>
实现相同功能
public List<String> getOddIndexedStringsVersionTwo(String[] names) { return Stream .of(names) .zipWithIndex() .filter(tuple -> tuple._2 % 2 == 1) .map(tuple -> tuple._1) .toJavaList(); }
测试:
@Test public void whenCalled_thenReturnListOfOddStringsVersionTwo() { String[] names = {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"}; List<String> expectedResult = Arrays.asList("Bashkim", "Lulzim", "Shpetim"); List<String> actualResult = StreamIndices.getOddIndexedStringsVersionTwo(names); assertEquals(expectedResult, actualResult); }
将Iterable_转换为_Stream
Iterable接口在设计时考虑了通用性,没有在其中添加stream()方法。但是我们可以将其传递给StreamSupport.stream()方法,然后从传入的Iterable实例中获取一个流。
假设我们有一个Iterable实例:Iterable<String> iterable = Arrays.asList("Testing", "Iterable", "conversion", "to", "Stream");
我们通过以下方式将其转换为流:
StreamSupport.stream(iterable.spliterator(), false);
该方法的第二个参数决定返回的结果流是不是并行流,如果参数为true,则返回并行流。
补充一个简短的说明——流不可重用,但是Iterable可重用; 它还提供了spliterator()方法,该方法在Iterable所描述的元素上返回一个Spliterator实例。
测试代码如下:@Test public void whenConvertedToList_thenCorrect() { Iterable<String> iterable = Arrays.asList("Testing", "Iterable", "conversion", "to", "Stream"); List<String> result = StreamSupport.stream(iterable.spliterator(), false) .map(String::toUpperCase) .collect(Collectors.toList()); assertThat( result, contains("TESTING", "ITERABLE", "CONVERSION", "TO", "STREAM")); }
对流进行debug-peek()方法
peek()方法的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());
这段代码演示了我们如何观察传递到某个节点的元素。
此外,peek()方法在另外一个场景下也很有用:需要改变元素的内部状态时。举例来说,如果我们想要在最终的打印操作之前,将用户的姓名转为小写,可以使用下面的代码:Stream<User> userStream = Stream.of(new User("Alice"), new User("Bob"), new User("Chuck")); userStream.peek(u -> u.setName(u.getName().toLowerCase())) .forEach(System.out::println);
对流中的数值元素求和
使用Stream.reduce()
Stream.reduce()是一个终结操作,对流中的元素执行归集运算。
在该操作中,可以对流中的每个元素应用二进制操作符(累加器),其中第一个操作数是前一次运算的结果,第二个操作数是当前流元素。
可以使用一个lambda表达式,将两个整数相加并返回求和后整数值:List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); Integer sum = integers.stream() .reduce(0, (a, b) -> a + b);
此外,也可以使用Java中已有的方法:
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); Integer sum = integers.stream() .reduce(0, Integer::sum);
当然,也可以自定义的求和方法:
public class ArithmeticUtils { public static int add(int a, int b) { return a + b; } }
// 将上面的函数作为参数传入reduce()方法
List
Integer sum = integers.stream()
.reduce(0, ArithmeticUtils::add);
#### 使用Stream.collect()
```java
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream()
.collect(Collectors.summingInt(Integer::intValue));
Collectors中也提供了summingLong() 和summingDouble() 方法来分别计算long和double的和。
使用IntStream.sum()
Stream API为我们提供了mapToInt()中间操作,该操作将我们的流转换为IntStream对象。
该方法将一个映射器作为参数,用于进行转换,然后我们可以调用sum()方法来计算流元素的总和。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream()
.mapToInt(Integer::intValue)
.sum();
使用Stream中的sum操作处理Object元素
假设我们有一个对象列表,并且想计算所有这些对象中给定字段的取值的总和。
举例来说,有下面的类:
public class Item {
private int id;
private Integer price;
public Item(int id, Integer price) {
this.id = id;
this.price = price;
}
// Standard getters and setters
}
然后,我们有该类的对象列表,并且要计算其中所有项的价格总和:
Item item1 = new Item(1, 10);
Item item2 = new Item(2, 15);
Item item3 = new Item(3, 25);
Item item4 = new Item(4, 40);
List<Item> items = Arrays.asList(item1, item2, item3, item4);
可以进行如下处理:
Integer sum = items.stream()
.map(x -> x.getPrice())
.collect(Collectors.summingInt(Integer::intValue));
或
items.stream()
.mapToInt(x -> x.getPrice())
.sum();