java.util.Function包。定义了四个最基础的函数接口:php
其中, compose, andThen, and, or, negate 用来组合函数接口而获得更强大的函数接口。java
其它的函数接口都是经过这四个扩展而来。程序员
这些函数接口能够接收哪些值呢?算法
每个流式计算的末尾总有一个相似 collect(Collectors.toList()) 的方法调用。collect 是 Stream 的方法,而参数则是聚合器Collector。已有的聚合器定义在Collectors 的静态方法里。express
那么这个聚合器是怎么实现的呢?大部分聚合器都是基于 Reduce 操做实现的。 Reduce ,名曰推导,含有三个要素: 初始值 init, 二元操做符 BinaryOperator, 以及一个用于聚合结果的数据源S。数组
Reduce 的算法以下:多线程
一个聚合器的实现,一般须要提供四要素:并发
Collectors.CollectorImpl 的实现展现了这一点:app
static class CollectorImpl<T, A, R> implements Collector<T, A, R> { private final Supplier<A> supplier; private final BiConsumer<A, T> accumulator; private final BinaryOperator<A> combiner; private final Function<A, R> finisher; private final Set<Characteristics> characteristics; CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A,R> finisher, Set<Characteristics> characteristics) { this.supplier = supplier; this.accumulator = accumulator; this.combiner = combiner; this.finisher = finisher; this.characteristics = characteristics; } }
自定义聚合器dom
public class TestCollector implements Collector<Integer, List<Integer>, List<Integer>> { public Supplier<List<Integer>> supplier() { return () -> { List<Integer> res = combiner().apply(Lists.newArrayList(), null); res.add(4); System.out.println("supplier\t" + res); return res; }; } @Override public BiConsumer<List<Integer>, Integer> accumulator() { return (res, num) -> { System.out.println("accumulator\tres\t" + res + "num\t" + num); if (num > res.get(0)) { res.add(num); } }; } @Override public BinaryOperator<List<Integer>> combiner() { System.out.println("combiner"); return (left, right) -> { System.out.println("combiner\tleft\t" + left + "right\t" + right); if (left != null) { return left; } return right; }; } @Override public Function<List<Integer>, List<Integer>> finisher() { return res -> { System.out.println("finisher\t" + res); res.remove(0); return res; }; } @Override public Set<Characteristics> characteristics() { System.out.println("characteristics"); return Collections.emptySet(); } public static void main(String[] args) { List<Integer> test = Stream.of(1, 4, 3, 4, 5, 6, 7, 3, 9, 4).collect(new TestCollector()); System.out.println("result:" + test); } } //寻找比4大的值 combiner characteristics combiner combiner left []right null supplier [4] accumulator res [4]num 1 accumulator res [4]num 4 accumulator res [4]num 3 accumulator res [4]num 4 accumulator res [4]num 5 accumulator res [4, 5]num 6 accumulator res [4, 5, 6]num 7 accumulator res [4, 5, 6, 7]num 3 accumulator res [4, 5, 6, 7]num 9 accumulator res [4, 5, 6, 7, 9]num 4 characteristics finisher [4, 5, 6, 7, 9] result:[5, 6, 7, 9]
Stream 主要有四类接口:
除了 Stream 自己自带的生成Stream 的方法,数组和容器及StreamSupport都有转换为流的方法。好比 Arrays.stream , [List|Set|Collection].[stream|parallelStream] , StreamSupport.[int|long|double|]stream;
流的类型主要有:Reference(对象流), IntStream (int元素流), LongStream (long元素流), Double (double元素流) ,定义在类 StreamShape 中,主要将操做适配于类型系统。
λ表达式本质上是一个匿名方法。
λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或语句块。
基本语法: (parameters) -> expression或(parameters) ->{ statements; }
lambda表达式的语法由参数列表、箭头符号->
和函数体组成。函数体既能够是一个表达式,也能够是一个语句块:
return
语句会把控制权交给匿名方法的调用者break
和continue
只能在循环中使用λ表达式的类型,叫作“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”。
它的定义是:一个接口,若是只有一个显式声明的抽象方法,那么它就是一个函数接口。通常用@FunctionalInterface标注(在编译期能够验证你这个接口是不是函数接口)(也能够不标)。
λ表达式的类型是由其上下文推导而来。
Callable<String> c = () -> "done";
PrivilegedAction<String> a = () -> "done";
第一个lambda表达式() -> "done"
是Callable
的实例,而第二个lambda表达式则是PrivilegedAction
的实例。
编译器负责推导lambda表达式的类型。它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。
lambda表达式对目标类型也是有要求的。编译器会检查lambda表达式的类型和目标类型的方法签名(method signature)是否一致。
当且仅当下面全部条件均知足时,lambda表达式才能够被赋给目标类型T(@FunctionalInterface验证的维度)
:
T
是一个函数式接口T
的方法参数在数量和类型上一一对应T
的方法返回值相兼容(Compatible)T
的方法throws
类型相兼容lambda表达式并非第一个拥有上下文相关类型的Java表达式:泛型方法调用和“菱形”构造器调用也经过目标类型来进行类型推导:
List<String> ls = Collections.emptyList(); List<Integer> li = Collections.emptyList();
Map<String, Integer> m1 = new HashMap<>(); Map<Integer, String> m2 = new HashMap<>();
捕获的概念在于解决在λ表达式中咱们可使用哪些外部变量(即除了它本身的参数和内部定义的本地变量)的问题。
与内部类很是类似,但有不一样点。不一样点在于内部类老是持有一个其外部类对象的引用。λ表达式呢,除非在它内部用到了其外部类对象的方法或者成员,不然它就不持有这个对象的引用。
这个特性对内存管理是一件好事:内部类实例会一直保留一个对其外部类实例的强引用,而那些没有捕获外部类成员的lambda表达式则不会保留对外部类实例的引用。要知道内部类的这个特性每每会形成内存泄露。
在Java8之前,若是要在内部类访问外部对象的一个本地变量,那么这个变量必须声明为final才行。在Java8中,这种限制被去掉了,代之以一个新的概念,有效只读-“effectively final”。它的意思是你能够声明为final,也能够不声明final可是按照final来用,也就是一次赋值永不改变。换句话说,保证它加上final前缀后不会出编译错误。
Java要求本地变量final或者effectively final的缘由是多线程并发问题。内部类、λ表达式都有可能在不一样的线程中执行,容许多个线程同时修改一个本地变量不符合Java的设计理念。lambda表达式对值封闭,对变量开放。
lambda表达式不支持修改捕获变量的另外一个缘由是咱们可使用更好的方式来实现一样的效果:使用规约(reduction)。
java.util.stream包提供了各类通用的和专用的规约操做(例如sum、min和max)。
int sum = list.stream() .mapToInt(e -> e.size()) .sum();
sum()
等价于下面的规约操做: int sum = list.stream() .mapToInt(e -> e.size()) .reduce(0 , (x, y) -> x + y);
规约须要一个初始值(以防输入为空)和一个操做符(在这里是加号),而后用下面的表达式计算结果:0 + list[0] + list[1] + list[2] + ...
任何一个λ表达式均可以表明某个函数接口的惟一方法的匿名描述符。咱们也可使用某个类的某个具体方法来表明这个描述符,叫作方法引用。
方法引用和lambda表达式拥有相同的特性,咱们并不须要为方法引用提供方法体,咱们能够直接经过方法名称引用已有方法。
下面是一组例子,教你使用方法引用代替λ表达式:
//c1 与 c2 是同样的(静态方法引用)
Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
Comparator<Integer> c1 = Integer::compare;
//下面两句是同样的(实例方法引用1)
persons.forEach(e -> System.out.println(e));
persons.forEach(System.out::println);
//下面两句是同样的(实例方法引用2)
persons.forEach(person -> person.eat());
persons.forEach(Person::eat);
//下面两句是同样的(构造器引用)
strList.stream().map(s -> new Integer(s));
strList.stream().map(Integer::new);
还有一些其它的方法引用:
super::toString //引用某个对象的父类方法
String[]::new //引用一个数组的构造器
Java8中,接口声明里能够有方法实现了,叫作默认方法。在此以前,接口里的方法所有是抽象方法。
这实际上混淆了接口和抽象类,但一个类仍然能够实现多个接口,而只能继承一个抽象类。
这么作的缘由是:因为Collection库须要为批处理操做添加新的方法,如forEach(),stream()等,可是不能修改现有的Collection接口——若是那样作的话全部的实现类都要进行修改,包括不少客户自制的实现类。因此只好使用这种妥协的办法。
如此一来,咱们就面临一种相似多继承的问题。若是类Sub继承了两个接口,Base1和Base2,而这两个接口刚好具备彻底相同的两个默认方法,那么就会产生冲突。这时Sub类就必须经过重载来显式指明本身要使用哪个接口的实现(或者提供本身的实现)。
public class Sub implements Base1, Base2 { public void hello() { Base1.super.hello(); //使用Base1的实现 } }
除了默认方法,Java8的接口也能够有静态方法的实现
public interface MyInterf { String m1(); default String m2() { return "Hello default method!"; } static String m3() { return "Hello static method in Interface!"; } }
有时候一个流的数据源不必定是一个已存在的集合对象,也多是个“生成器函数”。一个生成器函数会产生一系列元素,供给一个流。
Stream.generate(Supplier<T> s)就是一个生成器函数。其中参数Supplier是一个函数接口,里面有惟一的抽象方法 <T> get()。
下面这个例子生成并打印5个随机数:Stream.generate(Math::random).limit(5).forEach(System.out::println);
注意这个limit(5),若是没有这个调用,那么这条语句会永远地执行下去。也就是说这个生成器是无穷的。这种调用叫作终结操做,或者短路(short-circuiting)操做。
集合类的批处理操做API的目的是实现集合类的“内部迭代”,并指望充分利用现代多核CPU进行并行计算。
Java8以前集合类的迭代(Iteration)都是外部的,即客户代码。而内部迭代意味着改由Java类库来进行迭代,而不是客户代码。
for(Object o: list) { // 外部迭代
System.out.println(o);
}
能够写成: list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代
集合类(包括List)如今都有一个forEach方法,对元素进行迭代(遍历),因此咱们不须要再写for循环了。forEach方法接受一个函数接口Consumer作参数,因此可使用λ表达式。
Java8为集合类引入了另外一个重要概念:流(stream)。一个流一般以一个集合类实例为其数据源,而后在其上定义各类操做。
流的API设计使用了管道(pipelines)模式。对流的一次操做会返回另外一个流。stream()如同IO的API或者StringBuffer的append方法那样,从而多个不一样的操做能够在一个语句里串起来。
还有一个方法叫parallelStream(),顾名思义它和stream()同样,只不过指明要并行处理,以期充分利用现代CPU的多核特性。
//给出一个String类型的数组,找出其中全部不重复的素数
public void distinctPrimary(String... numbers) {
List<String> l = Arrays.asList(numbers);
List<Integer> r = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.distinct()
.collect(Collectors.toList());
System.out.println("distinctPrimary result is: " + r);
}
第一步:传入一系列String(假设都是合法的数字),转成一个List,而后调用stream()方法生成流。
第二步:调用流的map方法把每一个元素由String转成Integer,获得一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,因此这里用λ表达式。
第三步:调用流的filter方法,过滤那些不是素数的数字,并获得一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,因此这里用λ表达式。
第四步:调用流的distinct方法,去掉重复,并获得一个新流。这本质上是另外一个filter操做。
第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。咱们也能够用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既做为键又做为值。toMap方法接受两个Function类型的参数,分别用以生成键和值,Function是个函数接口,因此这里都用λ表达式。
你可能会以为在这个例子里,List l被迭代了好屡次,map,filter,distinct都分别是一次循环,效率会很差。实际并不是如此。这些返回另外一个Stream的方法都是“懒(lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法以前,lazy的方法不会执行。
当遇到eager方法时,前面的lazy方法才会被依次执行。并且是管道贯通式执行。这意味着每个元素依次经过这些管道。例若有个元素“3”,首先它被map成整数型3;而后经过filter,发现是素数,被保留下来;又经过distinct,若是已经有一个3了,那么就直接丢弃,若是尚未则保留。这样,3个操做其实只通过了一次循环。
String[] atp = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer","Roger Federer", "Andy Murray","Tomas Berdych", "Juan Martin Del Potro"}; List<String> players = Arrays.asList(atp); // 之前的循环方式 for (String player : players) { System.out.print(player + "; "); } // 使用 lambda 表达式以及函数操做(functional operation) players.forEach((player) -> System.out.print(player + "; ")); // 在 Java 8 中使用双冒号操做符(double colon operator) players.forEach(System.out::println);
// 使用匿名内部类 btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); // 或者使用 lambda expression btn.setOnAction(event -> System.out.println("Hello World!"));
Runable栗子
// 1.1使用匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }).start(); // 1.2使用 lambda expression new Thread(() -> System.out.println("Hello world !")).start(); // 2.1使用匿名内部类 Runnable race1 = new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }; // 2.2使用 lambda expression Runnable race2 = () -> System.out.println("Hello world !"); // 直接调用 run 方法(没开新线程哦!) race1.run(); race2.run();
String[] players = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer", "Roger Federer", "Andy Murray", "Tomas Berdych", "Juan Martin Del Potro", "Richard Gasquet", "John Isner"}; // 1.1 使用匿名内部类根据 name 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.compareTo(s2)); } }); // 1.2 使用 lambda expression 排序 players Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2)); Arrays.sort(players, sortByName); // 1.3 也能够采用以下形式: Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2))); // 1.1 使用匿名内部类根据 surname 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" ")))); } }); // 1.2 使用 lambda expression 排序,根据 surname Comparator<String> sortBySurname = (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ); Arrays.sort(players, sortBySurname); // 1.3 或者这样,怀疑原做者是否是想错了,括号好多... Arrays.sort(players, (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) ); // 2.1 使用匿名内部类根据 name lenght 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.length() - s2.length()); } }); // 2.2 使用 lambda expression 排序,根据 name lenght Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length()); Arrays.sort(players, sortByNameLenght); // 2.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length())); // 3.1 使用匿名内部类排序 players, 根据最后一个字母 Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); } }); // 3.2 使用 lambda expression 排序,根据最后一个字母 Comparator<String> sortByLastLetter = (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); Arrays.sort(players, sortByLastLetter); // 3.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
public class Person { private String firstName, lastName, job, gender; private int salary, age; public Person(String firstName, String lastName, String job, String gender, int age, int salary) { this.firstName = firstName; this.lastName = lastName; this.gender = gender; this.age = age; this.job = job; this.salary = salary; } // Getter and Setter // . . . . . } List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500)); add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600)); add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900)); add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700)); add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000)); add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300)); } }; List<Person> phpProgrammers = new ArrayList<Person>() { { add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550)); add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200)); add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600)); add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000)); add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100)); add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300)); add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100)); add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000)); add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600)); add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800)); } }; 如今咱们使用forEach方法来迭代输出上述列表: System.out.println("全部程序员的姓名:"); javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 咱们一样使用forEach方法,增长程序员的工资5%: System.out.println("给程序员加薪 5% :"); Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()); javaProgrammers.forEach(giveRaise); phpProgrammers.forEach(giveRaise); 另外一个有用的方法是过滤器filter() ,让咱们显示月薪超过1400美圆的PHP程序员: System.out.println("下面是月薪超过 $1,400 的PHP程序员:") phpProgrammers.stream() .filter((p) -> (p.getSalary() > 1400)) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 咱们也能够定义过滤器,而后重用它们来执行其余操做: // 定义 filters Predicate<Person> ageFilter = (p) -> (p.getAge() > 25); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender())); System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:"); phpProgrammers.stream() .filter(ageFilter) .filter(salaryFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); // 重用filters System.out.println("年龄大于 24岁的女性 Java programmers:"); javaProgrammers.stream() .filter(ageFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 使用limit方法,能够限制结果集的个数: System.out.println("最前面的3个 Java programmers:"); javaProgrammers.stream() .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); System.out.println("最前面的3个女性 Java programmers:"); javaProgrammers.stream() .filter(genderFilter) .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); 根据名字和薪水排序Java程序员,放到一个list中,而后显示列表 System.out.println("根据 name 排序,并显示前5个 Java programmers:"); List<Person> sortedJavaProgrammers = javaProgrammers .stream() .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName()))) .limit(5) .collect(toList()); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName())); System.out.println("根据 salary 排序 Java programmers:"); sortedJavaProgrammers = javaProgrammers .stream() .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) ) .collect( toList() ); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName())); 若是咱们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法: System.out.println("工资最低的 Java programmer:"); Person pers = javaProgrammers .stream() .min((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get() System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary()) System.out.println("工资最高的 Java programmer:"); Person person = javaProgrammers .stream() .max((p, p2) -> (p.getSalary() - p2.getSalary())) .get() System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary()) 结合 map 方法,咱们可使用 collect 方法来将咱们的结果集放到一个字符串,一个 Set 或一个TreeSet中 System.out.println("将 PHP programmers 的 first name 拼接成字符串:"); String phpDevelopers = phpProgrammers .stream() .map(Person::getFirstName) .collect(joining(" ; ")); // 在进一步的操做中能够做为标记(token) System.out.println("将 Java programmers 的 first name 存放到 Set:"); Set<String> javaDevFirstName = javaProgrammers .stream() .map(Person::getFirstName) .collect(toSet()); System.out.println("将 Java programmers 的 first name 存放到 TreeSet:"); TreeSet<String> javaDevLastName = javaProgrammers .stream() .map(Person::getLastName) .collect(toCollection(TreeSet::new)); Streams 还能够是并行的(parallel) System.out.println("计算付给 Java programmers 的全部money:"); int totalSalary = javaProgrammers .parallelStream() .mapToInt(p -> p.getSalary()) .sum(); 以使用summaryStatistics方法得到stream 中元素的各类汇总数据。 接下来,咱们能够访问这些方法,好比getMax, getMin, getSum或getAverage //计算 count, min, max, sum, and average for numbers List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers .stream() .mapToInt((x) -> x) .summaryStatistics(); System.out.println("List中最大的数字 : " + stats.getMax()); System.out.println("List中最小的数字 : " + stats.getMin()); System.out.println("全部数字的总和 : " + stats.getSum()); System.out.println("全部数字的平均值 : " + stats.getAverage());