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();
@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
在这个新的流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的特例。
<T>
@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.
示例: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); } }
总结操作和可选
收集器,以字符串形式收集在列表中
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); } }
概括
原文链接:http://codingdict.com