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表达式的理解与应用
# 是一个
# 类中
# 这一
# 回调
# 子类
# 或者是
# 只有一个
# 会把
# 称之为
# 返回值
# 的是
# 情况下
# 他们的
# 几个
# 就会
# 是在
# 都有
# 在这里
# 是这样
# 已有
相关文章:
ppt制作免费网站有哪些,ppt模板免费下载网站?
如何快速搭建高效WAP手机网站?
制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?
建站主机解析:虚拟主机配置与服务器选择指南
广州建站公司哪家好?十大优质服务商推荐
建站之星logo尺寸如何设置最合适?
c# F# 的 MailboxProcessor 和 C# 的 Actor 模型
如何通过宝塔面板实现本地网站访问?
建站之星会员如何解锁更多建站功能?
网站制作壁纸教程视频,电脑壁纸网站?
,柠檬视频怎样兑换vip?
定制建站方案优化指南:企业官网开发与建站费用解析
一键网站制作软件,义乌购一件代发流程?
如何在万网主机上快速搭建网站?
在线制作视频的网站有哪些,电脑如何制作视频短片?
如何快速搭建响应式可视化网站?
东莞市网站制作公司有哪些,东莞找工作用什么网站好?
如何制作算命网站,怎么注册算命网站?
javascript中对象的定义、使用以及对象和原型链操作小结
宿州网站制作公司兴策,安徽省低保查询网站?
建站之星代理如何获取技术支持?
如何基于云服务器快速搭建网站及云盘系统?
网站制作公司排行榜,四大门户网站排名?
怎么将XML数据可视化 D3.js加载XML
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
魔方云NAT建站如何实现端口转发?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
佛山企业网站制作公司有哪些,沟通100网上服务官网?
高性能网站服务器配置指南:安全稳定与高效建站核心方案
如何快速重置建站主机并恢复默认配置?
如何续费美橙建站之星域名及服务?
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
如何用美橙互联一键搭建多站合一网站?
公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?
建站之星代理商如何保障技术支持与售后服务?
清单制作人网站有哪些,近日“兴风作浪的姑奶奶”引起很多人的关注这是什么事情?
详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
如何通过免费商城建站系统源码自定义网站主题与功能?
如何选择高效便捷的WAP商城建站系统?
长春网站建设制作公司,长春的网络公司怎么样主要是能做网站的?
如何通过cPanel快速搭建网站?
微信h5制作网站有哪些,免费微信H5页面制作工具?
已有域名和空间,如何快速搭建网站?
如何选择PHP开源工具快速搭建网站?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
*服务器网站为何频现安全漏洞?
c# 服务器GC和工作站GC的区别和设置
如何快速查询网址的建站时间与历史轨迹?
建站168自助建站系统:快速模板定制与SEO优化指南
济南企业网站制作公司,济南社保单位网上缴费步骤?
*请认真填写需求信息,我们会在24小时内与您取得联系。