函数式接口
https://www.cnblogs.com/heimianshusheng/p/5672641.html
http://lucida.me/blog/java-8-lambdas-insideout-language-features
https://www.cnblogs.com/jianwei-dai/p/5819479.html
函数式接口,简单的说就是指仅含有一个抽象方法的接口,以 @Functionalnterface
标注,注意,这里的抽象方法指的是该接口自己特有的抽象方法,而不包含它从其上级继承过来的抽象方法。
如 java.lang.Comparable
,之前被称为 SAM 类型,即单抽象方法(Single Abstract Method)。我们并不需要声明一个接口是函数式接口,编译器会根据接口的结构自行判断,但是也可以用@FunctionalInterface
来显式指定一个接口是函数式接口,加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求
Java8 的接口还可以定义一个 default 的方法,default 方法使用
default
关键字修饰,它是对象方法,需要使用对象来进行访问。
Java8 中常用的全新的接口
Java7 中已经存在的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.beans.PropertyChangeListener
除此之外,Java8 中增加了一个新的包:java.util.function
,它里面包含了常用的函数式接口,例如:
- Predicate
:接收 T 并返回 boolean - Consumer
:接收 T,不返回值 - Supplier
:提供 T 对象(例如工厂),不接收值 - Function<T, R>:接收 T,返回 R
- UnaryOperator
:接收 T 对象,返回 T - BinaryOperator
:接收两个 T,返回 T
Java 中的 lambda 无法单独出现,它需要一个函数式接口来盛放,lambda 表达式方法体其实就是函数接口的实现。
Predicate 接口
Predicate 接口只有一个参数,返回 boolean 类型。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):
代码如下:
1 | Predicate<String> predicate = (s) -> s.length() > 0; |
Consumer 接口
在 java.lang.Iterable
接口中有一个默认的方法实现:
1 | default void forEach(Consumer<? super T> action) { |
1 | List<User> users = userRepository.findAll(); |
Supplier 接口
Supplier 接口返回一个任意范型的值,和 Function 接口不同的是该接口没有任何参数
代码如下:
1 | Supplier<Person> personSupplier = Person::new; |
Comparator 接口
Comparator 是老 Java 中的经典接口, Java8 在此之上添加了多种默认方法:
代码如下:
1 | Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); |
Lambda 表达式
1 | 基本语法: |
() -> { System.out.println(“Hello Lambda!”); }
1 | 如果只有一个参数且可以被 Java 推断出类型,那么参数列表的括号也可以省略: |
Target Typing(目标类型)
需要注意的是,函数式接口的名称并不是 lambda 表达式的一部分。那么问题来了,对于给定的 lambda 表达式,它的类型是什么?答案是:它的类型是由其上下文推导而来。例如,下面代码中的 lambda 表达式类型是ActionListener:
1 | ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers()); |
这就意味着同样的 lambda 表达式在不同上下文里可以拥有不同的类型:
1 | Callable<String> c = () -> "done"; |
第一个 lambda 表达式 () -> “done” 是Callable的实例,而第二个 lambda 表达式则是 PrivilegedAction 的实例。
编译器负责推导 lambda 表达式的类型。它利用 lambda 表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。lambda 表达式只能出现在目标类型为函数式接口的上下文中。
当然,lambda 表达式对目标类型也是有要求的。编译器会检查 lambda 表达式的类型和目标类型的方法签名(method signature)是否一致。当且仅当下面所有条件均满足时,lambda 表达式才可以被赋给目标类型 T:
- T 是一个函数式接口
- lambda 表达式的参数和 T 的方法参数在数量和类型上一一对应
- lambda 表达式的返回值和 T 的方法返回值相兼容(Compatible)
- lambda 表达式内所抛出的异常和 T 的方法 throws 类型相兼容
由于目标类型(函数式接口)已经 “知道” lambda 表达式的形式参数(Formal parameter)类型,所以我们没有必要把已知类型再重复一遍。也就是说,lambda 表达式的参数类型可以从目标类型中得出:
1 | Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2); |
在上面的例子里,编译器可以推导出 s1 和 s2 的类型是 String。此外,当 lambda 的参数只有一个而且它的类型可以被推导得知时,该参数列表外面的括号可以被省略:
1 | FileFilter java = f -> f.getName().endsWith(".java"); |
Contexts for Target Typing(目标类型的上下文)
之前我们提到 lambda 表达式智能出现在拥有目标类型的上下文中。下面给出了这些带有目标类型的上下文:
- 变量声明
- 赋值
- 返回语句
- 数组初始化器
- 方法和构造方法的参数
- lambda 表达式函数体
- 条件表达式(? :)
- 转型(Cast)表达式
在前三个上下文(变量声明、赋值和返回语句)里,目标类型即是被赋值或被返回的类型:
1 | Comparator<String> c; |
数组初始化器和赋值类似,只是这里的 “变量” 变成了数组元素,而类型是从数组类型中推导得知:
1 | filterFiles(new FileFilter[] { |
方法参数的类型推导要相对复杂些:目标类型的确认会涉及到其它两个语言特性:重载解析(Overload resolution)和参数类型推导(Type argument inference)。
1 | Thread oldSchool = new Thread(new Runnable () { |
第二个线程里的λ表达式,你并不需要显式地把它转成一个 Runnable,因为 Java 能根据上下文自动推断出来:一个 Thread 的构造函数接受一个 Runnable 参数,而传入的λ表达式正好符合其 run() 函数,所以 Java 编译器推断它为 Runnable。
重载解析会为一个给定的方法调用(method invocation)寻找最合适的方法声明(method declaration)。由于不同的声明具有不同的签名,当 lambda 表达式作为方法参数时,重载解析就会影响到 lambda 表达式的目标类型。编译器会通过它所得之的信息来做出决定。如果 lambda 表达式具有显式类型(参数类型被显式指定),编译器就可以直接 使用 lambda 表达式的返回类型;如果 lambda 表达式具有隐式类型(参数类型被推导而知),重载解析则会忽略 lambda 表达式函数体而只依赖 lambda 表达式参数的数量。
如果在解析方法声明时存在二义性(ambiguous),我们就需要利用转型(cast)或显式 lambda 表达式来提供更多的类型信息。如果 lambda 表达式的返回类型依赖于其参数的类型,那么 lambda 表达式函数体有可能可以给编译器提供额外的信息,以便其推导参数类型。
1 | List<Person> ps = ... |
在上面的代码中,ps 的类型是 List
- 使用显式 lambda 表达式(为参数 p 提供显式类型)以提供额外的类型信息
- 把 lambda 表达式转型为 Function<Person, String>
- 为泛型参数 R 提供一个实际类型。(.
map(p -> p.getName()))
lambda 表达式本身也可以为它自己的函数体提供目标类型,也就是说 lambda 表达式可以通过外部目标类型推导出其内部的返回类型,这意味着我们可以方便的编写一个返回函数的函数:
1 | Supplier<Runnable> c = () -> () -> { System.out.println("hi"); }; |
类似的,条件表达式可以把目标类型 “分发” 给其子表达式:
1 | Callable<Integer> c = flag ? (() -> 23) : (() -> 42); |
最后,转型表达式(Cast expression)可以显式提供 lambda 表达式的类型,这个特性在无法确认目标类型时非常有用:
1 | // Object o = () -> { System.out.println("hi"); }; 这段代码是非法的 |
除此之外,当重载的方法都拥有函数式接口时,转型可以帮助解决重载解析时出现的二义性。
Method Reference(方法引用)
其实是 lambda 表达式的一种简化写法。所引用的方法其实是 lambda 表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是
::
,右边是相应的方法名。
方法引用语法如下:
1 | 静态方法引用:ClassName :: methodName |
把 lambda 表达式的参数当成 ClassName 构造器的参数 。例如 BigDecimal :: new
等同于 x -> new BigDecimal(x)
。
Stream
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
http://www.importnew.com/20331.html
http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Java8 里面对 Stream 的定义:
A sequence of elements supporting sequential and parallel aggregate operations.
我们来解读一下上面的那句话:
- Stream 是元素的集合,这点让 Stream 看起来用些类似 Iterator
- 可以支持顺序和并行的对原 Stream 进行汇聚的操作
可以把 Stream 当成一个高级版本的 Iterator。
原始版本的 Iterator,用户只能一个一个的遍历元素并对其执行某些操作;
高级版本的 Iterator(Stream),用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母” 等,具体这些操作如何应用到每个元素上,就交给 Stream 了。
创建 Stream
- 通过 Stream 接口的静态工厂方法(Java8 中的一个新特性: 接口可以带静态方法)
- 通过 Collection 接口的默认方法(Java8 中的一个新特性: 就是接口中的一个带有实现的方法)
使用 Stream 静态方法来创建 Stream
- of 方法:有两个 overload 方法,一个接受变长参数,一个接口单一值
1
2
3
4
5Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
```
2. generator 方法:生成一个无限长度的 Stream,其元素的生成是通过给定的 Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)
Stream.generate(new Supplier
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
1 | 三条语句的作用都是一样的,只是使用了 lambda 表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的 Stream,其中值是随机的。这个无限长度 Stream 是懒加载,一般这种无限长度的 Stream 都会配合 Stream 的 limit() 方法来用。 |
这段代码就是先获取一个无限长度的正整数集合的 Stream,然后取出前 10 个打印。千万记住使用 limit 方法,不然会无限打印下去。
通过 Collection 子类获取 Stream
Collection 接口有一个 stream 方法,所以其所有子类都都可以获取对应的 Stream 对象。
1 | public interface Collection<E> extends Iterable<E> { |
Filter 过滤
过滤通过一个 Predicate 接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他 Stream 操作(比如 forEach)。
forEach 需要一个函数来对过滤后的元素依次执行。forEach 是一个最终操作,所以我们不能在 forEach 之后来执行其他 Stream 操作。
代码如下:
1 | stringCollection |
Sort 排序
排序是一个中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。
代码如下:
1 | stringCollection |
需要注意的是,排序只创建了一个排列好后的 Stream,而不会影响原有的数据源,排序之后原数据 stringCollection 是不会被修改的:
代码如下:
1 | System.out.println(stringCollection); |
其实这也是函数式编程的一个好处:不会改变对象状态,每次都会创建一个新对象。
Map 映射
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象,你也可以通过 map 来将对象转换成其他类型,
map 返回的 Stream 类型是根据 map 传递进去的函数的返回值决定的。下面的示例展示了将字符串转换为大写字符串:
代码如下:
1 | stringCollection |
Collectors
Match 匹配
Stream 提供了多种匹配操作,允许检测指定的 Predicate 是否匹配整个 Stream。所有的匹配操作都是最终操作,并返回一个 boolean 类型的值。
代码如下:
1 | boolean anyStartsWithA = |
Count 计数
计数是一个最终操作,返回 Stream 中元素的个数,返回值类型是 long。
代码如下:
1 | long startsWithB = |
findFirst 返回集合的第一个对象
findAny 返回流计算中第一个计算完成的集合中的任何一个对象
在串行流中,findAny 和 findFirst 返回的都是第一个对象;而在并行流中,findAny 返回的是最快处理完的那个线程的数据。所以说,在并行操作中,对数据没有顺序上的要求,那么 findAny 的效率会比 findFirst 要快。
如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理