在流上可以执行很多操作,这些操作分为中间操作(返回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 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 stream = list.stream().filter(element -> element.contains(“d”));
映射 Mapping
如果需要对流中的元素执行特定的函数进行转换,并将转换后的新元素收集到新的流中,可以使用map()方法:
List uris = new ArrayList<>();
uris.add(“C:\My.txt”);
Stream stream = uris.stream().map(uri -> Paths.get(uri));
上面的代码会对初始流中的每个元素执行指定的lambda表达式,将Stream转换为Stream 。
如果有一个流,其中每个元素都包含其对应的一串元素序列,要根据所有内部元素创建一个新流,应该使用flatMap()方法:
List details = new ArrayList<>();
details.add(new Detail());
Stream 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 integers = Arrays.asList(1, 1, 1);
Integer reduced = integers.stream().reduce(23, (a, b) -> a + b);
收集 Collecting
归集操作也可以通过collect()方法实现。在将流转换为集合、映射或使用一个字符串表示一个流时,该操作非常方便。还有一个工具类Collectors,提供了几乎所有常用的收集操作,对于一些复杂的任务,额可以创建自定义收集器。
List resultList = list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
该代码提供最后的collect()方法将字符串流转换为字符串列表。
搜索 Searching
在集合中的搜索意味着根据一个条件查找元素或验证元素的存在性,这个条件也叫做谓词或断言。搜索元素可能有返回值,也可能没有,所以接口返回的是一个Optional;验证元素存在性时返回是的一个布尔值。
下面的示例中,通过findAny()查找元素,通过anyMatch()检查是否存在满足条件的元素。
// searching for a element
Optional 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 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
.collect(Collectors.groupingBy(Person::getGender, Collectors.toList()));
// Grouping person names by gender
Map
.collect(Collectors.groupingBy(Person::getGender, Collectors.mapping(Person::getName, Collectors.toList())));
// Grouping average age by gender
Map
.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 Stream takeWhile(Stream stream, Predicate predicate) {
CustomSpliterator customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
return StreamSupport.stream(customSpliterator, false);
}
}
下面是CustomSpliterator的代码:
public class CustomSpliterator extends Spliterators.AbstractSpliterator {
private Spliterator splitr;
private Predicate predicate;
private boolean isMatched = true;
public CustomSpliterator(Spliterator splitr, Predicate 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 initialStream = Stream.of(“cat”, “dog”, “elephant”, “fox”, “rabbit”, “duck”);
List 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 void forEach(Stream stream, BiConsumer consumer) {
Spliterator 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 initialStream = Stream.of(“cat”, “dog”, “elephant”, “fox”, “rabbit”, “duck”);
List 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 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
使用filter()
可以使用Optional::isPresent过滤所有包含值的optionals,如何通过map操作执行Optional::get提取出其中的值:
List filteredList = listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
使用flatmap()
还有一种方式是将flatMap与lambda函数一起使用,函数会将空的Optional转换为空流,将非空的Optional转换为只包含一个元素的流,然后将流汇聚:
List filteredList = listOfOptionals.stream()
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.collect(Collectors.toList());
也可以使用其它的转换方式来实现:
List 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 filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
合并不同的流
使用Java原生接口
合并两个流的话,可以使用静态方法Stream.concat() :
@Test
public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream 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 stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream 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 stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream stream4 = Stream.of(99);
Stream 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
然后使用flatMap()和恒定式将其转换为一个Stream。
使用StreamEx
StreamEx是一个开源Java库,它对Java 8 中的Streams接口进行了扩展,其使用StreamEx类作为对JDK的流接口的增强。
StreamEx提供了append()方法来合并流:
@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
Stream stream1 = Stream.of(1, 3, 5);
Stream stream2 = Stream.of(2, 4, 6);
Stream stream3 = Stream.of(18, 15, 36);
Stream stream4 = Stream.of(99);
Stream 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 stream1 = Stream.of(“foo”, “bar”);
Stream openingBracketStream = Stream.of(“[”);
Stream closingBracketStream = Stream.of(“]”);
Stream 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 seq1 = Stream.of(1, 3, 5);
Stream seq2 = Stream.of(2, 4, 6);
Stream 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 seq = Stream.of(“foo”, “bar”);
Stream openingBracketSeq = Stream.of(“[”);
Stream closingBracketSeq = Stream.of(“]”);
Stream 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 getEvenIndexedStrings(String[] names) {
List 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 expectedResult
= Arrays.asList(“Afrim”, “Besim”, “Durim”);
List actualResult
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
使用 StreamUtils
使用索引进行迭代的另一种方式是使用proton-pack库中的StreamUtils工具类,其中的zipWithIndex()方法(可以在这里找到最新版本)。
早pom文件中增加以下配置导入依赖:
com.codepoetics protonpack 1.13 使用方式如下:public List
List
.zipWithIndex(names.stream())
.filter(i -> i.getIndex() % 2 == 0)
.collect(Collectors.toList());
return list;
}
测试:
@Test
public void whenCalled_thenReturnListOfEvenIndexedStrings() {
List names = Arrays.asList(
“Afrim”, “Bashkim”, “Besim”, “Lulzim”, “Durim”, “Shpetim”);
List
Indexed.index(0, “Afrim”),
Indexed.index(2, “Besim”),
Indexed.index(4, “Durim”));
List
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
使用 StreamEx
使用StreamEx中EntryStream提供的方法filterKeyValue() 可以依照索引进行迭代。
pom依赖:
one.util streamex 0.6.5 实现与前面演示相同功能:public List getEvenIndexedStringsVersionTwo(List 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 expectedResult
= Arrays.asList(“Afrim”, “Besim”, “Durim”);
List actualResult
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
使用 Vavr中的Stream
还可以使用Vavr的Stream类中的zipWithIndex() 方法。
引入依赖:
io.vavr vavr 0.9.0 实现相同功能public List 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 expectedResult
= Arrays.asList(“Bashkim”, “Lulzim”, “Shpetim”);
List actualResult
= StreamIndices.getOddIndexedStringsVersionTwo(names);
assertEquals(expectedResult, actualResult);
}
将Iterable转换为Stream
Iterable接口在设计时考虑了通用性,没有在其中添加stream()方法。但是我们可以将其传递给StreamSupport.stream()方法,然后从传入的Iterable实例中获取一个流。
假设我们有一个Iterable实例:
Iterable iterable = Arrays.asList(“Testing”, “Iterable”, “conversion”, “to”, “Stream”);
我们通过以下方式将其转换为流:
StreamSupport.stream(iterable.spliterator(), false);
该方法的第二个参数决定返回的结果流是不是并行流,如果参数为true,则返回并行流。
补充一个简短的说明——流不可重用,但是Iterable可重用; 它还提供了spliterator()方法,该方法在Iterable所描述的元素上返回一个Spliterator实例。
测试代码如下:
@Test
public void whenConvertedToList_thenCorrect() {
Iterable iterable = Arrays.asList(“Testing”, “Iterable”, “conversion”, “to”, “Stream”);
List 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 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 integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream()
.reduce(0, (a, b) -> a + b);
此外,也可以使用Java中已有的方法:
List 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 integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream()
.reduce(0, ArithmeticUtils::add);
使用Stream.collect()
List 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 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 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();