为了支持函数式编程,Java 8引入了Lambda表达式,Android N已经开始支持Java 8 了。Java 8中的新特性,是开发者们的一大福音,从此我们可以happy的在代码中使用Lambda了,调用Stream等。本篇文章主要介绍Lambda的特性,实现原现,使用方法,关于Java8 新特性及用法,会再开一篇博文进行总结。使用Lambda可以大大减少代码的编写,只关注最重要的部分。虽然使代码的可读性变差,但用习惯了就会喜欢上Lambda表达式,它使代码变得干净整洁了不是一点半点。
既然大家都用上了lambda表达式,为什么我还要写这篇文章呢,咱们开发人员当然不能老是拿来主义,知其然还得知其所以然。在Java 8中到底是如何实现Lambda表达式的呢? Lambda表达式经过编译之后,到底会生成什么东西呢? 它和匿名内部类有什么区别呢?如果掌握了它的运行原理,以后面试被问起来,也能说出个1,2,3来不是。
lambda表达式不是简单的匿名内部类,因为使用匿名内部类,编译器会为每一个匿名内部类创建一个类文件,而类在使用前需要加载类文件并进行验证,这个过程会影响应用的启动性能。类文件加载很可能是一个耗时的操作,若lambda采用匿名内部类实现,会使应用内存占用增加,同时也会使lambda表达式与匿名内部类的字节码生成机制绑定。所以lambda表达式不是采用匿名内部类来实现。
我们通过分析下面代码:
public class Lambda {
Function f = s -> Integer.parseInt(s);
}
查看上面的类经过编译之后生成的字节码:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: invokedynamic #2, 0 // InvokeDynamic
#0:apply:()Ljava/util/function/Function;
10: putfield #3 // Field f:Ljava/util/function/Function;
13: return
可以看到lambda使用了java中的动态指令,所以lambda内部并不是使用内部类来实现的。
lambda表达式将翻译策略推迟到运行时,主要是将表达式转成字节码invoked dynamic 指令,如上面编译成的字节码,主要有以下两步:
1)生成一个invoked dynamic调用点(dynamic工厂),当lambda表达式被调用时,会返回一个lambda表达式转化成的函数式接口实例;
2)将lambda表达式的方法体转换成方法供invoked dynamic指令调用。
对于大多数情况下,lambda表达式要比匿名内部类性能更优。
对于lambda表达式翻译成实际运行代码,分为对变量捕获和不对变量捕获方法,即是否需要访问外部变量。
对于下面的表达式:
public class Lambda {
Function f = s -> Integer.parseInt(s);
}
1)对于不进行变量捕获的lambda表达式,其方法实现会被提取到一个与之具有相同签名的静态方法中,这个静态方法和lambda表达式位同一个类上。
上面的表达式会变成:
static Integer lambda$1(String s) {
return Integer.parseInt(s);
}
2)对于捕获变量的lambda表达式,lambda表达式依然会被提取到一个静态方法中,被捕获的变量会同正常的参数一样传入到这个方法中。
static Integer lambda$1(int offset, String s) {
return Integer.parseInt(s) + offset;
}
1)连接方面,上面提到的lambda工厂,这一步相当于匿名内部类的类加载过程,虽然预加热会消耗时间,但随着调用点连接的增加,代码被频繁调用后,性能会提升,另一方面,如果连接不频繁,lambda工厂方法也会比匿名内部类加载快,最高可达100倍;
2)如果lambda不用捕获变量,会自动进行优化,避免基于lambda工厂实现下额外创建对象,而匿名内部类,这一步对应的是创建外部类的实例,需要更多的内存;
lambda表达式并非java8所特有,scala曾经通过匿名内部类的形式支持lambda表达式。
1) 在工程的build.gradle的buildscript下的dependencies下加入依赖:
classpath 'me.tatarka:gradle-retrolambda:3.3.1'
2) 在app的build.gradle中最头上引入lambda包
apply plugin: 'me.tatarka.retrolambda'
并在android闭包标签下添加:
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
这样就可以在程序中直接使用lambda快速进行开发了。
1) setOnItemClickListener的替换写法
//之前
xxxListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
//Do something
}
});
//使用lambda后
xxxListView.setOnItemClickListener((parent,view,position,id)->{
//Do something
});
//甚至
xxxListView.setOnItemClickListener((a,b,c,d)->{
//Do something
});
2) onClickListener的写法
//之前
View.OnClickListener onClickListener = new View.OnClickListener(){
@Override
public void onClick(View view) {
handleClick();
}
});
findViewById(R.id.someView).setOnClickListener(onClickListener);
//使用lambda后
View.OnClickListener onClickListener = view -> handleClick();
findViewById(R.id.someView).setOnClickListener(onClickListener);
3) 遇到Rxjava中Action或Fun函数时:
如下面的请求用户信息,subscribe(Consumer
//之前 Api.getInstance().getRoleInfo(requestEnvelope)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(ResponseEnvelope responseEnvelope) throws Exception {
//response(responseEnvelope);
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
//throwException(throwable);
}
});
//使用lambda后 Api.getInstance().getRoleInfo(requestEnvelope)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::response, this::throwException));
只需在类中定义相应的response和throwException方法即可,使用上更简洁直观。