许多热门的编程语言如今都有一个叫做lambda或者闭包的语言特性,包括比较经典的函数式编程语言Lisp,Scheme,也有稍微年轻的语言比如JavaScript,Python,Ruby,Groovy,Scale,C#,甚至C++也有Lambda表达式。一些语言是运行在java虚拟机上,作为虚拟机最具代表的语言java当然也不想落后。
Lambda表达式的概念来自于Lambda演算,下面是一个java lambda的简单例子,
(int x) -> { return x+1; }
简单来看lambda像一个没有名字的方法,它具有一个方法应该有的部分:参数列表int x
,方法body return x+1
,和方法相比lambda好像缺少了一个返回值类型、异常抛出和名字。返回值类型和异常是通过编译器在方法体中推导出来,在上面这个例子中返回值类型是int,没有抛出异常。真正缺少的就是一个名字,从这个角度来看,lambda表达式是一种匿名方法。
从上面的分析可以看出lambda和java内部类的特性有点相似,匿名内部类不只是一个方法,而是一个包含一个或多个方法的类,他们的作用都是一样的,都是作为方法的参数传递,我从JDK源码中提取出来listFiles(FileFilter)
方法:
public File[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}
listFilter
方法接收一个功能接口作为参数,在这里是FileFilter接口:
public interface FileFilter {
boolean accept(File pathname);
}
fileFilter
接收一个File
对象返回一个boolean
值,listFiles
方法把Filter
应用到所有的File
对象接收 那些accept
返回true
的文件。对于listFiles
方法来讲我们必须传递一个函数式接口给他,这是FileFileter
的一个实现,一般我们通过匿名类来完成:
File myDir = new File("/home/user/files");
if (myDir.isDirectory()) {
File[] files = myDir.listFiles(
new FileFilter() {
public boolean accept(File f) { return f.isFile(); }
}
);
}
我们现在可以用lambda来实现:
File myDir = new File("/home/user/files");
if (myDir.isDirectory()) {
File[] files = myDir.listFiles(
(File f) -> { return f.isFile(); }
);
}
这两种情况我们都是传递了一个函数式接口给方法就像传递对象一样,我们使用代码就像使用数据一样,使用匿名类我们实际上传递了一个对象给方法,使用lambda不再需要创建对象,我们只需要把lambda代码传递给方法。
除了传递lambda之外我们还可以传递一个方法引用,比如:
File[] files = myDir.listFiles( File::isFile );
在之前的例子,我们使用lambda表达式定义了一个函数,我们可以把它作为参数传递给一个方法,方法把它当成一个对象来使用,lambda表达式有函数和对象的一些属性,看你从什么角度来看:
listFiles
方法内部,lambda表达式是一个对象的引用,在这里lambda表达式是一种常规的对象,比如有地址和类型。从实际的角度来分析,lambda对象是由编译期和运行时系统来创建的,这就允许编译期进行优化而使用者不需要关心具体细节,编译器从lambda表达式的上下文环境来获取lambda对象的语义类型,但是编译期并不创建那个对象而是直到运行时由虚拟机动态创建,这里说的动态创建是指调用invokedynamic
字节码指令来创建。使用动态创建可以推迟对象的创建到对象第一次被使用时,如果你只是定义了lambda表达式而从未使用,它的类型和对象都不会创建。
整个魔幻之处就在于类型的推导,这个类型称为目标类型,运行时系统动态创建的类型是目标类型的子类型。之前的那个例子我们看到目标类型是FileFilter
,在例子中我们定义了一个lambda表达式把它传递给listFiles方法,然后listFiles方法把它作为FileFilter
子类的一个对象来使用。这里看起来好像有点神奇,我们并没有声明lambda表达式实现了FileFilter
接口,listFiles
方法也没有表明它很愉快的接收了lambda表达式,它只是需要一个FileFilter
的子类的对象,这是如何工作的?
这里面的魔术在于编译期执行了类型推导,编译器根据lambda表达式的上下文来决定需要什么类型的对象,然后编译器观察lambda表达式是否兼容需要的类型。如果Java是一种函数式编程语言的话lambda表达式最自然的类型就是某种函数式类型,用来描述函数的一种特殊类型。函数式类型仅仅描述了函数的签名比如(int,int)->boolean
.但是Java不是函数式编程语言因此没有函数式类型,语言的设计者可以选择添加一种新的类型,由于他们不想给Java的类型系统引入太多的改变,因此他们尝试寻找一种办法来集成lambda表达式到语言中而不需要添加函数式类型。
结果他们使用函数式接口来代替,函数式接口是只有一个方法的接口,这样的接口在JDK里有很多,比如经典的Runnable接口,它只有一个方法void run()
,还有很多其他的,比如Readable,Callable,Iterable,closeable,Flushnable,Formattable,Comparable,Comparator
,或者我们前面提到的FileFilter
接口。函数是接口和lambda表达式奕扬都只有一个方法,语言的设计者决定让编译器把lambda表达式转换成匹配的函数式接口。这种转换通常是自动的。比如我们前面提到的(File f) -> { return f.isFile(); }
,编译器知道listFiles方法的签名,因此我们需要的类型就是FileFilter
,FileFilter
是这样的:
public interface FileFilter { boolean accept(File pathname); }
FileFilter仅仅需要一个方法因此它是函数式接口类型,我们定义的lambda表达式有一个相匹配的签名,接收一个File
对象,返回一个boolean
值,不抛出检查的异常,因此编译器把lambda表达式转换成函数式接口FileFilter
类型。
假如我们有下面两个函数式接口:
public interface FileFilter { boolean accept(File pathname); }
public interface Predicate<T> { boolean test(T t); }
我们的lambda表达式兼容两种函数式接口类型:
FileFilter filter = (File f) -> { return f.isFile(); };
Predicate<File> predicate = (File f) -> { return f.isFile(); };
filter = predicate;//错误,不兼容的类型
当我们试图给两个变量相互赋值时编译器会报错,虽然两个变量都是同一个lambda表达式,原因很简单两个变量是不同的类型。也有可能出现编译器无法判断匹配的函数式接口类型,比如这个例子:
Object ref = (File f) -> { return f.isFile(); };
这个赋值语句的上下文没有提供足够的信息来转换,因此编译器会报错,解决这个问题最简单的方法就是添加一个类型转换:
Object ref = (FileFilter) (File f) -> { return f.isFile(); };
Lambda表达式出现在我们通常需要匿名内部类的地方,在很多场合他们是可以互换的。但是他们还是有几个区别:
匿名类一般这样编写:
File[] fs = myDir.lis tFiles(
new FileFilter() {
public boolean accept(File f) { return f.isFile(); }
}
);
而Lambda表达式有多种形式:
File[] files = myDir.listFiles( (File f) -> {return f.isFile();} );
File[] files = myDir.listFiles( f -> f.isFile() );
F ile[] fil e s = myDir.listFiles( File::isFile );
匿名类相对Lambda表达式来讲多了一些成本,使用匿名类或造成新类型的创建、新类型对象的创建。运行时匿名内需要:
Lambda表达式需要函数式接口的转换和最终的调用,类型推导发生在编译期,不需要运行时消耗,之前提到过,lambda对象的创建是通过字节码指令invokedynamic
来完成的,减少了类型和实例的创建消耗。
匿名类可以访问外部域的final
变量,如下所示:
void method() {
final int cnt = 16;
Runnable r = new Runnable() {
public void run() {
System.out.println("count: " + cnt);
}
};
Thread t = new Thread(r);
t.start();
cnt++;// error: cnt is final
}
对于lambda表达式,cnt变量不需要显式声明为final的,一旦变量在lambda中使用编译期会自动把它当成是final
的变量,换句话说在lambda中使用的外部域变量是隐式final的,
void method() {
int cnt = 16;
Runnable r = () -> { System.out.println("count: " + cnt);
};
Thread t = new Thread(r);
t.start();
cnt++;// error: cnt is implicitly final
}
从java8开始匿名内部类也不需要再显式声明final类,编译器会自动把它当成是final。
匿名内部类是一个类,也就是说它自己引入了一个作用域,你可以在里面定义变量,而lambda表达式没有自己的作用域。
void method() {
int cnt = 16;
Runnable r = new Runnable() {
public void run() { int cnt = 0; // fine
System.out.println("cnt is: " + cnt); }
};
}
lambda表达式:
void method() {
int cnt = 16;
Runnable r = () -> { int cnt = 0; // error: cnt has already been defined
System.out.println("cnt is: " + cnt);
};
}
不同的作用域规则对于this
和super
关键字有不同的效果,在匿名类中this
表示匿名类对象本身的引用,super
表示匿名类的父类。在lambda表达式this
和super
关键字意思和外部域中this
和super
的意思一样,this
一般是包含它的那个对象,super
表示包含它的类的父类。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。