什么是@FunctionalInterface注解?
lambda表达式的使用和@FunctionalInterface注解关系密切,它用来修饰仅有一个抽象方法的接口,比如Runnable接口的源码就带有@FunctionalInterface注解 —— 它告诉编译器:“这个接口只能有一个抽象方法!”。所以,如果你在加了@FunctionalInterface注解的接口里,试图添加第二个抽象方法,编译器将提示报错!
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
怎么理解functional Interface?它直译成中文是“功能性接口”。什么是功能?功能就是一个动作的实现,比如打印的功能、结算的功能,我们都知道接口是功能的抽象,如果某个接口仅仅是有且仅有一种功能的抽象,那它就是一个functional Interface。
如何理解lambda?
那么它和lambda表达式有什么关系?—— 用大白话来解释,lambda表达式用来描述功能性接口的某种实现,你可以理解它是一种具体动作、具体实现。比如我们有一个Printable接口:
@FunctionalInterface
public interface Printable {
void print();
}
接口只是抽象,对不对?就好像打印这个功能,我们可以竖着打印、横着打印、打印成小册子,这些都是接口具体的实现——它们就可以用lambda进行描述:
public class MyLambdaTest {
public static void main(String[] args) {
Printable myLambda1 = () -> { System.out.println("竖着打印!") };
Printable myLambda2 = () -> { System.out.println("横着打印!") };
Printable myLambda3 = () -> { System.out.println("斜着打印!") };
}
}
如果你对这些表达式感到不能理解,可以试着这么想:“接口就是某个或某些动作的抽象,任何实现它的动作都可以被看成它本身”,就好像不管是横、竖着打印,都是打印一样,所以myLambda1-3的类型都可以为Printable,是不是没毛病?
当然,你在代码不会看到有人单纯地写一串lambda表达式(如上例),然后孤零零地放在那里。为什么描述一种实现?因为我们希望把动作本身当做参数传进某个方法里——也就是把lambda当作一个参数传到方法里。比如我们有两台打印机,它们都能打印,A可以横着打印,B可以竖着打印,用代码实现如下:
public class PrinterA implements Printable {
public void print() {
System.out.println("横着打印!");
};
}
// -----------------
public class PrinterB implements Printable {
public void print() {
System.out.println("竖着打印!");
};
}
我们在日常生活中拥有一台打印机目的是什么?为了打印(printSomething方法)。为了可以打印,我们会买(new)一个打印机帮我们打印,如果想横打印就new一个PrinterA,想竖着打印就new一个PrinterB:
public class MyLambdaTest {
public static void main(String[] args) {
Printable myPrinter = new PrinterA();
printSomething(myPrinter);
}
public static void printSomething(Printable printer) {
printer.print();
}
}
当然,上文描述的是现实生活里的操作——要打印必须要买打印机。但在代码里,有必要这么复杂吗?我们的目的是为了完成打印的动作(执行printSomething方法),所以,只需要把如何打印直接作为参数传进去就可以了,不用非得new一个打印机。我们刚刚说了,lambda就是为了描述动作而存在的,所以printSomething的参数直接传入lambda即可!所以上面的代码就可以直接简化为:
public class MyLambdaTest {
public static void main(String[] args) {
printSomething(
() -> System.out.println("横着打印!"); // lambda在此
);
}
public static void printSomething(Printable printer) {
printer.print();
}
}
为什么lambda可以如此简写?
为什么lambda写法可以如此简单?因为关于方法的实现规则,在功能性接口里已经写得明明白白。lambda只适用于functional interface,后者有且只有一个方法,当你使用lambda时,也只有可能指“那”一个方法,它的方法签名里(比如void print();
)已经告诉了你它的方法名、参数信息、返回值类型,所以lambda的参数(参数类型都不用写!)和方法体之间只需要一个右箭头进行连接即可,当方法体里只有一行代码,方法体的花括号甚至可以省略。
案例1:
@FunctionalInterface
public interface Printable {
void print(); // 无参、无返回值
}
// --------------
public class MyLambdaTest {
public static void main(String[] args) {
Printable myLambda1 = () -> { System.out.println("竖着打印!") };
Printable myLambda2 = () -> System.out.println("竖着打印!"); // 省略方法体花括号
}
}
案例2:
@FunctionalInterface
public interface Printable {
String print(); // 无参、返回值类型为String
}
// --------------
public class MyLambdaTest {
public static void main(String[] args) {
Printable myLambda1 = () -> { return "竖着打印!" };
Printable myLambda2 = () -> "竖着打印!"; // 只有一行代码,省略方法体花括号甚至省略return关键字
}
}
案例3:
@FunctionalInterface
public interface Printable {
int print(String prefix, int num); // 有参、返回值类型为int
}
// --------------
public class MyLambdaTest {
public static void main(String[] args) {
String p = "打印机A";
int n = 5;
Printable myLambda1 = (p, n) -> { // 参数类型都不用写
System.out.println(p + "竖着打印!") ;
return n;
};
}
}
所以,一个复杂的lambda表达式应用,可能是如下这样的:1.它是某一个方法的参数,比如printSomething需要传入一个动作作为参数,比如new一个线程需要传入一个Runnable动作作为参数;2.它的书写非常简单,因为接口已经把它的相关信息定义得明明白白,不用废话太多,编译器compiler知道你在说什么!
public class MyLambdaTest {
public static void main(String[] args) {
printSomething(
(s, n) -> {
System.out.println(prefix + "竖着打印!") ;
return n;
}
);
}
}