匿名内部类
public class Demo3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xixi");
System.out.println("haha");
}
}).start();
}
}
如果是在IEDA里面编写代码。会发现new Runbable()是灰色的
提示你可以用lambda替换;
先感受一下lambda表达式
public class Demo3 {
public static void main(String[] args) {
new Thread(()->{
System.out.println("xixi");
}).start();
}
}
非常优雅,非常简洁。
Lambda表达式的本质
lambda表达式,其实是一段可传递的代码,它的本质是以类的身份,干方法的活。
什么意思呢?
public class Demo {
public static void main(String[] args) {
new MyThread(() -> System.out.println("哈哈哈")).start();
}
}
在new Thread()的时候,我们需要传入Runnable接口的子类对象(匿名内部类),但是我们传入了一个lambda表达式,也没有报错,这说明什么?
说明lambda表达式在身份上和匿名内部类等价。
但是lambda表达式作为参数传进入之后,真正起作用的也就是"这段代码"(可以看作一个方法,因为方法是对代码块的封装),这又说明了什么?
说明lambda表达式在作用上和方法等价。
那么Lambda表达式其实就是把方法作为参数传递
Lambda表达式格式解析
写了一个compareString()方法,形参有三个,str1 str2 comparator,
public class Demo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abcd";
int i = compareString(str1, str2, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
}
public static int compareString(String str1, String str2, Comparator<String> comparator) {
return comparator.compare(str1, str2);
}
}
上面说到内部类等价与lambda表达式:
public class Demo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abcd";
// 上面推导得出Lambda表达式与匿名类对象等价,所以我们可以把Lambda表达式赋值给Comparator接口
Comparator<String> comparator = (String s1, String s2) -> {
return s1.length() - s2.length();
};
// 调用
int k = compareString(str1, str2, comparator);
}
public static int compareString(String str1, String str2, Comparator<String> comparator) {
return comparator.compare(str1, str2);
}
}
在调整
public class Demo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abcd";
// 上面推导得出Lambda表达式与匿名类对象等价,所以我们可以把Lambda表达式赋值给Comparator接口
Comparator<String> comparator = (String s1, String s2) -> {
return s1.length() - s2.length();
};
// 调用
int k = compareString(str1, str2, comparator);
// 改进一下,跳过赋值这一步,直接把整个Lambda传给compareString()方法:
compareString(str1, str2, (String s1, String s2) -> {
return s1.length() - s2.length();
});
// 上面的代码虽然能运行,但是idea一直显示灰色,说有更优雅的写法。好吧,我改改。
int x = compareString(str1, str2, (s1, s2) -> s1.length() - s2.length());
// 不对,还是不够精简,再改改(方法引用):
x = compareString(str1, str2, Comparator.comparingInt(String::length));
// 完美。
}
public static int compareString(String str1, String str2, Comparator<String> comparator) {
return comparator.compare(str1, str2);
}
}
可以发现
类名、方法名、形参类型、返回值类型、return关键字及方法体的{}都被省略了。
从语法格式来讲,Lambda表达式其实就是对一个方法掐头去尾,只保留形参列表和方法体。可以粗略认为:
Lambda表达式 = 形参列表 + 方法体
对于匿名内部类而言,类名、方法名真的没什么用。只有当一个方法需要被使用多次时,我们才需要为它命名,以便其他程序通过方法名重复调用。而匿名内部类的性质就是仅调用一次,所以名字对它来说是可有可无的。至于返回值与形参类型,Lambda都可以通过上下文推断,所以也可以省略:
// 基于上下文,很容易推算出返回值类型就是String。形参同理。
public someType get() {
return "Hello World!";
}
至于方法体的{}能不能省略,本质和if是否需要{}一样:
if(true)
System.out.println("可以省略{}");
if(true) {
int i = 1;
System.out.println("不可以省略{}");
}
函数式接口
介绍完Lambda表达式,最后提一下函数式接口。大家肯定会有疑问:难道所有接口都可以接收Lambda表达式吗?
显然不是的,接口要想接收lambda表达式,必须是一个函数式接口,所谓函数式接口,最核心的特征就是:有且仅有一个抽象方法。
我们给上面的MyRunnable接口加一个抽象方法:
Lambda表达式不能用了:
因为lambda表达式的本质是传递一个方法体,如果一个接口有两个方法需要实现,lambda表达式不知道要实现那个了。
你也可以这样想:
Java8的Lambda都是基于上下文推导的,当一个接口只有一个方法时,推导结果是唯一确定的,但是方法不唯一时,无法推导得到唯一结果。
为了提醒程序员编写Lambda接口时不要写多个抽象方法,Java8提供了一个新注解:
拓展:Lambda与匿名内部类
上面的案例一直在对比匿名内部类和Lambda,很多人可能在心里已经自动把Lambda等同于匿名内部类,认为Lambda是匿名内部类的语法糖。
然而并不是。
看代码:
@SpringBootTest
public class LambdaTest {
@Test
public void testClosure() throws InterruptedException {
// 在匿名内部类的外面定义一个String变量
final String str = "hello";
// 构造一个匿名内部类对象
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(str);
}
};
new Thread(r).start();
TimeUnit.SECONDS.sleep(1);
}
}
接下来,我们给上面的代码加上一句:
@SpringBootTest
public class LambdaTest {
@Test
public void testClosure() throws InterruptedException {
// 在匿名内部类的外面定义一个String变量
final String str = "hello";
// 构造一个匿名内部类对象
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(str);
System.out.println("this==="+this);
}
};
new Thread(r).start();
TimeUnit.SECONDS.sleep(1);
}
}
输出结果:
注意,带$表示匿名内部类,比如LambdaTest$1表示LambdaTest中的匿名内部类,编译后会产生两个文件。
但如果把Runnbale换成Lambda表达式实现:
输入结果
Lambda方法体外部并没有匿名内部类,当然只能指向LambdaTest。更准确地说,this是指向方法的调用者,是隐式传递的。从这个角度看,Lambda和匿名内部类本质上还是不同的。
以后编码时,再遇到这种编译错误就不会迷惑了:
改成Lambda表达式即可,因为Lambda表达式外层就是当前类的实例: