Java 8 Stream API和收集器的完整指南


Map / Filter / Reduce会涉及到什么? 让我们举个例子。我们将列出一个Person实例。

List<Person> persons = new ArrayList<>();

现在,假设我们要从该列表中计算20岁以上人群的平均年龄。

我们将如何进行? 映射步骤。 该映射采用一个人员列表,并返回一个整数列表。 两个列表的大小相同。

2.过滤步骤

获取年龄列表(整数列表),并返回整数列表。 如果我的筛选只是一个大于20的谓词边,那么在返回列表中,我的所有年龄都大于20。

3.Reduction step

我们只是说它等同于SQL聚合。现在,什么是SQL聚合?例如,它是元素之和或最大值,均值或平均值之类的东西。 这只是一个简单的函数,可以将示例中的所有整数恢复为一个整数。 什么是Stream?

public interface Stream<T> extends BaseStream<T,Stream<T>> {
   // ....
}

Stream是Java类型的接口。此类型为T。这意味着我们可以有整数流,人流,客户流,字符串流等。 它提供了一种有效处理JVM内部数据的方法。它可以有效地处理大量数据。 它可以并行处理数据,以利用多个CPU的计算能力。 该过程在管道中进行,因为它将避免不必要的中间计算。 Java 8中流的定义 为什么Collection不能成为Stream? Stream是一个新概念,我们不想更改集合API的工作方式。

什么是Stream? 我可以在其上定义操作的对象,并且可以通过操作定义映射,过滤器或归约操作。 不包含任何数据的对象。 不允许流更改其处理的数据的对象。 能够一次处理数据的对象。 应该从算法的角度优化对象,并能够并行处理数据。

建立和消费流

我们如何建立流? 好吧,事实上,我们有许多模式可以构建流。

让我们看看第一个,可能是最有用的一个。我们有一个stream方法已被添加到collection接口中,因此可以调用人员。信息流将在人员列表上打开信息流。

List<Person> persons = new ArrayList<Person>
Stream<Person> streams = person.stream();
  1. forEach 在流接口上定义的forEach方法,并将其传递给使用者。
@FunctionalInterface
public interface Consumer<T> {
// ..
}

让我们看一下消费者界面。它是一个功能接口,因此只有一种抽象方法。这是功能接口的定义。可以通过lambda表达式实现。

streams.forEach(p -> System.out.println(p));

它也可以作为方法参考System编写。out :: println。

streams.forEach(System.out::println);

实际上,Consumer有点复杂。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after){
       Objects.requireNonNull(after);
       return (T t) -> accept(t); after.accept(t); };
    }
}

这将使我们能够链接消费者。

List<String> result = new ArrayList<>();
List<Person> persons = ... ;
Consumer<String> c1 = result::add;
Consumer<String> c2 = System.out::println;
persons.stream()
           .forEach(c1.addThen(c2));

因为forEach()不返回任何内容。

2.Filtering a Stream

它获取在数据源上定义的流,并根据谓词过滤掉该数据的一部分。

List<Person> persons = ... ;
Stream<Person> stream = persons.stream();
Stream<Person> filtered = stream.filter(
                  person -> person.age() > 20 );

我们在这里使用谓词,只是检查该人的年龄是否大于20。 以谓词为参数。这是谓词。

Predicate<Person> p = person -> person.age() > 20 ;

这是一个正则表达式,老兄。getAge> 20。 让我们看一下Predicate接口,它有一个称为test的单一方法,该方法将一个对象作为参数并返回一个布尔值。

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);
  default Predicate<T> and(Predicate<? super T> other);
  static <T> Predicate<T> isEqual(Object targetRef);
  default Predicate<T> negate();
  default Predicate<T>or(Predicate<? super T> other);
}

我们必须谨慎使用这种编写方式,因为这里没有考虑到我在编写布尔操作时通常会优先考虑的优先级。

Predicate<Integer> p1 = i -> i > 20;
Predicate<Integer> p2 = i -> i < 30;
Predicate<Integer> p3 = i -> i == 0;
Predicate<Integer> p = p1.and(p2).or(p3); // (p1 AND p2) OR p3
Predicate<Integer> p = p3.or(p1).and(p2); // (p3 OR p1) AND p2

警告:在这种情况下,调用方法时不处理优先级。 我在谓词接口isEqual中也有一个静态方法。

Predicate<String> p = Predicate.isEqual("two");

现在,此isEqual方法有什么作用?它通过比较作为参数传递的对象来创建新的谓词。

Predicate<String> p = Predicate.isEqual("two") ;
Stream<String> stream1 = Stream.of("one", "two", "three") ;
Stream<String> stream2 = stream1.filter(p) ;

让我们注意到流接口的of()方法是静态方法,因此它是在此处使用的在Java 8中利用文件声明接口代码模式的新方法。我可以在接口中编写静态方法也是在Java中创建流的另一种方法。 每次通过filter方法写入的流是stream的新实例,因此stream1和stream2对象是不同的对象。 消费和过滤流的示例

import java.util.function.Predicate;
import java.util.stream.Stream;
/**
 * @author Suresh
 */
public class FirstPredicates {

    public static void main(String[] args) {

        Stream<String> stream = Stream.of("one", "two", "three", "four", "five");

        Predicate<String> p1 = s -> s.length() > 3;

        Predicate<String> p2 = Predicate.isEqual("two");
        Predicate<String> p3 = Predicate.isEqual("three");

        stream
                .filter(p2.or(p3))
                .forEach(s -> System.out.println(s));
    }
}

Lazy Operations on a Stream

Predicate<String> p = Predicate.isEqual("two") ;
Stream<String> stream1 = Stream.of("one", "two", "three") ;
Stream<String> stream2 = stream1.filter(p) ;

在这个新的流Stream2中我有什么? 没事 流中没有任何对象。这是流的定义。流不能保存任何数据。 该代码不执行任何操作,即在给定的流上声明操作,但在此调用中不处理任何数据。 对filter方法的调用称为延迟调用。这意味着实际上,当我调用该方法时,只考虑了一个声明,而没有处理任何数据。 返回另一个Stream的Stream的所有方法都是惰性的。 换句话说,将返回Stream的Stream上的操作称为中间操作。

List<String> result = new ArrayList<>();
List<Person> persons = ... ;
persons.stream()
    .peek(System.out::println)
    .filter(person -> person.getAge() > 20)
    .peek(result::add);

如果我们回到消费者那里,考虑另一种称为“偷看”的消费方法。 窥视方法看起来像forEach方法。唯一的区别是peek方法返回该流,而forEach方法则不返回任何内容。由于peek方法返回一个流,因此我们可以放心地假定它是一个中间操作。 然后,我调用了filter方法。同样,它只是一个声明,而最终调用是另一个peek方法,它也是一个声明。 因此,答案是,此代码不执行任何操作。首先,它不打印任何内容。系统。out :: println永远不会被调用,并且结果列表也将保持为空,因为在该代码中永远不会调用result :: add。

示例:Intermediary and Terminal Operations

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
 *
 * @author Suresh
 */
public class IntermediaryAndFinal {

    public static void main(String[] args) {

        Stream<String> stream = Stream.of("one", "two", "three", "four", "five");

        Predicate<String> p1 = Predicate.isEqual("two");

        Predicate<String> p2 = Predicate.isEqual("three");

        List<String> list = new ArrayList<>();

        stream
                .peek(System.out::println)
                .filter(p1.or(p2))
                //.peek(list::add) // Intermediary Operation
                .forEach(list::add); // Terminal/Final Operation

        System.out.println("Done!");
        System.out.println("size = " + list.size());

    }
}

总结中介和终端运营 此流API定义了中介操作。 因为执行了forEach操作,所以它一定不能是中间操作。 然后是窥视操作,这实际上是中介操作和筛选操作。 在这些操作上,我们可以看到forEach并不懒惰。 我们看到偷看和筛选操作很懒。 地图操作

List<Person> list = ... ;
Stream<Person> stream = list.stream();
Stream<String> names =
         stream.map(person -> person.getName());

map操作实现了我们在本文开头看到的map / filter / reduce算法的第一步。 map操作返回一个Stream,因此我们可以安全地假定它是一个中间操作。

@FunctionalInterface
public interface Function<T, R> {
      R apply(T t);
}

映射器功能由“功能”接口建模。实际上,它仅执行一种称为apply的方法。此方法将一个对象作为参数,然后返回另一个对象。 我们还提供了一组默认方法来链接和组成映射。

@FunctionalInterface
public interface Function<T, R> {
         R apply(T t);
         default <V> Function<V, R> compose(
                 Function<? super V, ? extends T> before);
         default <V> Function<T, V> andThen(
                 Function<? super R, ? extends V> after);
}

即,我们有两个默认方法,compose和andThen。 这些是完整的签名。当心泛型!例如,如果您希望通过person类的扩展来允许调用,则在设计此类方法时必须格外小心。 而且,我还有一个称为身份的静态实用程序方法。身份做什么?好吧,这很明显。它接受一个对象并返回相同的对象。

@FunctionalInterface
public interface Function<T, R> {
       R apply(T t);
       // default methods
       static <T> Function<T, T> identity() {
             return t -> t;
       }
}

平面图操作 FlatMapping操作要理解有些棘手。 让我们看一下该方法的签名。

<R> Stream<R> flatMap(Function<T, Stream<R>> flatMapper);
<R> Stream<R> map(Function<T, R> mapper);

flatMap使用函数作为参数,与map方法的函数类型相同。 现在,如果我仔细检查,我会看到一个映射接受一个对象并返回另一个对象,而flatMap接受一个对象并作为对象的返回类型返回一个对象流。 因此,flatMapper接受类型为T的元素,返回某种类型为Stream的元素。 如果flatMap是常规地图,它将返回由提供的函数返回的那些流的Stream,即流。 但是,由于它是flatMap,所以它返回的是经过扁平化的流,并成为单个流。 这是什么意思?这意味着包含流中的所有对象都将在保持流中被占用。

Map and Flatmap Examples

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
/**
 *
 * @author Suresh
 */
public class FlatMapExample {
public static void main(String... args) {

        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        List<Integer> list2 = Arrays.asList(2, 4, 6);
        List<Integer> list3 = Arrays.asList(3, 5, 7);

        List<List<Integer>> list = Arrays.asList(list1, list2, list3);


        System.out.println(list);

        Function<List<?>, Integer> size = List::size;
        Function<List<Integer>, Stream<Integer>> flatmapper = 
                l -> l.stream();
    // list.stream()
    //          .map(size)

    //          .forEach(System.out::println);

        list.stream()
                .flatMap(flatmapper)
                .forEach(System.out::println);
    }
}

在流上包装地图和过滤器 我们看到了3种操作类别

以消费者为参数的forEach和Peek 采用谓词的filter方法, flatMap方法上的地图,该地图将映射器作为参数。 映射器是功能接口功能的一个实例。

Reduction, Functions, and Bifunctions 我们的map / filter / reduce算法的最后一步是Reduction步骤。 Stream API包含两种还原。 第一种是基本和经典的SQL操作,例如最小值,最大值,总和,平均值等。

List<Integer> ages = ... ;
Stream<Integer> stream = ages.stream();
Integer sum =
        stream.reduce(0, (age1, age2) -> age1 + age2);

该减少量需要两个整数,分别为age1和age2,并返回它们的总和。 第一个参数,该0应该是归约操作的标识元素。 第二个参数是类型BinaryOperator <T>的归约运算。在此,T是整数类型。实际上,BinaryOperator是BiFunction的特例。

@FunctionalInterface
public interface BiFunction<T, U, R> {
     R apply(T t, U u);
     // plus default methods
}

BiFunction看起来像一个函数。它在此处接受T和U类型的两个对象,并返回R类型的对象。

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
    // T apply(T t1, T t2);
    // plus static methods
}

BinaryOperator只是BiFunction的扩展,其中所有这三种类型实际上都是相同的。因此,BiFunction将两个对象作为相同类型的参数,并返回相同类型的对象。

减少空集:Identity Element 这个双功能有两个参数,所以我们可能要问两个问题。 如果流为空会怎样?并且,如果Stream仅具有一个元素会发生什么? 如果Stream为空,则减少空Stream是提供的标识元素 如果Stream仅具有一个元素,则此流的减少量就是该元素。 实际上,该元素与已提供的标识元素结合在一起。

Stream<Integer> stream = ...;
BinaryOperation<Integer> sum = (i1, i2) -> i1 + i2;
Integer id = 0; // identity element for the sum
int red = stream.reduce(id, sum);

总和的标识元素为0,那么我可以通过提供此标识元素0和以该lambda表达式为模型的求和运算来减少流。

Stream<Integer> stream = Stream.empty();
int red = stream.reduce(id, sum);
System.out.println(red);

让我们空荡荡的流。可以通过在流接口上将静态方法调用为空来构建空流,然后我们可以运行该方法,并且该流的缩减确实为0。

Stream<Integer> stream = Stream.of(1);
int red = stream.reduce(id, sum);
System.out.println(red);

让我们看另一个示例,其中通过从流接口再次调用off方法和静态方法来提供仅具有一个元素的流,在这里,我们可以看到该流的缩减确实为1。

Stream<Integer> stream = Stream.of(1, 2, 3, 4);
int red = stream.reduce(id, sum);
System.out.println(red);

并且,作为最后一个示例,让我们采用其中包含1、2、3、4的几个整数的常规流。让我们减少该流。它确实打印10,这当然是正确的答案。 选装件

BinaryOperation<Integer> max = (i1, i2) ->
                                    i1 > i2 ? i1 : i2;

问题在于,max操作没有还原方法的标识元素。 因此,不能以这种方式定义空Stream的最大值。

List<Integer> ages = ... ;
Stream<Integer> stream = ages.stream();
... max = stream.max(Comparator.naturalOrder());

那么,此max方法的返回类型是什么? 如果返回类型为int,即Java语言的原始类型,则默认值为0,并且显然0不是max方法的identity元素。 如果返回类型为Integer,则默认值为null,在这种情况下,我当然不希望返回null值,因为我必须在代码中检查过一次,如果返回值为null以避免null点异常。

List<Integer> ages = ... ;
Stream<Integer> stream = ages.stream();
Optional<Integer> max = stream.max(Comparator.naturalOrder());

实际上,此调用的返回类型称为可选,Integer的可选。 什么是可选的?这是Java 8中的新概念。它是一个类,我们可以将其视为包装类型。 好吧,整数的可选值看起来像是包装类型,唯一的区别是,在包装类型中,我总是有一个值,而在可选类型中,我可能没有值。 返回一个可选的方法可能没有结果,这正是我在这里要表达的意思,因为如果我获取一个空流的最大值,那么我将不知道最大值。

可选模式

Optional<String> opt = ... ;
if (opt.isPresent()) {
     String s = opt.get() ;
} else {
     ...
}

该isPresent如果不是这种情况,方法将返回true,如果我有我的选配,假内的值。而且,如果我有一个值,可以通过在此可选对象上调用get方法来获取它。

String s = opt.orElse("") ; // defines a default value

该否则容易方法封装两个通话。实际上,如果存在对象,则仅调用isPresent并将为我调用get方法。但是,如果我愿意,我也可以决定引发异常。

String s = opt.orElseThrow(MyException::new) ; // lazy construct.
  • 此orElseThrow方法将生成一个新的异常。我只是提供一个lambda表达式,它将以一种惰性的方式为我创建该异常。例外。我们仅在需要时进行构建,并且此方法将返回字符串(如果存在),并且将抛出此异常(如果不存在)。
  • Wrapping up Reduction Operations 可用减少量— max()。min()和count()会返回流中元素的数量。 布尔值归约— allMatch(),noneMatch(),anyMatch()。这三个方法都将谓词作为参数,并且如果谓词对于该流的所有元素都返回true,则allMatch方法将返回true。 减少返回可选值,而不是最大值和均值。findFirst()和findAny()就是这样的方法。 减少称为终端操作。 它们触发数据处理。

示例:Reductions, Optionals

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
 *
 * @author Suresh
 */
public class ReductionExample {

    public static void main(String... args) {

        List<Integer> list = Arrays.asList();

        Optional<Integer> red =

                list.stream()

                        .reduce(Integer::max);

        System.out.println("red = " + red);

    }

}

总结操作和可选

  • 减少只是经典的SQL操作。
  • 中介操作与终端操作之间的区别,即中介仅添加流的操作声明,而终端操作触发对流的计算。
  • 之所以需要这个可选概念,是因为并非总是可以在简化步骤中定义默认值。

收集器,以字符串形式收集在列表中

  • 现在让我们来看第二种减少。如果您查看Java文档,则会看到这种减少称为可变减少。
  • 为什么?因为我们将减少可变容器中的流,因为我们将在该容器中添加流的所有元素。
List<Person> persons = ... ;
String result = persons.stream()
     .filter(person -> person.getAge() > 20)
     .map(Person::getLastName)
     .collect(Collectors.joining(", "));

这种收集方法需要收集器。以字符串作为参数加入。 因此,作为字符串的结果,我们在人员列表中看到了早于20岁的人员名称,并用逗号分隔。

List<Person> persons = ... ;
List<String> result =.  persons.stream()
       .filter(person -> person.getAge() > 20)
       .map(Person::getLastName)
       .collect(Collectors.toList());

我们还可以收集列表。基本上,我们可以对流执行相同的处理,但是这次,我们将它们收集在一个列表中,而不是将所有名称收集在一个字符串中。

Collecting in a Map

List<Person> persons = ... ;
Map<Integer, List<Person>> result = persons.stream()
    .filter(person -> person.getAge() > 20)
    .collect(Collectors.groupingBy(Person::getAge));

我们采用了基于人员的信息流。我们筛选出所有20岁以下的人,然后将其收集在地图中。现在,这张地图是用什么制成的?好吧,我们只是传递Person :: getAge。再次,这是一个方法参考。

List<Person> persons = ... ;
Map<Integer, Long> result = persons.stream()
      .filter(person -> person.getAge() > 20)
      .collect(Collectors.groupingBy(Person::getAge,
             Collectors.counting() // the downstream collector
             ));

可以对这些值进行后处理。

示例:Processing Streams

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.paumard.stream.model.Person;
/**
 *
 * @author Suresh
 */

public class CollectorsExample {

    public static void main(String... args)  {
        List<Person> persons = new ArrayList<>();

        try (
                BufferedReader reader =
                        new BufferedReader(
                                new InputStreamReader(
                                        CollectorsExample.class.getResourceAsStream("people.txt")));

                Stream<String> stream = reader.lines();
        ) {

            stream.map(line -> {
                String[] s = line.split(" ");
                Person p = new Person(s[0].trim(), Integer.parseInt(s[1]));
                persons.add(p);
                return p;
            })
                    .forEach(System.out::println);

        } catch (IOException ioe) {
            System.out.println(ioe);
        }

        Optional<Person> opt =
                persons.stream().filter(p -> p.getAge() >= 20)
                        .min(Comparator.comparing(Person::getAge));

        System.out.println(opt);

        Optional<Person> opt2 =
                persons.stream().max(Comparator.comparing(Person::getAge));
        System.out.println(opt2);

        Map<Integer, String> map =
                persons.stream()
                        .collect(
                                Collectors.groupingBy(
                                        Person::getAge,
                                        Collectors.mapping(
                                                Person::getName,
                                                Collectors.joining(", ")
                                        )
                                )
                        );
        System.out.println(map);
    }
}

概括

  • 我们对map / filter / reduce算法进行了快速解释,并且再次说明,该算法不是Java平台的典型算法。这是一种通用算法。
  • 然后,我们定义什么是Stream。我们看到了建立流的几种模式。
  • 我们看到了中间操作和最终操作之间的区别-第一个是惰性的,第二个触发了数据的处理。
  • 我们看到了一些消耗操作-forEach操作(是最终操作)和peek操作(是中间操作)。
  • 我们看到了两个映射操作-首先是map(),然后是flatMap()。
  • 我们看到了使用filter方法的过滤操作。
  • 我们看到了还原步骤,以及还原操作。我们有两种简化操作-第一种是SQL聚合,即max,min,sum,count,count等,第二种是可变的简化。
  • 可变的减少功能是一个非常强大的工具,它具有collect方法,Collector接口和Collectors类。它使我们可以非常快速地构建复杂的结构,以减少流中的元素。


原文链接:http://codingdict.com