什么是Lambda表达式
Lambda表达式基于数学中的λ演算得名,lambda表达式也可以叫做匿名函数,是指一类无需定义标识符(函数名)的函数或子程序。匿名函数最早是由LISP语言提出,后续好多语言都支持此特性。JAVA语言是面向对象语言,号称一切皆为对象,对函数式编程的一些特性一直没有支持。但随着现在语言环境的发展,scala/python/ruby/c++等等发展,java也被感动了,java8也支持了lambda表达式,使java语言又有了新的活力。
我们先看一段代码例子,lambda表达式的样子。
java8以前的代码:
//创建一个线程,打印一句话
Thread thread = new Thread(new Runnable(){
public void run(){
System.out.println("Hello Lambda");
}
});
//对整数数组排序
Integer[] array = {1,9,8,38,3,9,7,12,30};
Arrays.sort(array, new Comparator<Integer>(){
@Override
public int compare(Integer i1, Integer i2) {
return i1.compareTo(i2);
}
});
java8使用lambda表达式的代码:
//创建一个线程,打印一句话
Thread thread = new Thread(() -> System.out.println("Hello Lambda"));
//对整数数组排序
Integer[] array = {1,9,8,38,3,9,7,12,30};
Arrays.sort(array,(i1,i2) -> i1.compareTo(i2));
java8 使用lambda表达式是不是更简短明了?感觉心情一下就舒畅了。是不是想试试?我们下面看看lambda的语法。
lambda表达式语法
(parameters) -> {
body
}
lambda表达式示例
//定义一个函数,返回两个整数的和
(Integer i1,Integer i2) -> { return i1 + i2; }
//没有参数的一个函数,打印一句话
()->{System.out.println("Hello")}
//返回一个整数常量100
() -> {return 100;}
//打印传入字符串
(String s) -> { System.out.println(s); }
上面的一些示例是语法的标准写法,其实可以支持一些简化的技巧:
- 参数的类型既可以明确声明,也可以根据上下文来推断,大部分情况都不需要你声明类型,除非你需要
- 空圆括号代表参数集为空
- 如果只有一个参数,圆括号也可以省略。 a -> return 2*a
- 如果函数主体只有一句,大括号也可以省略,分号也可以省略
- reuturn 关键字也可以省约,匿名函数以代码段最后表达的值为函数返回值
- 匿名函数返回值类型和代码块表达式一致,当然也可以没有返回值
我们可以看看简化后的代码样子:
//定义一个函数,返回两个整数的和
//参考上述简化技巧1,4,5条
(i1,i2) -> {i1 + i2}
//没有参数的一个函数,打印一句话
//参考上述简化技巧 2,4,6条
() -> System.out.println("Hello")
//返回一个整数常量100
//参考上述简化技巧2,4,5,6
() -> 100
//打印传入字符串
//参考上述简化技巧3,4,6
s -> System.out.println(s)
lambda表达式可以引用函数或类范围内的变量,但不能做修改,你可以理解为lambda应用的都是final变量,当然你可以在匿名函数内部定义局部变量。
public static void doSomeThing(String s, int i) {
Runnable r = () -> {
System.out.println(s);
System.out.println(i);
//i ++; 不能修改
};
new Thread(r).start();
}
函数式接口
lambda对所有函数式接口都兼容。函数式接口就是只有一个抽象方法的接口,比如Runnable,ActionListener,PathFilter等等。java8 也提供了一些函数接口,方便实现函数式编程。
Consumer<Integer> c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };
可以使用系统定义好的函数式接口实现函数式编程,举个简单例子看看:
public class PredicateTest {
public static void doFilter(List<Integer> list, Predicate<Integer> p){
for(Integer i : list){
if (p.test(i)){
System.out.println(i);
}
}
//完全java8的写法,使用stream接口可以一句话
//list.stream().filter(p).forEach(System.out::println);
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 9, 8, 38, 3, 9, 7, 12, 30);
//打印出所有偶数
doFilter(list,i -> i % 2 == 0);
//打印出所有大于50的数
doFilter(list,i -> i >= 50);
}
}
你也可以自己定义函数接口,然后通过lambda表达式使用。我们举一个简单例子:
MyInterface.java
//声明一个函数式接口
@FunctionalInterface
public interface MyInterface {
public void doSomeThing();
}
FunctionInterfaceTest.java
public class FunctionInterfaceTest {
public void action(MyInterface handler){
handler.doSomeThing();
}
public static void main(String[] args) {
FunctionInterfaceTest instance = new FunctionInterfaceTest();
//匿名类的方式
instance.action(new MyInterface(){
public void doSomeThing(){
System.out.println("匿名类的方式调用");
}
});
//lambda方式
instance.action( () -> System.out.println("lambda表达式的方式调用") );
}
}
lambda表达式的方式实现更简洁,它和匿名类最大的区别式this关键字的解释不同,匿名类的this指向匿名类自身,而lambda表达式引用的this指向外部对象。只是由于两者的编译方式不一样,编译器会将lambda 表达式转化为类里面的私有函数,使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,所有lambda表达式引用的this为外部对象.所有lambda表达式和函数的效果类似。
方法引用
看看下面的一些代码示例的写法:
//System.out::println 等同于 x -> System.out.println(x),对象方法引用
button.setOnAction(System.out::println);
//Integer::compareTo 等同于 (x,y) -> x.compareTo(y)
list.sort(Integer::compareTo);
//构造方法的引用,将流转换成数组
Button[] buttons = stream.toArray(Button[]::new);
通过例子可以看出 ::操作符可以实现下面几种情况的方法引用,同时也支持方法重载,编译器会根据上下文现在相应的函数实现。
object::instanceMethod
Class::staticMethod
Class::instanceMethod
super::instanceMethod
实现原理
lambda表达式其实就是一种语法糖,编译器会将lambda 表达式转化为类里面的私有函数,使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,可以通过观察class字节观察一下。
$ javap -p PredicateTest
public class java8_test.PredicateTest {
public java8_test.PredicateTest();
public static void doFilter(java.util.List<java.lang.Integer>, java.util.function.Predicate<java.lang.Integer>);
public static void main(java.lang.String[]);
private static boolean lambda$0(java.lang.Integer);
private static boolean lambda$1(java.lang.Integer);
}
可以看到我们定义的PredicateTest 多个两个私有函数 lambda$0 和 lambda$1,如果javap -v -p -s 可以看到更详细的信息,感兴趣可以看看。
总结
对Java8 的lambda表达式的特性评价褒贬不一,有人担心会破坏java代码的严谨和可读性。我觉得只要合理恰当使用会使你的代码更简洁优雅,结合stream api更会产生一样不到的效果。比如你用java8写spark应用你会觉得很爽,也可以写出比较优雅的代码,不用受到scala程序员的鄙视了。