1.背景
经常在使用Rxjava或者RxBind等一些函数式编程语言的时候就会发现有很多简写的语句,比如()->{}
这样子的东西,刚开始看不懂这些都代表什么意思,更不知道要怎么去写这样子看起来有逼格的代码,查了些资料,发现原来这是JAVA 8新特性Lambda表达式
。喜欢研究新技术的我呢,当然是不会落下这学习新东西的机会,于是,玩了一周的零碎时间来看了下我们的Lambda表达式
,今天来总结一下我对Lambda表达式
的粗浅认识。
2.什么是Lambda?
Lambda表达式
:我们可以简单的理解为一个匿名函数的速写形式(当然在java里的实现是和匿名函数有区别的),它帮助我们解决了很多项目中的冗余代码,在几乎不增加代码宽度
的情况下大幅减少了代码的高度
,使我们的代码变得更加精练、简洁,而且由于其实现方式有别于匿名函数,在一定程序上提高了效率。
3.什么是函数式接口?
函数式接口是只包含一个抽象方法的接口。比如我们的OnClickListener
接口
public interface OnClickListener {
void onClick(View v);
}
就是一个典型的函数式接口,Java 8里也定义了一种新的接口:@FunctionalInterface
,用于指明该接口类型声明是根据 Java 语言规范定义的函数式接口。
4.Lambda的结构构成
() -> {}
一段带有输入参数的可执行语句块。
1.一个Lambda由
()
,->
,{}
,其中()
表示参数,{}
表示执行语句或者代码块,由->
隔开。
2.所有的参数都在()
内,这里的参数主要是函数式接口里抽象方法所接收的参数,如果参数只有一个可以不用写()
,如v -> {代码体}
代码一个点击事件的Lambda
写法;如果没有参数,那么()
必须写,如() -> {System.out.println("aaa")}
3.所有的代码体都必须写在{}
里,代码体可以为0或者多条代码。如果只有一条语句,{}
可省略,反之,必须要有。
4.代码体的返回值类型与函数式接口的返回值类型一致。若没有,则返回为空。
5.Lambda表达式举例
触摸事件的监听处理:
//old way:
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
//new way:
mBtn.setOnTouchListener((v, event) -> {
return false;
});
6.方法引用(Method Preference)
方法引用
允许我们定义一个方法,然后以函数式接口的方式去访问它,也就是说通过方法引用,我们只需要写一个方法,然后通过方法名称直接引用该方法就可以了.方法引用
可以说是在lambda表达式
的基础上又进行了一次简化。而且方法引用还可以直接对已有方法进行引用,也就是说,连方法都不需要定义直接使用已有方法就可以了。代码相当的简洁。
7.方法引用格式
方法引用使用::
分隔符来定义方法引用。常见的方法引用方式有以下三种:
1.ObjectName::instanceMethod (对象::实例方法)
2.ClassName::StaticMethod (类名::静态方法)
3.ClassName::instanceMethod (类名::实例方法)
1,2都是比较正常的,参数都是函数式接口对应的参数,而3就比较特别一点,函数式接口对应参数的第一个参数会被拿来当作执行对象。如String::compareToIgnoreCase
等同于(x,y) -> x.compareToIgnoreCase(y)。
方法引用例子:
class Person {
private final String name;
private final int age;
public int getAge() { return age; }
public String getName() {return name; }
...
}
Person[] people = ...
//这里使用的是普通的lambda
Comparator<Person> byName = Comparator.comparing(p -> p.getName());
Arrays.sort(people, byName);
//使用方法引用 也就是上面的p参数会调用我们这里的getName方法
Comparator<Person> byName = Comparator.comparing(Person::getName);
看到上面的Person::getName
是不是觉得好神奇…我们这里调用的相当于自定义的方法,lambda
自动帮我们封装了一个函数式接口。
至于调用已存在不需要自己自定义的方法,在后面Rxbinding再讲。
8.构造器引用
构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。
9.Lambda表达式综合例子
这里主要是对比一下普通不用lambda表达式
,使用lambda表达式
以及使用方法引用
完成同一个功能的实现方式.
//普通实现
List<String> names = new ArrayList<>();
names.add("zhangshan");
names.add("lisi");
List<String> upcaseNames = new ArrayList<>();
for (String name : names) {
upcaseNames.add(name.toUpperCase());
}
//lambda实现
List<String> upcaseNames = names.stream().map(name -> name.toUpperCase()).collect(Collectors.toList());
//方法引用的实现(这里调用的就是已经存在的方法)
List<String> upcaseNames = names.stream().map(String::toUpperCase).collect(Collectors.toList());
上面的三种实现已经很明显了吧,代码结构越来越简洁。当然这里我们先不用去管Stream
,map
以及collect
这三个操作符,它们是Java 8函数式编程的新特性。
10.Lambda表达式与匿名内部类的区别
在很大程度上,我们可以理解为lambda
解决的就是匿名内部类的高度问题
,但实际上,它们是有本质区别的。
1.它们在底层实现上是不一样的,lambda的底层实现更加优化。
2.关于关键字this
,两者的有区别的。lambda
表示创建表达式的外部类,而匿名内部类指向的是自己本身。
3.两者同样都可以引用外部类的变量,而且都是要在变量不可变的情况下。匿名内部类需要显示的对外部类的变量加final
修辞符,而lambda
不需要显示的加final
修辞符,编译器会隐式的加上。
11.Lambda扩展:Rxbinding的使用
使用Rxjava的同学们应该也会使用Rxbinding
来进行一些绑定的操作,为什么用呢?应该它是函数式的。RxBinding
也支持使用lambda表达式
比如:按钮的点击事件处理
RxView.clicks(mBtn).subscribe(this::onclicka);
private void onclicka(Void aVoid) {
}
这里使用的就是方法引用
的调用自定义方法的操作。
再来看,TextView的textChanged事件
RxTextView.textChanges(mTv).subscribe(mTv::setText);
这里使用的就是方法引用
的调用已存在方法的操作。
好了,今天的lambda
之旅就到这里了。