java1.8 lambda表达式_java1.8之Lambda表达式

行为参数化

为了应对多变的需求,难道我们就要因为客户每提出一个需求,我们就要写一个方法去实现吗?

显然这样做很冗余,而且维护性大大降低,这说明代码的设计不够好。好在已经有前人帮我们提出了行为参数化思想(即将一段代码逻辑作为参数,使之可以在不同对象间传递)。

java1.8以前使用匿名类来实现行为参数化,即使用匿名类去实现一个函数式接口中的方法。java1.8之后,推出了Lambda表达式来替代以前匿名类实现行为参数化的繁复过程,使代码更简洁、更优雅。

Lambda初体验

先从简单的例子开始:创建一个thread,需要在Thread()构造方法中传入一个Runnable接口的实现类对象,但一般不会为了这个实现类对象去创建一个实现类,java1.8之前更简洁的更便于维护的方式是在构造方法中创建一个实现了Runnable接口的匿名类对象,只使用一次,代码如下:

new Thread(new Runnable() {

@Override

public void run() {

System.out.println("使用匿名类实现Runnable接口,实现功能需要6行代码");

}

}).start();

可以看到,通过匿名类实现Runnable接口,需要编写6行代码,但其实真正实现了我们需要的功能的代码只有一行(黑色加粗),从代码量上来看,这就显得很冗余,“高度问题(height problem)”。

java1.8发布的新特性,lambda表达式,就可以很好的解决这个问题,下面的代码等价上面的代码:

new Thread(() -> {System.out.println("使用Lambda表达式,只需要一行代码");}).start();

注意上面代码中的红色字体部分,这就是Lambda表达式的一个简单演示,lambda表达式充当了这个接口中的抽象方法的具体实现。

Lambda表达式的语法结构

下面我们就来看一下lambda表达式的几种使用语法:

(params) -> expression

(params) -> statement

(params) -> { statement; }

左边第一个括号中的params参数列表根据需要增加;中间是一个箭头,英文半角的-与大于号>组成,这两个符号之间不能有空格,箭头两边可以有空格;箭头的右边是表达式或者语句块。如果是类似“return a+b”这种结构的方法体,可以直接写成(int a, int b) -> a+b ,expresion能够返回该表达式的结果,可以看到lambda表达式把return这种方法退出语句都简化省略掉了。如果只是想通过控制台输出语句打印一段话,可以写成() -> System.out.println("Hello") 语句末尾的分号都可以省略不写。如果是实现方法的逻辑比较复杂,就可以用花括号将一段逻辑代码括起来,比如 () -> { 语句块 }

函数式接口

在进一步说明lambda表达式之前,先做一个知识储备,什么是函数式接口?

只拥有一个方法的接口,称为函数式接口。在以前的版本中,人们常称这种类型为SAM类型,即单抽象方法类型(SAM,Single Abstract Method)

java1.8之后,设计者们对JDK做了全面的改动,为符合函数式接口规范的接口,都加上了@FunctionalInterface注解,通知编译器这些接口是符合函数式接口的规范,虽然可能有的接口中有多个方法,但是方法的签名可以各有不同。

好像还是不太明白?我们找几个JDK的例子来看看,比如:

(1)Callable接口

@FunctionalInterface

public interface Callable {

V call() throws Exception;

}

(2)Runnable接口

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

(3)java.util.Comparator接口

@FunctionalInterface

public interface Comparator {

int compare(T o1, T o2);

boolean equals(Object obj);

// java1.8之后还增加了一些default方法,这里就不列出

}

可以发现,Callable和Runnable这两个接口的共性,接口中都只声明了一个方法。符合这种结构规范的interface,java中就称之为函数式接口。而在(3)Comparator接口中有两个方法,为什么呢?因为boolean equals(Object obj)是Object类的public方法,函数式接口中允许定义Object的public方法,像clone()方法就不能定义因为是protected方法,加上了@FunctionalInterface注解告诉编译器,这个接口必须符合函数式接口规范的,如果不符合就会编译报凑。

Lambda表达式的结果类型,目标类型(Target Typing)

在初体验的例子中,好像lambda表达式没有结果值类型,但不代表lambda就没有结果类型,只是我们不需要指定lambda表达式的结果类型。

那lambda表达式的结果类型是什么呢?答案是:它的类型是由其上下文推导而来。也就是说,同一段lambda表达式在不同的上下文环境中,可能会有不同的结果类型,比如:

Callable c =() -> "done.";

PrivilegedAction p =() -> "done.";

虽然c和p等号右边的lambda表达式一样,但是两个lambda表达式的结果却不一样,第一个是Callable类型,第二个是PrivilegedAction类型。

由编译器完成对Lambda表达式的结果类型推导,编译器根据Lambda表达式的上下文推导出一个预期的类型,这个预期的类型就是目标类型。lambda表达式对目标类型也有要求,编译器会检查lambda表达式的推导类型和目标类型的方法签名是否一致。需要满足下列全部条件,lambda表达式才可以被赋给目标类型T:

·T 是一个函数式接口

·lambda表达式的参数与 T 中的方法的形参列表在数量、类型上完全一致

·lambda表达式的返回值与 T 中的方法的返回值相兼容,lambda表达式的返回值类型应该是 T 的实现类或子类

·lambda表达式内所抛出的异常与 T 中的方法throws的异常类型相兼容,同上一条

我个人对目标类型的理解:

目标类型不同于返回值类型,它是对要实现的方法所属的函数式接口的一种参考,待实现方法有返回值类型,也有其所属的接口或类,而这个方法所属的接口或类,就是目标类型。

java设计者要求,lambda表达式只能出现在目标类型为函数式接口的上下文中。

代码高度降低了,宽度呢?

lambda表达式将多行代码浓缩到一行,是解决了“高度问题”,但是过多的信息在一行表述,显然会增加lambda表达式一行的代码量,这就产生了“宽度问题”,java设计者在设计lambda表达式时考虑到这一点,做了优化的设计:

(1)省略形参类型

由于目标类型(函数式接口)已经“知道”lambda表达式的形式参数(Formal parameter)类型,所以没有必要把已知类型再重复写一遍。也就是说,lambda表达式的参数类型可以从目标类型中得出。

举个例子:

Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);

其中s1和s2我们虽然没有明确指定其参数类型,但是编译器可以通过上下文推导出其形参类型,Comparator接口中有两个方法,int compare(T o1, T o2)、boolean equals(Object obj),根据lambda表达式的参数列表(2个形参),可以推导出要实现的接口方法是compare(T o1, T o2),又根据目标类型Comparator指定了就是,就可以推导出s1和s2的参数类型就是String。

(2)当lambda参数只有一个且其类型可以被推导出时,参数列表的()括号也可以省略

举个例子:

FileFilter java = f -> f.getName().endsWith(".java");

java.io.FileFilter接口中仅有一个方法,boolean accept(File pathname),可以推导出该lambda表达式的参数列表应该是File类型,也就是说参数f的类型也可以省略了,而且只有这一个参数,那么括号()也可以省略了。

上下文

上面提到很多次lambda表达式只能出现拥有目标类型的上下文中,下面列出带有目标类型的上下文:

·变量声明

·赋值

·返回语句

·数组初始化器

·方法和构造方法的参数

·lambda表达式函数体

·条件表达式(? :)

·转型(Cast)表达式

方法引用

通过上面的例子和说明,我们知道了lambda表达式允许我们自定义一个匿名方法((params) -> {...} 这看起来就像是一个没有名字的方法定义),并能以函数式接口的方式使用这个匿名方法。那现在我们也可以不用自定义方法,直接引用已有的方法也是可以的,这种引用我们称之为方法引用。

方法引用和lambda表达式拥有相同的特性(例如,都需要一个目标类型,并且需要被转换为函数式接口的实例),只不过不需要为已有方法提供方法体,我们直接通过该方法的名字就可以引用这个已有方法。

举个例子:

class Person {

private final String name;

public String getName(){

return this.name;

}

....

}

Person[] people = ...

Comparator byName = Comparator.comparing(p - > p.getName());

Arrays.sort(people, byName);

----------------------------------------

加粗部分可以用方法引用lambda表达式来替代:

Comparator byName = Comparator.coparint(Person::getName);

是不是看起来表义就更清晰了呢?方法引用Person::getName就可以看作是lambda表达式p -> p.getName()的一种简写形式,虽然看起来好像代码量没有减少多少,但是拥有了更明确的语义——如果我们想调用的方法拥有一个名字,那我们就直接用这个名字来调用它吧。

方法引用的种类

下面列出方法引用的几种语法:

·静态方法引用ClassName::staticMethodName

·实例中的实例方法引用instanceReferenceName::methodName

·父类上的实例方法引用super::methodName

·本类上的实例方法引用ClassName::methodName

·构造方法引用Class::new

·数组构造方法引用TypeName[]::new

在类型和方法名之间,加上分隔符“::”

用一个例子融会贯通

首先看实例代码:

List people = ... Collections.sort(people,newComparator() {publicintcompare(Person x, Person y) {returnx.getLastName().compareTo(y.getLastName()); } });

看了lambda表达式的用法之后,是不是感觉冗余代码太多呢?

我们先用lambda表达式去掉冗余的匿名类,精简成一行代码:

Collections.sort(people,

(Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

现在看起来代码是精简了很多,但是感觉抽象程度还比较差,开发人员仍然需要进行实际的比较操作,我们可以借助java.util.Comparator接口中静态方法comparing() (这也是Java1.8新增的):

Collections.sort(people,

Comparator.comparing((Person p) -> p.getLastName()));

编译器可以帮助我们做类型推导,同时还可以借助静态导入,进一步精简:

Collections.sort(people,comparing(p-> p.getLastName()));

现在看起来,就发现可以将lambda表达式用方法引用来替换:

Collections.sort(people, comparing(Person::getLastName));

使用Collections.sort()的辅助方法也不太妥当,它使代码冗余,也无法针对List接口的数据结构提供特定的高效实现,而且因为Collections.sort()方法不属于List接口,用户在阅读List接口文档的时候可能不会意识到Collections类中有提供一个针对List接口的排序方法sort(),这里可以做一步优化,我们可以为List接口添加一个default方法sort(),然后直接通过List对象调用该sort()方法:

people.sort(comparing(Person::getLastName));

这样即方便调用,也方便代码的阅读和后期维护。将最终结果对比一开始的匿名类的实现方法,是不是要更简短,但语义却更清晰了呢?这就是lambda表达式的好处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305815.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ABP vNext 自动注入,暗藏天坑如斯

导言我们在使用ABP vNext框架时,都知道该框架为我们实现了自动依赖注入(实现自动注入需要在项目里面创建Module类,并且将Module类上的DependsOn到相应的启动Module类或调用Module类,这步很关键)自动注入示例只要我们实现如下接口:…

Community Server系列之四:Ajax在CS2.0中的应用1

Ajax技术在时下很热门,当然在CS2.0中也不例外的运用到了此技术,CS2中没有使用任何第三方Ajax控件,这就给我们提供一个研究Ajax机理的好地方,现介绍一下在CS2中Ajax的一些简单应用,并分析应用的原理。 我想要研究此技术…

为什么人和人的差距这么大?

点击蓝字关注,回复“职场进阶”获取职场进阶精品资料一份这是一篇去年写的旧文,不少读者从这篇文章中获得了一些方法和力量,于是再分享下:工作和生活中不光要埋头干活,还要抬头看天。思考总结方法论是提升认知的必备途…

github可以传java吗_如何在github上传本地项目代码(新手使用)----亲测使用

首先你要在github上申请一个账号然后你要下载一个git工具进入官网直接下载就行,下载完成后进入github首页,点击新项目new repository(新建),如下图所示:然后进入如下页面,主要填写红色圈起来的几个部分,如下…

英才评测 个人性格倾向 IT知识

网址http://www.01hr.com/testcenter/index_ccw.jsp?id9203&clid1135238483687 转载于:https://www.cnblogs.com/sutengcn/archive/2006/05/06/392790.html

【One by One系列】IdentityServer4(一)OAuth2.0与OpenID Connect 1.0

在微服务场景中,身份认证通常是集中处理,这也是有别于单体应用一把梭哈的模式,其中,在微软微服务白皮书中,提供了两种身份认证模式:网关,没错,原话是If youre using an API Gateway,…

php多进程 写入文件_PHP多进程中使用file_put_contents安全吗?

TL;DRLinux下,PHP多进程使用 file_put_contents() 方法记录日志时,使用追加模式(FILE_APPEND),简短的日志内容不会重叠,即能安全的记录日志内容。file_put_contents() 使用 write() 系统调用实现数据的写入,write() 系…

虚拟机概述[转贴]

[摘要]描述什么是虚拟机、及运行在各种平台上的多种多样的虚拟机软件PXE2写著 你一定接触过各种各样的虚拟机,Vmware,VirtualPC,甚至JVM或是VBRunDLL,等等等等,在数字的信息世界里虚拟和现实是如此的难解难分。你一定接…

ABP vNext分布式事件总线RabbitMQ注意事项

[https://docs.abp.io/zh-Hans/abp/latest/Distributed-Event-Bus-RabbitMQ-Integration](ABP vNext官方文档链接),基本使用可直接阅读官方文档,云怀不重复造轮子,只做官方未提到但重要的说明关键配置说明关键配置类:AbpRabbitMqE…

window运行php环境,Windows环境下使用phpstudy搭建php运行环境

首先在百度百科上对于phpstudy的定义是一个PHP调试环境的程序集成包。该程序包集成最新的ApachePHPMySQLphpMyAdminZendOptimizer,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境,该程序不仅包括PHP调试环境,还包括了…

去除代码行号的一个小程序(控制台版本)

清风竹林发布了去除代码行号的一个小程序,确实方便大家收集一些文章代码,但个人认为象这样的小东东,要使广大网友能拿来就用,用.Net 2.0做成WinForm,有点贵族化了,于是动手整出个平民化的控制台版本,可以清除指定的文本…

. NET5实战千万高并发项目,性能吊打JAVA,C#排名万年老五,有望逆袭!

“秒杀活动”“抢红包”“微博热搜”“12306抢票”“共享单车拉新”等都是高并发的典型业务场景,那么如何解决这些业务场景背后的难点问题呢?秒杀系统中,QPS达到10万/s时,如何定位并解决业务瓶颈?明星婚恋话题不断引爆…

java不朽神迹,不朽的神迹 Eternal Legacy HD v1.0.8

游戏简介不朽的神迹是一个拥有全3D实时渲染的史诗战斗场面、360度自由调整的视角及丰富的动作特效的游戏。玩家将在游戏中探索壮丽的大陆,体验张力十足的战斗系统。游戏提供了多至3名角色同时参战,可从队伍成员中选择出战的队友,并且定义他们…

ABP vNext 审计日志获取真实客户端IP

背景在使用ABP vNext时,当需要记录审计日志时,我们按照https://docs.abp.io/zh-Hans/abp/latest/Audit-Logging配置即可开箱即用,然而在实际生产环境中,某些配置并不可取,比如今天的主角——客户端IP,记录用…

郭昶

郭 昶左直拳饰演《外来媳妇本地郎》中康家老二康祁宗的演员郭昶6月14日去世了,胃癌,享年50岁。这个消息真令人难以置信,不胜嘘唏。 《外来媳妇本地郎》在广东这边很受欢迎,每集结尾那带有浓厚岭南特色的粤曲小调在胡同小巷时有…

php 常用rpc框架,php的轻量级rpc框架yar

php的轻量级rpc框架yar目的:类方法的远程调用,也就是一个rpc请求。RPC本质上也是一个网络请求,既然是请求,对于效率来说,就需要考虑了。yar是基于http来做的。使用场景:多个项目共享model总的来说这种调用代价挺好的&a…

ABP vNext IOC替换原有Service实现

即 .NET IOC替换原有Service实现背景在使用ABP vNext时,该框架为我们实现了非常多的默认行为,以便开箱即用,但在实际使用中,我们总是需要根据自己的需求定制自己的服务,在.Net框架中,便提供了Service.Repla…

aqs java 简书,Java AQS源码解读

1、先聊点别的说实话,关于AQS的设计理念、实现、使用,我有打算写过一篇技术文章,但是在写完初稿后,发现掌握的还是模模糊糊的,模棱两可。痛定思痛,脚踏实地重新再来一遍。这次以 Java 8源码为基础进行解读。…

仓储模式到底是不是反模式?

【导读】仓储模式我们已耳熟能详,但当我们将其进行应用时,真的是那么得心应手吗?确定是解放了生产力吗?这到底是怎样的一个存在,确定不是反模式?一篇详文我们探讨仓储模式,这里仅我个人的思考&a…

网络工程师必须懂的十五大专业术语!

1、什么时候使用多路由协议? 当两种不同的路由协议要交换路由信息时,就要用到多路由协议。当然,路由再分配也可以交换路由信息。下列情况不必使用多路由协议: 从老版本的内部网关协议( Interior Gateway Protocol&…