stream list 过滤出一个字段的列表_Java 8 Stream API详解( 三)——Stream操作

fac6acd8a6ada8988ebc626b02fd88e6.png

在流上可以执行很多操作,这些操作分为中间操作(返回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<String>转换为Stream<Path>。

如果有一个流,其中每个元素都包含其对应的一串元素序列,要根据所有内部元素创建一个新流,应该使用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<Detail>会被丢弃。

匹配 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()));
}

这里主要分为两步:

  1. 首先创建包含四个流的新流,其结果为Stream<Stream<Integer>>
  2. 然后使用flatMap()和恒定式将其转换为一个Stream<Integer>

使用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> integers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = integers.stream()
  .reduce(0, ArithmeticUtils::add);

使用Stream.collect()

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();
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值