Java8 新特性Lambda表达式实例详解

在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口):
public interface OnClickListener {
void onClick(View v);
}
我们是这样使用它的:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setText("lalala");
}
});
这种回调模式在各种框架中非常流行,但是像上面这样的匿名内部类并不是一个好的选择,因为:
令人高兴的是Java8为我们带来了Lambda,下面我们看看利用Lambda如何实现上面的功能:
button.setOnClickListener(v -> v.setText("lalala"));
怎么样?!五行代码用一行就搞定了!!!
在这里补充个概念函数式接口;前面提到的OnClickListener接口只有一个方法,Java中大多数回调接口都有这个特征:比如Runnable和Comparator;我们把这些只拥有一个方法的接口称之为函数式接口。
一、Lambda表达式
匿名内部类最大的问题在于其冗余的语法,比如前面的OnClickListener中五行代码仅有一行是在执行任务。Lambda表达式是匿名方法,前面我们也看到了它用极其轻量的语法解决了这一问题。
下面给大家看几个Lambda表达式的例子:
(int x, int y) -> x + y //接收x和y两个整形参数并返回他们的和
() -> 66 //不接收任何参数直接返回66
(String name) -> {System.out.println(name);} //接收一个字符串然后打印出来
(View view) -> {view.setText("lalala");} //接收一个View对象并调用setText方法
Lambda表达式语法由参数列表、->和函数体组成。函数体既可以是一个表达式也可以是一个代码块。
表达式:表达式会被执行然后返回结果。它简化掉了return关键字。
代码块:顾名思义就是一坨代码,和普通方法中的语句一样。
二、目标类型
通过前面的例子我们可以看到,lambda表达式没有名字,那我们怎么知道它的类型呢?答案是通过上下文推导而来的。例如,下面的表达式的类型是OnClickListener
OnClickListener listener = (View v) -> {v.setText("lalala");};
这就意味着同样的lambda表达式在不同的上下文里有不同的类型
Runnable runnable = () -> doSomething(); //这个表达式是Runnable类型的 Callback callback = () -> doSomething(); //这个表达式是Callback类型的
编译器利用lambda表达式所在的上下文所期待的类型来推导表达式的类型,这个被期待的类型被称为目标类型。lambda表达式只能出现在目标类型为函数式接口的上下文中。
Lambda表达式的类型和目标类型的方法签名必须一致,编译器会对此做检查,一个lambda表达式要想赋值给目标类型T则必须满足下面所有的条件:
由于目标类型是知道lambda表达式的参数类型,所以我们没必要把已知的类型重复一遍。也就是说lambda表达式的参数类型可以从目标类型获取:
//编译器可以推导出s1和s2是String类型
Comparator<String> c = (s1, s2) -> s1.compareTo(s2);
//当表达式的参数只有一个时括号也是可以省略的
button.setOnClickListener(v -> v.setText("lalala"));
ps: Java7中的泛型方法和<>构造器也是通过目标类型来进行类型推导的,如:
List<Integer> intList = Collections.emptyList>(); List<String> strList = new ArrayList<>();
三、作用域
在内部类中使用变量名和this非常容易出错。内部类通过继承得到的成员变量(包括来说object的)可能会把外部类的成员变量覆盖掉,未做限制的this引用会指向内部类自己而非外部类。
而lambda表达式的语义就十分简单:它不会从父类中继承任何变量,也不用引入新的作用域。lambda表达式的参数及函数体里面的变量和它外部环境的变量具有相同的语义(this关键字也是一样)。
下面我们举个栗子吧!
public class HelloLambda {
Runnable r1 = () -> System.out.println(this);
Runnable r2 = () -> System.out.println(toString());
@Override
public String toString() {
return "Hello, lambda!";
}
public static void main(String[] args) {
new HelloLambda().r1.run();
new HelloLambda().r2.run();
}
}
上面的代码最终会打印两个Hello, lambda!,与之相类似的内部类则会打印出类似HelloLambda$1@32a890和HelloLambda$1@6b32098这种出乎意料的字符串。
总结:基于词法作用域的理念,lambda表达式不可以掩盖任何其所在上下文的局部变量。
四、变量捕获
在Java7中,编译器对内部类中引用的外部变量(即捕获的变量)要求非常严格:如果捕获的变量没有被声明为final就会产生一个编译错误。但是在Java8中放宽了这一限制–对于lambda表达式和内部类,允许在其中捕获那些符合有效只读的局部变量(如果一个局部变量在初始化后从未被修改过,那么它就是有效只读)。
Runnable getRunnable(String name){
String hello = "hello";
return () -> System.out.println(hello+","+name);
}
对于this的引用以及通过this对未限定字段的引用和未限定方法的调用本质上都属于使用final局部变量。包含此类引用的lambda表达式相当于捕获了this实例。在其他情况下,lambda对象不会保留任何对this的应用。
这个特性对内存管理是极好的:要知道在java中一个非静态内部类会默认持有外部类实例的强引用,这往往会造成内存泄露。而在lambda表达式中如果没有捕获外部类成员则不会保留对外部类实例的引用。
不过尽管Java8放宽了对捕获变量的语法限制,但试图修改捕获变量的行为是被禁止的,比如下面这个例子就是非法的:
int sum = 0;
list.forEach(i -> {sum += i;});
为什么要禁止这种行为呢?因为这样的lambda表达式很容易引起race condition
lambda表达式不支持修改捕获变量的另外一个原因是我们可以使用更好的方式来实现同样的效果:使用规约(condition)。java.util.stream包提供了各种规约操作,关于Java8中的Stream API我们放到下一章介绍。
五、方法引用
lambda表达式允许我们定义一个匿名方法,并以函数式接口的方式使用它。Java8能够在已有的方法上实现同样的特性。
方法引用和lambda表达式拥有相同的特性(他们都需要一个目标类型,并且需要被转化为函数式接口的实例),不过我们不需要为方法引用提供方法体,我们可以直接通过方法名引用已有方法。
以下面的代码为例,假设我们要按照userName排序
class User{
private String userName;
public String getUserName() {
return userName;
}
...
}
List<User> users = new ArrayList<>();
Comparator<User> comparator = Comparator.comparing(u -> u.getUserName());
Collections.sort(users, comparator);
我们可以用方法引用替换上面的lambda表达式
Comparator<User> comparator = Comparator.comparing(User::getUserName);
这里的User::getUserName被看做是lambda表达式的简写形式。尽管方法引用不一定会把代码变得更紧凑,但它拥有更明确的语义–如果我们想要调用的方法拥有一个名字,那么我们就可以通过方法名调用它。
方法引用有很多种,它们的语法如下:
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Java8新特性Lambda表达式
# java
# Lambda表达式详解
# java8
# 新特性
# Java8新特性lambda表达式有什么用(用法实例)
# Java8新特性Lambda表达式的一些复杂用法总结
# Java8新特性之Lambda表达式浅析
# Java8中lambda表达式的应用及一些泛型相关知识
# Java8 Lambda表达式详解及实例
# Java8中的 Lambda表达式教程
# Java8与Scala中的Lambda表达式深入讲解
# Java8中的lambda表达式入门教程
# 30分钟入门Java8之lambda表达式学习
# Java8中Lambda表达式的理解与应用
# 是一个
# 类中
# 这一
# 回调
# 子类
# 或者是
# 只有一个
# 会把
# 称之为
# 返回值
# 的是
# 情况下
# 他们的
# 几个
# 就会
# 是在
# 都有
# 在这里
# 是这样
# 已有
相关文章:
Python多线程使用规范_线程安全解析【教程】
如何续费美橙建站之星域名及服务?
网站制作的软件有哪些,制作微信公众号除了秀米还有哪些比较好用的平台?
宝塔新建站点报错如何解决?
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
建站之星后台管理系统如何操作?
建站之星微信建站一键生成小程序+多端营销系统
内部网站制作流程,如何建立公司内部网站?
韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南
活动邀请函制作网站有哪些,活动邀请函文案?
c# 服务器GC和工作站GC的区别和设置
宝塔新建站点为何无法访问?如何排查?
Android滚轮选择时间控件使用详解
建站主机选虚拟主机还是云服务器更好?
零基础网站服务器架设实战:轻量应用与域名解析配置指南
齐河建站公司:营销型网站建设与SEO优化双核驱动策略
如何在服务器上配置二级域名建站?
如何通过智能用户系统一键生成高效建站方案?
如何通过商城免费建站系统源码自定义网站主题?
如何选择建站程序?包含哪些必备功能与类型?
上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?
C++如何编写函数模板?(泛型编程入门)
如何在云主机上快速搭建多站点网站?
整人网站在线制作软件,整蛊网站退不出去必须要打我是白痴才能出去?
重庆网站制作公司哪家好,重庆中考招生办官方网站?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
如何彻底删除建站之星生成的Banner?
如何在腾讯云免费申请建站?
北京网站制作公司哪家好一点,北京租房网站有哪些?
建站之星后台密码遗忘或太弱?如何重置与强化?
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
微信小程序 五星评分(包括半颗星评分)实例代码
存储型VPS适合搭建中小型网站吗?
行程制作网站有哪些,第三方机票电子行程单怎么开?
定制建站流程步骤详解:一站式方案设计与开发指南
个人摄影网站制作流程,摄影爱好者都去什么网站?
小米网站链接制作教程,请问miui新增网页链接调用服务有什么用啊?
教学论文网站制作软件有哪些,写论文用什么软件
?
常州自助建站工具推荐:低成本搭建与模板选择技巧
如何在阿里云部署织梦网站?
如何在云主机上快速搭建网站?
网站制作知乎推荐,想做自己的网站用什么工具比较好?
制作国外网站的软件,国外有哪些比较优质的网站推荐?
如何在云指建站中生成FTP站点?
潮流网站制作头像软件下载,适合母子的网名有哪些?
相册网站制作软件,图片上的网址怎么复制?
免费网站制作模板下载,除了易企秀之外还有什么H5平台可以制作H5长页面,最好是免费的?
如何快速完成中国万网建站详细流程?
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?
邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?
*请认真填写需求信息,我们会在24小时内与您取得联系。