Lambda表达式初体验
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
public interface UserService {void show();}
public class LambdaTest {
public static void main(String[] args) {
toShow(new UserService() {
@Override
public void show() {
System.out.println("使用匿名内部类");
}
});
toShow(() -> {
System.out.println("使用Lambda表达式");
});
}
private static void toShow(UserService userService) {
userService.show();
}
}
运行结果:
可以看到使用匿名内部类和Lambda表达式都可以实现只有一个抽象方法一个接口,并且可以达到一样的目的,但是Lambda表达式明显是语法简单,只关注了抽象方法的实现内容。但是使用Lambda表达式是有要求的:Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。@FunctionalInterface 是一个标志注解,被修饰的接口中的抽象方法只能有一个,这个注解往往会和 lambda 表达式一起出现。比如:
定义了两个抽象方法,就会编译不通过,因此@FunctionalInterface 注解是规定接口只能有一个抽象方法的好帮手。
Lambda表达式的演变过程
Lambda表达式是jdk8的新特性,其出现的目的是为了避免过多的内部类,去掉一些没有意义的代码,只留下核心的代码,其实质属于函数式编程的概念,而Lambda表达式在多线程这一块使用的很多,因此在这里介绍。Lambda表达式只适用于函数式接口的使用,也就是只有一个方法的接口,这样可以简化大量代码。
public class Test {
static class Love2 implements LoveInterface {
@Override
public void love(String a, String b) {
System.out.println("I love you too --> " + a + b);
}
}
public static void main(String[] args) {
LoveInterface loveInterface = null;
//成员内部类
loveInterface = new Love1();
loveInterface.love("test01 ", "成员内部类");
//静态内部类
loveInterface = new Love2();
loveInterface.love("test02 ", "静态内部类");
//局部内部类
class Love3 implements LoveInterface {
@Override
public void love(String a, String b) {
System.out.println("I love you --> " + a + b);
}
}
loveInterface = new Love3();
loveInterface.love("test03 ", "局部内部类");
//匿名内部类
loveInterface = new LoveInterface() {
@Override
public void love(String a, String b) {
System.out.println("I love you --> " + a + b);
}
};
loveInterface.love("test04 ", "匿名内部类");
//Lambda表达式1.0
loveInterface = (String a, String b) -> {
System.out.println("I love you --> " + a + b);
};
loveInterface.love("test05 ", "Lambda表达式1.0");
//Lambda表达式2.0(如果参数只有一个括号也可以省略)
loveInterface = (a, b) -> {
System.out.println("I love you --> " + a + b);
};
loveInterface.love("test06 ", "Lambda表达式2.0");
//Lambda表达式3.0(如果方法体不止一行则不能去掉大括号)
loveInterface = (a, b) -> System.out.println("I love you --> " + a + b);
loveInterface.love("test07 ", "Lambda表达式3.0");
}
}
interface LoveInterface {
void love(String a, String b);
}
class Love1 implements LoveInterface {
@Override
public void love(String a, String b) {
System.out.println("I love you --> " + a + b);
}
}
通过上述代码可以看到,函数式接口的编写从“成员内部类”—>“静态内部类”—>“局部内部类”—>“匿名内部类”—>“Lambda表达式”的不断简化,通过Lambda表达式,对于函数式接口,无需它的实现类、方法名、参数类型等等,大量简化代码。
Lambda表达式语法
Lambda表达式由组成三部分:
(参数类 参数名 ...) -> { //方法体 }
// () 为参数列表 -> 为运算符(读作goes to) { } 为方法体
由此可见lambda表达式简化了方法的修饰符,返回值,方法名
注意:
- 小括号内的所有参数都可以不
- 写参数类型
- 只有一个参数时可省略(),方法体只有一条语句时可以省略{ }
- 如果方法体只有一行return语句,那么可以一并省略“return”和“;”
Lambda表达式的原理
public class LambdaTest {
public static void main(String[] args) {
toShow(() -> {
System.out.println("使用Lambda表达式");
});
}
private static void toShow(UserService userService) {
userService.show();
}
}
首先,使用“javap -p LambdaTest.class”查看 LambdaTest 类的 class 文件中所有方法,发现有一个不是我们自己定义的方法,那就是“private static void lambda$main$0()”
然后使用“javap -c -p LambdaTest.class”,查看 lambda$main$0() 方法:
可以看到方法体的内容就是lambda表达式的方法体内容。然后查看jdk的 LambdaMetafactory
类处理lambda的方法:
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
// 通过new一个InnerClassLambdaMetafactory并调用buildCallSite
// 为Lambda表达式生成了一个内部类
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
可见其实是再程序运行过程中动态生成一个匿名内部类,关于内部类,在运行时加上vm参数 -Djdk.internal.lambda.dumpProxyClasses,可以发现生成了:
由此可以知道Lambda表达式,编译器在类中生成一个静态函数,运行时调以内部类形式调用该静态函数,就好比这样的代码:
public class LambdaTest {
public static void main(String[] args) {
toShow(() -> {
lambda$main$0();
});
}
private static void toShow(UserService userService) {
userService.show();
}
private static void lambda$main$0(){
System.out.println("使用Lambda表达式");
}
}
Lamba和匿名内部类的对比
所需类型
- 匿名内部类的类型可以是类、抽象类、接口
-
抽象方法的数量
匿名内部类所需的接口中的抽象方法是没有限制的,只有全部实现即可
-
实现原理
匿名内部类是在编译后生成一个Class
-
Lambda表达式的主要用途
Lambda表达式创建线程
以前都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。
Thread t = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(2 + ":" + i); } }); t.start();给集合排序
再集合排序中,如果要自定义排序规则,就需要实现Comparable接口,而它刚好只有一个comperTo()抽象方法,这里就可以使用到lambda表达式。
public static void main(String[] args) { List<Person> list=new ArrayList<>(); Person person1=new Person("周杰伦",40); Person person2=new Person("林俊杰",38); Person person3=new Person("薛之谦",36); Person person4=new Person("易烊千玺",20); list.add(person1); list.add(person2); list.add(person3); list.add(person4); list.sort((p1, p2) -> { return p1.age - p2.age; }); list.forEach(System.out::println); }遍历集合
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); Collections.addAll(list, 1,2,3,4,5); //lambda表达式 方法引用 list.forEach(System.out::println); System.out.println("----------------"); list.forEach(element -> { if (element % 2 == 0) { System.out.println(element); } }); }
