参考资料:
https://mp.weixin.qq.com/s/ht3KIeG9-H9HtgF_nlm5cw
Lambda,中文名“兰布达”。是匿名函数的别名,Java8后开始引入Lambda表达式.而Android方面Android Studio 2.4 Preview 4 及其之后完全的支持lambda 表达式,如果是之前版本就需要借助插件和编译器了。下面以我们常见的点击事件为例开始讲解Lambda表达式,先看下面的代码:
TextView tv =findViewById(R.id.textView);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
上面的代码是我们常见的简单的不能在简单的点击事件,我们把此代码转化为Lambda表达是看看效果,等等,我们上面说了Java8后开始引入Lambda表达式支持,Android Studio 2.4 Preview 4 及其之后完全的支持lambda 表达式,那我们只需要设置一下自己的Project引用的是JDK1.8即可,如图:
然后我们回到代码发现,建议开发者替换原有写法,改为lambda表达式:
image.png
我们按照要求选择一下,然后回车:
image.png
既可以看到代码变成了如下的样子:
image.png
我去,,第一眼感觉就是代码变少了。这其实也是Lambda表达式的优点:其对匿名内部类笨拙繁琐的代码的简化.lambda 表达式不仅对对象名进行隐匿,更完成了方法名的隐匿,展示了一个接口抽象方法最有价值的两点:参数列表和具体实现.
那么我们就来探讨监听事件是怎么通过Lambda表达式一步步的如此简洁的。
1.Lambda表达式的形式
Lambda表达式共有三种形式:函数式接口,方法引用,构造器引用。其中函数式接口是最基本的,其余两种形式都是基于它进行拓展。
1.1函数式接口
函数式接口指的是有且只有一个抽象方法的接口,比如我们上面的OnClickListener。lambda 表达式就是对这类接口的匿名内部类进行简化。基本形式如下:
( 参数列表... ) -> { 语句块... }
- ok,那我们基于基本形式对setOnClickListener(new View.OnClickListener()){}做一下改变为:
// 基本形式如下:( 参数列表... ) -> { 语句块... }
tv.setOnClickListener((View v) -> {
//doSomeThing.....
});
- 当参数只有一个时,参数列表两侧的圆括号也可省略
//参数只有一个时(注意是只有一个时,两个时就正常写吧),参数列表两侧的圆括号也可省略
tv.setOnClickListener(v -> {
//doSomeThing.....
});
- 当语句块内的处理逻辑只有一句表达式时,其两侧的花括号也可省略
tv.setOnClickListener(v -> Log.e(TAG, "花括号也可省略" ));
看到没,就是这个样子,就是这么变过来的,就是这么简单
-
当只有一句去除花括号的表达式且接口方法需要返回值时,这个表达式不用(也不能)在表达式前加 return ,就可以当作返回语句。下面用 Java 的 Function 接口作为示例,这是一个用于转换类型的接口,在这里我们获取一个 User 对象的姓名字符串并返回:
image.png
1.2方法引用形式
我们先来一个方法引用形式的分类,然后一个个讲解,方法应用形式有三种:
- ClassName :: staticMethod
- ClassName :: instanceMethod
- object :: instanceMethod
-
1.2.1 ClassName :: staticMethod
直接调用某类的静态方法,并将接口方法参数传入。
比如我们写一个例子看看,我们先定义一个判断字符是否为空的接口:
public interface IIsEmpty<T> {
//判断是否为空的接口
boolean isEmpty( T t);
}
我们知道TextUtils.isEmpty()可以判断非空,那我们就引用,然后在看其具体的实现:
IIsEmpty<String> iIsEmpty = s1 -> TextUtils.isEmpty(s1);
我们在继续使用方法引用的形式继续简化这一段 lambda 表达式(也就是 ClassName :: staticMethod):
IIsEmpty<String> iIsEmpty = TextUtils::isEmpty;
方法引用形式就是当逻辑实现只有一句且调用了已存在的方法进行处理( this 和 super 的方法也可包括在内)时,对函数式接口形式的 lambda 表达式进行进一步的简化。传入引用方法的参数就是原接口方法的参数。
如果上面的你还没看懂,我们在来一个例子:
interface Predicate<T> {
boolean test(T t);
}
public static boolean doPredicate(int age, Predicate<Integer> predicate) {
return predicate.test(age);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//判断输入的数字是否大于18
boolean isAdult = doPredicate(20, x -> x >= 18);
System.out.println(isAdult);
}
这下懂了没?
实际上上述例子中的接口是不用我们自己写的,jdk设计者已经为我们准备了java.util.function包:
image.png
我们前面写的Predicate函数式接口也是JDK种的一个实现,他们大致分为以下几类:
image.png
-
1.2.2 object :: instanceMethod
直接调用任意对象的实例方法,如 obj::equals 代表调用 obj 的 equals 方法与接口方法参数比较是否相等,效果等同 obj.equals(t);。 -
1.2.3ClassName :: instanceMethod
image.png
较为特殊,将接口方法参数列表的第一个参数作为方法调用者,其余参数作为方法参数。由于此类接口较少,故选择 Java 提供的 BiFunction 接口作为示例,该接口方法接收一个 T1 类对象和一个 T2 类对象,通过处理后返回 R 类对象:
1.3构造器引用
Lambda 表达式的第三种形式,其实和方法引用十分相似,只不过方法名替换为 new 。其格式为 ClassName :: new。这时编译器会通过上下文判断传入的参数的类型、顺序、数量等,来调用适合的构造器,返回对象。
我们来定义一个实体类UserBean
public class UserBean {
String userName;
int age;
public UserBean(String userName) {
this.userName = userName;
}
public UserBean(){
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public UserBean(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "UserBean{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
接下来定义一个返回UserBean的接口:
public interface Construction<T> {
T getBean(String name ,Integer age);
}
具体使用:
/**
* 通过Lamdba构造器获取对象
*/
void constructorMthod() {
Construction<UserBean> construction = UserBean::new;
UserBean userBean =construction.getBean("张三",15);
System.out.println(userBean.toString());
}
2.其他说明
2.1 变量捕获
在使用匿名内部类时,若要在内部类中使用外部变量,则需要将此变量定义为 final 变量。因为我们并不知道所实现的接口方法何时会被调用,所以通过设立 final 来确保安全。在 lambda 表达式中,仍然需要遵守这个标准。
不过在 Java 8 中,新增了一个 effective final 功能,只要一个变量没有被修改过引用(基本变量则不能更改变量值),即为实质上的 final 变量,那么不用再在声明变量时加上 final 修饰符,也就是说我们可以不做任何声明上的改变即可在 lambda 中使用外部变量,前提是我们以 final 的规则对待这个变量。
2.2 this 关键字
在匿名内部类中,this 关键字指向的是匿名类本身的对象,而在 lambda 中,this 指向的是 lambda 表达式的外部类。
2.3 方法数量差异
当前 Android Studio 对 Java 8 新特性编译时采用脱糖(desugar)处理,lambda 表达式经过编译器编译后,每一个 lambda 表达式都会增加 1~2 个方法数。而 Android 应用的方法数不能超过 65536 个。虽然一般应用较难触发,但仍需注意。
2.4 默认方法
在Java语言中,一个接口中定义的方法必须由实现类提供实现。但是当接口中加入新的API时, 实现类按照约定也要修改实现,而Java8的API对现有接口也添加了很多方法,比如List接口中添加了sort方法。 如果按照之前的做法,那么所有的实现类都要实现sort方法,JDK的编写者们一定非常抓狂,那应当怎么办呢?
在Java8种引入新的机制,支持在接口中声明方法同时提供实现。 这令人激动不已,你有两种方式完成 1.在接口内声明静态方法 2.指定一个默认方法。
如我们上面定义的Predicate接口:
调用执行 :
Predicate predicate = o -> false;
predicate.testMethod();
基本就到这里了,拜拜。
网友评论