在流上可以执行很多操作,这些操作分为中间操作(返回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”));
 过滤 Filteringfilter()方法可用于挑选满足断言的流元素,举例来说,如果有一个这样的流:
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<Gender, List> peopleByGender = people.stream()
 .collect(Collectors.groupingBy(Person::getGender, Collectors.toList()));
 // Grouping person names by gender
 Map<Gender, List> 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 extends Spliterators.AbstractSpliterator {
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 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 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<Optional> listOfOptionals = Arrays.asList(Optional.empty(), Optional.of(“foo”), Optional.empty(), Optional.of(“bar”));
使用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<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 stream1 = Stream.of(1, 3, 5);
 Stream stream2 = Stream.of(2, 4, 6);
 Stream 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 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<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 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<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 stream1 = Stream.of(“foo”, “bar”);
 Stream openingBracketStream = Stream.of(“[”);
 Stream 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 seq1 = Stream.of(1, 3, 5);
 Stream 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 seq = Stream.of(“foo”, “bar”);
 Stream openingBracketSeq = Stream.of(“[”);
 Stream 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()));
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 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<Indexed> getEvenIndexedStrings(List names) {
 List<Indexed> list = StreamUtils
 .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> expectedResult = Arrays.asList(
 Indexed.index(0, “Afrim”),
 Indexed.index(2, “Besim”),
 Indexed.index(4, “Durim”));
 List<Indexed> actualResult
 = 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<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 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();