主要内容
- Lambda表达式
- Functional接口
- 方法引用
1.Lambda表达式
Lambda表达式这个新特性也许是Java8最受欢迎的一个,以至于在Android Studio还没有提供正式支持的时候,就有开发出的插件来兼容。
本人最先接触Lambda表达式是在Python中,众所周知Python语言是极其简洁的,Lambda表达式更是为其提供了巨大贡献。不过第一次接触的时候,感觉这种写法非常奇怪,但是写的多了以后,就会体会到Lambda表达式的巨大魅力。
由于本人现在主要是Android开发,所以就从Android代码中举例。个人认为在Android开发中有两个比较烦人的事情。第一个是findViewById(),只要不借用其他手段,写一个界面都要写大量的findViewById(),费事费力,但又少不了。第二个就是各种事件的绑定,如:
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
可能我就是想显示一个Toast,或者其他的简单的点击事件,就要写这么一大块代码,当然你也可以不用匿名内部类,但是随着整个类的扩大,以后看个代码,跳来跳去的,不胜其烦。
而且这两个问题对于Android初学者来说,都是一开始都会遇见的,估计学的时候也是看的一脸懵逼。(。•ˇ‸ˇ•。)
好在程序员们是永不满足的,第一个问题在Android中已经有了很好的解决方法,第二个问题也在Java8中得到了妥善解决,解决方法就是我们今天学习的Lambda表达式。
首先官方在文档中就承认:
One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear.
既然是笨重和不清楚的,那么Lambda如何来解决呢?为了使不接触Android开发的童鞋也能理解,我在这里模拟几个类:
interface MyListener {
void doSomething(String name);
}
public class MyButton {
private MyListener listener;
public void setListener(MyListener listener) {
this.listener = listener;
}
public void click(String name){
listener.doSomething(name);
}
}
public static void main(String[] args){
MyButton button = new MyButton();
button.setListener(new MyListener() {
@Override
public void doSomething(String name) {
out.print("hello " + name);
}
});
button.click("jack");
}
先看看setListener这一代码块中哪些是多余的,也就是对我们的逻辑不重要的。如果不清楚地话,可以回想一下,如果用的是IntelliJ之类的IDE,使用代码提示后,IDE自动帮我们补全了哪些代码(什么?你一直用记事本开发项目,好吧,我敬你是条好汉,请忽略这些,它不适用你现在的境界)。对,当你在括号内写个new,再写个首字母,回车一敲。OK!除了那行输出语句,其他的都给你写了,有的甚至还帮你加一行注释。
这也就清楚地告诉了我们,除了那条输出语句,也就是你自己的逻辑,其他的东西都是千篇一律,你就是写出朵花来也还是这些东西。既然IDE可以帮我们做这些东西,那么我们为何不能写简单一些,那些一样的东西在编译的时候,让编译器帮我们补上呢,或者编译器理解是什么意思就行,补不补都无所谓。于是Lambda表达就应运而生。
那么我们看看上面那段代码用Lambda表达式怎么写:
button.setListener( (String name) -> {out.print("hello "+name);} );
对,就是这么简洁,一行搞定(学完后面的方法引用时,会更加简洁)。
这里就引出了Lambda表达式的语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
前面是原来方法的参数,中间一个箭头,后面是要执行的逻辑,就这么清晰简单。
当然,如果像我刚才举的例子一样,一行语句,那么花括号可以省略,结尾引号也可以省略(多于一个则不可):
button.setListener((String name)->out.print("hello "+name));
有同学可能会问,那参数前面的类型是不是必要的呢?对,那个类型,不是我们使用的时候所决定的,是在定义的时候就已经决定的,所以在这里就算给写成另外一种类型也用不了,所以它也是多余的,也可以省略(特别的,如果只有一个参数,外面的那个小括号也可以省略,但是如果没有参数,必须放一个空括号)。
button.setListener(name -> out.print("hello " + name))
2.Functional接口
首先需要引入一个概念--函数式接口。简单的来说就是只含一个方法的普通接口。为什么要只含一个方法?目的就是为了更好地支持Lambda表达式,如果一个接口中有多个方法,是不可以使用Lambda表达式,因为Lambda表达式隐去了方法名和参数类型,无法确认到底使用的是哪个方法。
更重要的是,对于现有的能很好支持Lambda表达式的接口,一旦后期添加方法,就不再是函数式接口,很容易导致大量代码编译不过。所以,Java8提供了@FunctionalInterface的注解,来标注这个接口为函数式接口。对于添加了该注解的方法,如果有多个抽象方法,将会直接报错:
注意:虽然函数式接口只能有一个抽象方法,但是不影响有默认方法和静态方法,应为这两种方法不是默认重写的,在Lambda表达式中应用是不会出错的:
作为对应的更新,Java8也为我们提供了大量现成的函数式接口方便使用,他们大多数逻辑都比较简单,大家就算不使用在需要的时候也可以自己写出来。
可以参考这里:Java8 函数式接口
3.方法引用
Java8是将Lambda表达式作为更新的一个重头戏,所以为我们提供的当然不仅仅只是上面的特性,更是提供了方法引用这种语法,将Lambda表达式的简洁发挥到了极致。
在介绍什么是方法引用时,我们先看一个例子。前面我提供的第一个Lambda表达式后说,下面这个语句可以更加简洁:
button.setListener( (String name) -> {out.print("hello "+name);} );
是时候兑现承诺了:
button.setListener(out::println);
是不是更加简(sang)洁(xin)明(bing)了(kuang),感觉这次Java8誓要将简洁这条路走到底啊。
废话少说,这就是方法引用的一种形式--引用静态方法。它借鉴了类似c++中的操作符::,意思是相似的,表示操作符后面的方法是属于谁的。
为什么要添加方法引用这种语法呢?可以这样想,原来Lambda表达式的箭头左边是方法的参数,右边是代码逻辑,为了减少代码臃肿和提高复用,我们往往会专门写一个方法,然后传入左边的参数执行即可。也就是类似下面形式:
((arg1,arg2,...) -> fun(arg1,arg2,...) )
这里的关键就是那个函数,参数不是我们所能改变的,而我们在此所做的仅仅是将参数传入某个方法,某种意义来说,这也是一种多余的操作,能不能我们在这里指定要用的是哪个方法,编译器帮我们完成这个动作呢?
答案当然是可以的,利用的就是方法引用这一新语法。
方法引用一共有下面四种形式:
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某种类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
我们由此可以归纳出方法引用的规律,::操作符后面是方法名,方法名不带括号,虽说有四种形式,但是大体都是符合Java语法的。比如静态方法由类名调用,实例方法由实例化对象调用,只是将以前的点号替换为双冒号而已。
同时要注意,Lambda表达式左边的参数要和所引用的方法的参数要一致,不能多也不能少,顺序也要一致。
方法引用这种语法只有一个用途,就是用在Lambda表达式,以增加代码的简洁,如官方文档所说:
In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.
4.小结
在Android Studio没有正式支持Lambda表达式时,就已经可以将我们写的代码以Lambda表达式的形式展现,大家就可以感受到Lambda表达式带来的便利。
不过Lambda表达式表达式虽然非常简洁,但是对代码不熟悉的人,初次看见还是增加了不少阅读难度(其省略了类名,抽象方法名,参数类型,甚至改变的参数名,而Java非常提倡使用一些有清晰语义的命名,这些命名上的提示对于代码阅读和理解会有很大帮助)。
但是一旦熟练后你的代码将变得非常整齐简洁,无论是以后的修改还是别人阅览都会提供很多便利。(尤其是在Android开发中使用RxJava一类的API,其中夹杂着大量匿名内部类,Lambda表达式的引入更是让本来就很清晰的代码逻辑更加完美)