点击上方“
朱小厮的博客
”,选择“
设为星标
”
做积极的人,而不是积极废人
来源:
http://uee.me/aWWS2
1. 用法
示例:最普遍的一个例子,执行一个线程
new Thread(() -> System.out.print("hello world")).start();
->
我们发现它指向的是 Runnable接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread.
*
* The general contract of the method run
is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
分析
-
->这个箭头是lambda表达式的关键操作符
-
->把表达式分成两截,前面是函数参数,后面是函数体。
-
Thread的构造函数接收的是一个Runnable接口对象,而我们这里的用法相当于是把一个函数当做接口对象传递进去了,这点理解很关键,这正是函数式编程的含义所在。
-
我们注意到Runnable有个注解 @FunctionalInterface,它是jdk8才引入,它的含义是函数接口。它是lambda表达式的协议注解,这个注解非常重要,后面做源码分析会专门分析它的官方注释,到时候一目了然。
/* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface
{}
2. 由此引发的一些案例
有参数有返回值的实例:集合排序
List<String> list = new ArrayList<>();
Collections.sort(list, (o1, o2) -> {
if(o1.equals(o2)) {
return 1;
}
return -1;
})
我们知道Collections.sort方法的第二个参数接受的是一个 Comparator
的对象,它的部分关键源码是这样的
:
@FunctionalInterface
public interface Comparator {
int compare(T o1, T o2);
}
如上已经去掉注释和部分其他方法。
我们可以看到sort的第二个参数是Comparator的compare方法,参数类型是T,分别是o1和o2,返回值是一个int。
3. 疑问
public class Main {
interface ITest {
int test(String string);
}
static void Print(ITest test) {
test.test("hello world");
}
public static void main(String[] args) {
Print(string -> {
System.out.println(string);
return 0;
});
}
}
如上所示,确实不需要增加 @FunctionInterface注解就可以实现。
我们带着这两个疑问来进入源码解析。
4. 源码解析
必须了解注解 @FunctionInterface
上源码:
package java.lang;
import java.lang.annotation.*;
/**
* An informative annotation type used to indicate that an interface
* type declaration is intended to be a functional interface as
* defined by the Java Language Specification.
*
* Conceptually, a functional interface has exactly one abstract
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* not count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
*
*
Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
*
*
If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
*
*
The type is an interface type and not an annotation type, enum, or class.
*
The annotated type satisfies the requirements of a functional interface.
*
*
*
However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.
*
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
我们说过这个注解用来规范lambda表达式的使用协议的,那么注释中都说了哪些呢?
* An informative annotation type used to indicate that an functional interface
* type declaration is intended to be a functional interface as
* defined by the Java Language Specification.
第一是接口允许有实现的方法。
这种实现的方法是用default关键字来标记的
(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法),例如
:
当然这是jdk8才引入的特性,到此我们才知道,知识是一直在变化的,我们在学校中学到interface接口不允许有实现的方法是错误的,随着时间推移,一切规范都有可能发生变化。
如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则:一个被它注解的接口只能有一个抽象方法
例如同样是Compartor接口中,它重新声明了equals方法:
这些是对如下注释的翻译和解释。
* Conceptually, a functional interface has exactly one abstract
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
*
public methods of {@code java.lang.Object}, that also does
* not count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
-
这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class
-
这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。
*
If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
*
*
The type is an interface type and not an annotation type, enum, or class.
*
The
annotated type satisfies the requirements of a functional interface.
*
*
However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.
通过了解function interface我们能够知道怎么才能正确的创建一个function interface来做lambda表达式了。接下来的是了解java是怎么把一个函数当做一个对象作为参数使用的。
5. 穿越:对象变身函数
让我们重新复盘一下上面最开始的实例:
new Thread(() -> System.out.print("hello world")).start();
我们知道在jdk8以前我们都是这样来执行的:
Runnable r = new Runnable(){
System.out.print("hello world");
};
new Thread(r).start();
我们知道两者是等价的,也就是说 r 等价于 ()->System.out.print("hello world"),一个接口对象等于一个lambda表达式?那么lambda表达式肯定做了这些事情(未看任何资料,纯粹推理,有误再改正):
-
创建接口对象
-
实现接口对象
-
返回接口对象
6. 关于 UnaryOperator
上篇文章(聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator)关于 UnaryOperator草草收尾,在这里给大家重新梳理一下,关于它的使用场景以及它与lambda表达式的关系。
要先理解它的作用,它是接受一个参数并返回与该类型同的值,来看一个List怎么用它的,java.util.List中的replaceAll就用它了:
default void replaceAll(UnaryOperator operator) {
Objects.requireNonNull(operator);
final ListIterator li = this.listIterator();
while (li.hasNext()) {
li.set(
operator.apply(li.next()));
}
}
我们可以看到这个方法的目的是把list中的值经过operator操作后重新返回一个新值,例如具体调用。
List<String> list = new ArrayList<>();
list.add("abc");
list.replaceAll(s -> s + "efg");
System.out.println(list);
其中lambda表达式 s->s+"efg"就是这个operator对象,那么最终list中的值就变成了["abcefg"],由此我们可以知道它的作用就是对输入的值再加工,并返回同类型的值,怎么用就需要你自己扩展发挥了。
在我看来,它跟lambda表达式的关系并不大,只是它是jdk内置的一种标准操作,类似的二元操作符 BinaryOperator它可以接受两个同类型参数,并返回同类型参数的值。
7. 关于UnaryOperator,我们百尺竿头更进一步,深入到核心
先贴出它的源码:
@FunctionalInterface
public interface UnaryOperator extends Function {
/**
* Returns a unary operator that always returns its input argument.
*
* @param the type of the input and output of the operator
* @return a unary operator that always returns its input argument
*/
static UnaryOperator identity() {
return t -> t;
}
}