ps: Lambda 出来啦很久了,我最近才开始学习使用啊,这还是因为 DataBinding 调用带参数的方法需要用到 Lambda 表达式才开始学的。话说新东西出来的确是要学的,研究的。就算不是第一时间去学习研究,那也要在有人尝过鲜之后对齐有肯定的评价后,第一第二时间开始学习入手,要是都等到需要的时候再去学习,那真是一件很让让人沮丧的事。
再说一下,Lambda 刚出来时,因为的确就看着不习惯,也没去专门学习 Lambda ,这件事往小了说是偷懒了,往大了说就是没有勇气去跨出舒适区,没有勇气去跳出舒适区,怎么能快速的进步呢,要不怎么升职加薪,我最近也是体悟出了这个道理的含义。开发同修仙,不进则退,只有挑战自己向前走这一条路,也就是要勇于跳出舒适区。
先说 AS 如何支持 Lambda 表达式
-
首先确定你的AndroidStudio中使用的是jdk1.8的版本
在项目的根路径 build.gradle 文件中添加 Lambda 的脚本下载路径,也就是 classpath
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.2.5' // 添加的这行
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
重点再说一次,是在项目的根路径的 build.gradle 文件中添加,别加错地方
- 在你的module目录下,通畅也就是 app 目录下的 build.gradle 下添加 java 编译版本号
apply plugin: 'me.tatarka.retrolambda'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
// 加的这块
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "ex.hxx.com.daggertest"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
.......
-
然后我们就可以看见在编译器在可使用 lambda 的位置会出现灰色提示,可以使用 ALT+ENTER 快捷键自动转换成 lambda 表达式
跟着上面操作过后,我们就可以在代码里面使用 Lambda 表达式了,那么 Lambda 表达式是什么呢
简单认识下 Lambda
最简单的说法,也是直指本质的是: Lambda 是 java 对方法中需要传入的 匿名实现类的简写。举个例子,我们给 button 注册一个点击事件,就是传入一个 View.ClickListener 对象。大伙都体验过在一个方法的参数中传入一个匿名实现类,一个还好,要是需要传入多个呢,那在查看时就是低于式体验了,太不友好了,这种体验类似于 callback 的回调地狱,我找个 JS 的例子大家欣赏下:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
这个玩意写完了,就是让作者自己看都尼玛感觉是坨屎吧,有时候,这种情况避免不了,尤其是在网络,多线程,数据库代码中这种多层 callback 嵌套或是需要传入多个匿名实现对象的代码会有较多机会出现。Lambda 就是为了解决这种屎一样的代码的,效果还可以,但还是不够彻底,也就是从没法看,变成有法看了,但我们还是得学啊,因为目前也只能做到这样了,别人都这么写,到时你看不懂,看不习惯,看不爽就麻烦了。
上面简单的说了 Lambda 是干什么的,那么现在正式来说说 Lambda 了。Lambda 是 java 8 的特性, 是函数式编程的一个思想,是 java 在往函数式编程发展过程中加入的一大特性。
Lambda 在函数式编程中应该叫 闭包 ,我们平时也叫 Lambda 表达式,这里有一个 闭包 概念的解释,看不懂没关系,不影响我们使用他。
"定义在函数内部的函数", 在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁
--来自阮一峰技术博客
对于函数式编程和 java 的对比,北京 GDG 2017中有一位就是专门的说的这块内容,值得一看,Kotlin as Your Next Language ,放心是中文的
看着很抽象是不,其实我们可以这样理解:使用 Lambda 表达式替代匿名实现类,Lambda 表达式最大的好处是简捷,尤其是对于内部只有一行逻辑实现的匿名实现类优化最好,但是注意啊,Lambda 只能代替内部有一个方法的匿名类,一般多用来替代只有一个方法的接口和内部类,比如各种 ClickListener,runnable 接口。
好了我们先看一下 Lambda 表达式的优势,使用 button 的点击事件做个例子:
原生版
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText( MainActivity.this,"AAA",Toast.LENGTH_SHORT ).show();
}
});
Lambda 版
button.setOnClickListener(v -> Toast.makeText( MainActivity.this,"AAA",Toast.LENGTH_SHORT ).show());
这样我们把2层多行代码通过 Lambda 表达式优化成一层单行代码,这样阅读起来的的确确是好太多了。缺点是必须要适应,熟悉 Lambda 表达式的写法,转变起来需要一些时间。
Lambda 语法
Lambda 的语法是隐藏实现类的 类名 和 方法名,下面是基本写法:
(parameters) -> { expression or statements }
左边括号内是方法需要传入的参数,右边括号内是方法实现逻辑
下面是一些例子:
// 无参数, 返回1+2的结果
() -> 1+2
// 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 接收2个参数(数字),返回表达式运算的结果
(x, y) -> x + y
// 多个语句要用大括号包裹, 并且返回值要用return指明
(x, y) -> {
int result = x + y;
System.out.print(result);
return result;
}
// 接收string 对象, 并在控制台打印
s -> System.out.print(s)
其中参数的类型可以不声明, 编译器会结合上下文智能推断, 比如这句
s -> System.out.print(s)
等价于
(String s) -> System.out.print(s)
注意: 无参数时()不能省略
语法非常简单, 就是因为简单, 反而更让人摸不着头脑。但是注意啊,Lambda 的语法的确是隐藏实现类的 类名 和 方法名,但是这是对我们编码人员来说,对于机器来说还是要能准备的知道实现类类型和方法名,对于实现类类型,这个是由外层调用者的规定来实现的,对于方法名来说,就是通过限定实现类内部只有一个方法来实现的,要是有多个方法的话,编译器知道你调的是哪个方法,这里是在我们做代码封装,框架设计时需要注意的。
可能上面语法写的有些简略,可能会给大家带来一些困扰,那么下买我再来一个 Lambda 的例子:
标准 java 代码:
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
这是一段很简单的排序代码,但是涉及到一个Java匿名内部类的书写之痛,排序方法真正核心的是compare方法内的实现,但是为了实现b.compareTo(a),我们写了很多不必要的代码,lambda表达式正是这种类型代码的救星。
标准 lambda 表达式:
Collections.sort( names, (String a, String b) -> {
return b.compareTo(a);
} );
简略 lambda 表达式 1:
// 方法实现要是单行,我们可以省略 return,缩进为一行
Collections.sort( names, (String a, String b) -> {b.compareTo(a);} );
简略 lambda 表达式 2:
// 方法实现要是单行,我们还可以省去大括号
Collections.sort( names, (String a, String b) -> b.compareTo(a) );
简略 lambda 表达式 3:
// JAVA8 可以根据上下文直接推导出参数类型,我们还可以去掉参数类型
Collections.sort(names, (a,b) -> b.compareTo(a));
最终:我们的代码由原来的七行代码变为一行代码,初次感受可能感觉可读性降低了,不好看,但若是大家熟悉了 lambda 的写法,你会发现这样比之前容易阅读的多。
我们直接使用 lambda 获取匿名实现类对象也是可以的:
Comparator<String> comparator = (String s1, String s2) -> {
doSomeWork();
return result;
};
我再举几个 lambda 的应用场景:
- 单实现的接口
- 条目点击事件
- 封装网络请求的回调
- 与RxJava的链式调用
1.view.setOnClickListener(v -> {
//dosomething
});
2. listview.setOnItemClickListener((parent, view, position, id) -> {
//dosomething
});
3.例如通过封装OkHttp或者Retrofit的回调方法,将其转变成单实现的回调接口进行调用
4.Integer[] list = {1,2,3,4,5};
Observable
.from(list)
.filter(integer->integer%2==0)//挑选出偶数
.map(integer -> "number is"+integer)//转换成String
.subscribe(s->System.out.println(s));//相当于forEach(s->System.out.println(s));
//forEach是同步的 subscribe是异步的
最后
写到这里 lambda 就可以了,但是有几点还是要说一下的:
- lambda 只使用于 内部只有一个 方法 的 接口 和 类 ,方法多了编译器会识别不出来你想调用那么方法,因为 lambda 把方法名省略了
- 若要使用 lambda ,在代码封装和框架设计时要注意 lambda 省略 类名和方法名的特性
- Lambda的作用域:
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量(在访问外层局部变量时虽然没有添加final关键字,但是我们可以理解为该变量为隐形final),或者实例的字段以及静态变量。即局部变量在lambda表达式中只可读不可写,而成员变量和静态变量既可读又可写; - this 关键字:
在匿名内部类中,this 关键字指向的是匿名类本身的对象,而在 lambda 中,this 指向的是 lambda 表达式的外部类。 - 方法数差异
当前 Android Studio 对 Java 8 新特性编译时采用脱糖(desugar)处理,lambda 表达式经过编译器编译后,每一个 lambda 表达式都会增加 1~2 个方法数。而 Android 应用的方法数不能超过 65536 个。虽然一般应用较难触发,但仍需注意。