ImageVerifierCode 换一换
格式:DOCX , 页数:23 ,大小:32.77KB ,
资源ID:9299839      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/9299839.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Java 8 Lambda表达式.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Java 8 Lambda表达式.docx

1、Java 8 Lambda表达式深入学习Java 8 Lambda(语言篇lambda表达式的深入学习)关于1. 深入理解 Java 8 Lambda(语言篇lambda,方法引用,目标类型和默认方法)2. 深入理解 Java 8 Lambda(类库篇Streams API,Collector 和并行)3. 深入理解 Java 8 Lambda(原理篇Java 编译器如何处理 lambda)本文是深入理解 Java 8 Lambda 系列的第一篇,主要介绍 Java 8 新增的语言特性(比如 lambda 和方法引用),语言概念(比如目标类型和变量捕获)以及设计思路。本文是对Brian Goe

2、tz的State of Lambda一文的翻译,那么问题来了:为什么要翻译这个系列?1. 工作之后,我开始大量使用 Java2. 公司将会在不久的未来使用 Java 83. 作为资质平庸的开发者,我需要打一点提前量,以免到时拙计4. 为了学习Java 8(主要是其中的 lambda 及相关库),我先后阅读了Oracle的官方文档,Cay Horstmann(Core Java的作者)的Java 8 for the Really Impatient和Richard Warburton的Java 8 Lambdas5. 但我感到并没有多大收获,Oracle的官方文档涉及了 lambda 表达式的每

3、一个概念,但都是点到辄止;后两本书(尤其是Java 8 Lambdas)花了大量篇幅介绍 Java lambda 及其类库,但实质内容不多,读完了还是没有对Java lambda产生一个清晰的认识6. 关键在于这些文章和书都没有解决我对Java lambda的困惑,比如: Java 8 中的 lambda 为什么要设计成这样?(为什么要一个 lambda 对应一个接口?而不是 Structural Typing?) lambda 和匿名类型的关系是什么?lambda 是匿名对象的语法糖吗? Java 8 是如何对 lambda 进行类型推导的?它的类型推导做到了什么程度? Java 8 为什么

4、要引入默认方法? Java 编译器如何处理 lambda? 等等7. 之后我在 Google 搜索这些问题,然后就找到Brian Goetz的三篇关于Java lambda的文章(State of Lambda,State of Lambda libraries version和Translation of lambda),读完之后上面的问题都得到了解决8. 为了加深理解,我决定翻译这一系列文章警告(Caveats)如果你不知道什么是函数式编程,或者不了解map,filter,reduce这些常用的高阶函数,那么你不适合阅读本文,请先学习函数式编程基础(比如这本书)。State of Lamb

5、dabyBrian GoetzThe high-level goal of Project Lambda is to enable programming patterns that require modeling code as data to be convenient and idiomatic in Java.关于本文介绍了 Java SE 8 中新引入的 lambda 语言特性以及这些特性背后的设计思想。这些特性包括:o lambda 表达式(又被成为“闭包”或“匿名方法”)o 方法引用和构造方法引用o 扩展的目标类型和类型推导o 接口中的默认方法和静态方法1. 背景Java 是一

6、门面向对象编程语言。面向对象编程语言和函数式编程语言中的基本元素(Basic Values)都可以动态封装程序行为:面向对象编程语言使用带有方法的对象封装行为,函数式编程语言使用函数封装行为。但这个相同点并不明显,因为Java 对象往往比较“重量级”:实例化一个类型往往会涉及不同的类,并需要初始化类里的字段和方法。不过有些 Java 对象只是对单个函数的封装。例如下面这个典型用例:Java API 中定义了一个接口(一般被称为回调接口),用户通过提供这个接口的实例来传入指定行为,例如:123publicinterfaceActionListener voidactionPerformed(Ac

7、tionEvent e);这里并不需要专门定义一个类来实现ActionListener,因为它只会在调用处被使用一次。用户一般会使用匿名类型把行为内联(inline):12345button.addActionListener(new ActionListener() publicvoidactionPerformed(ActionEvent e) ui.dazzle(e.getModifiers(); );很多库都依赖于上面的模式。对于并行 API 更是如此,因为我们需要把待执行的代码提供给并行 API,并行编程是一个非常值得研究的领域,因为在这里摩尔定律得到了重生:尽管我们没有更快的 CP

8、U 核心(core),但是我们有更多的 CPU 核心。而串行 API 就只能使用有限的计算能力。随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as data)的方法。匿名内部类并不是一个好的选择,因为:1. 语法过于冗余2. 匿名类中的this和变量名容易使人产生误解3. 类型载入和实例创建语义不够灵活4. 无法捕获非final的局部变量5. 无法对控制流进行抽象上面的多数问题均在Java SE 8中得以解决:o 通过提供更简洁的语法和局部作用域规则,Java SE 8 彻底解决了问题 1 和问题 2o 通过提供更加灵

9、活而且便于优化的表达式语义,Java SE 8 绕开了问题 3o 通过允许编译器推断变量的“常量性”(finality),Java SE 8 减轻了问题 4 带来的困扰不过,Java SE 8 的目标并非解决所有上述问题。因此捕获可变变量(问题 4)和非局部控制流(问题 5)并不在 Java SE 8的范畴之内。(尽管我们可能会在未来提供对这些特性的支持)2. 函数式接口(Functional interfaces)尽管匿名内部类有着种种限制和问题,但是它有一个良好的特性,它和Java类型系统结合的十分紧密:每一个函数对象都对应一个接口类型。之所以说这个特性是良好的,是因为:o 接口是 Jav

10、a 类型系统的一部分o 接口天然就拥有其运行时表示(Runtime representation)o 接口可以通过 Javadoc 注释来表达一些非正式的协定(contract),例如,通过注释说明该操作应可交换(commutative)上面提到的ActionListener接口只有一个方法,大多数回调接口都拥有这个特征:比如Runnable接口和Comparator接口。我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即单抽象方法类型(Single Abstract Method)我们并不需要额外的工作来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断

11、过程并非简单的对接口方法计数:一个接口可能冗余的定义了一个Object已经提供的方法,比如toString(),或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API作者们可以通过FunctionalInterface注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。实现函数式类型的另一种方式是引入一个全新的结构化函数类型,我们也称其为“箭头”类型。例如,一个接收String和Object并返回int的函数类型可以被表示为(String, Object) - int。我们仔细考虑

12、了这个方式,但出于下面的原因,最终将其否定:o 它会为Java类型系统引入额外的复杂度,并带来结构类型(Structural Type)和指名类型(Nominal Type)的混用。(Java 几乎全部使用指名类型)o 它会导致类库风格的分歧一些类库会继续使用回调接口,而另一些类库会使用结构化函数类型o 它的语法会变得十分笨拙,尤其在包含受检异常(checked exception)之后o 每个函数类型很难拥有其运行时表示,这意味着开发者会受到类型擦除(erasure)的困扰和局限。比如说,我们无法对方法m(T-U)和m(X-Y)进行重载(Overload)所以我们选择了“使用已知类型”这条路

13、因为现有的类库大量使用了函数式接口,通过沿用这种模式,我们使得现有类库能够直接使用 lambda 表达式。例如下面是 Java SE 7 中已经存在的函数式接口:o java.lang.Runnableo java.util.concurrent.Callableo java.security.PrivilegedActiono java.util.Comparatoro java.io.FileFiltero java.beans.PropertyChangeListener除此之外,Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例

14、如:o Predicate接收T并返回booleano Consumer接收T,不返回值o Function接收T,返回Ro Supplier提供T对象(例如工厂),不接收值o UnaryOperator接收T对象,返回To BinaryOperator接收两个T,返回T除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如IntSupplier和LongBinaryOperator。(我们只为int、long和double提供了特化函数式接口,如果需要使用其它原始类型则需要进行类型转换)同样的我们也提

15、供了一些针对多个参数的函数式接口,例如BiFunction,它接收T对象和U对象,返回R对象。3. lambda表达式(lambda expressions)匿名类型最大的问题就在于其冗余的语法。有人戏称匿名类型导致了“高度问题”(height problem):比如前面ActionListener的例子里的五行代码中仅有一行在做实际工作。lambda表达式是匿名方法,它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。下面是一些lambda表达式:123(int x, int y) - x + y() -42(String s) - System.out.println(s); 第

16、一个 lambda 表达式接收x和y这两个整形参数并返回它们的和;第二个 lambda 表达式不接收参数,返回整数 42;第三个 lambda 表达式接收一个字符串并把它打印到控制台,不返回值。lambda 表达式的语法由参数列表、箭头符号-和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:o 表达式:表达式会被执行然后返回执行结果。o 语句块:语句块中的语句会被依次执行,就像方法中的语句一样 return语句会把控制权交给匿名方法的调用者 break和continue只能在循环中使用 如果函数体有返回值,那么函数体内部的每一条路径都必须返回值表达式函数体适合小型 lambda 表达

17、式,它消除了return关键字,使得语法更加简洁。lambda 表达式也会经常出现在嵌套环境中,比如说作为方法的参数。为了使 lambda 表达式在这些场景下尽可能简洁,我们去除了不必要的分隔符。不过在某些情况下我们也可以把它分为多行,然后用括号包起来,就像其它普通表达式一样。下面是一些出现在语句中的 lambda 表达式:12345678FileFilter java = (File f) - f.getName().endsWith(*.java);String user = doPrivileged() - System.getProperty(user.name);new Thread

18、() - connectToService(); sendNotification();).start();4. 目标类型(Target typing)需要注意的是,函数式接口的名称并不是 lambda 表达式的一部分。那么问题来了,对于给定的 lambda 表达式,它的类型是什么?答案是:它的类型是由其上下文推导而来。例如,下面代码中的 lambda 表达式类型是ActionListener:1ActionListener l = (ActionEvent e) - ui.dazzle(e.getModifiers();这就意味着同样的 lambda 表达式在不同上下文里可以拥有不同的类型:

19、123Callable c = () -done;PrivilegedAction a = () -done;第一个 lambda 表达式() -done是Callable的实例,而第二个 lambda 表达式则是PrivilegedAction的实例。编译器负责推导 lambda 表达式类型。它利用 lambda 表达式所在上下文所期待的类型进行推导,这个被期待的类型被称为目标类型。lambda 表达式只能出现在目标类型为函数式接口的上下文中。当然,lambda 表达式对目标类型也是有要求的。编译器会检查 lambda 表达式的类型和目标类型的方法签名(method signature)是否

20、一致。当且仅当下面所有条件均满足时,lambda 表达式才可以被赋给目标类型T:o T是一个函数式接口o lambda 表达式的参数和T的方法参数在数量和类型上一一对应o lambda 表达式的返回值和T的方法返回值相兼容(Compatible)o lambda 表达式内所抛出的异常和T的方法throws类型相兼容由于目标类型(函数式接口)已经“知道” lambda 表达式的形式参数(Formal parameter)类型,所以我们没有必要把已知类型再重复一遍。也就是说,lambda 表达式的参数类型可以从目标类型中得出:1Comparator c = (s1, s2) - pareToIgn

21、oreCase(s2);在上面的例子里,编译器可以推导出s1和s2的类型是String。此外,当 lambda 的参数只有一个而且它的类型可以被推导得知时,该参数列表外面的括号可以被省略:123FileFilter java = f - f.getName().endsWith(.java);button.addActionListener(e - ui.dazzle(e.getModifiers();这些改进进一步展示了我们的设计目标:“不要把高度问题转化成宽度问题。”我们希望语法元素能够尽可能的少,以便代码的读者能够直达 lambda 表达式的核心部分。lambda 表达式并不是第一个拥有

22、上下文相关类型的 Java 表达式:泛型方法调用和“菱形”构造器调用也通过目标类型来进行类型推导:12345List ls = Collections.emptyList();List li = Collections.emptyList();Map m1 = new HashMap();Map m2 = new HashMap();5. 目标类型的上下文(Contexts for target typing)之前我们提到 lambda 表达式智能出现在拥有目标类型的上下文中。下面给出了这些带有目标类型的上下文:o 变量声明o 赋值o 返回语句o 数组初始化器o 方法和构造方法的参数o lam

23、bda 表达式函数体o 条件表达式(? :)o 转型(Cast)表达式在前三个上下文(变量声明、赋值和返回语句)里,目标类型即是被赋值或被返回的类型:12345678Comparator c;c = (String s1, String s2) - pareToIgnoreCase(s2);public Runnable toDoLater()return () - System.out.println(later); 数组初始化器和赋值类似,只是这里的“变量”变成了数组元素,而类型是从数组类型中推导得知:1234filterFiles(new FileFilter f - f.exists(

24、), f - f.canRead(), f - f.getName().startsWith(q) );方法参数的类型推导要相对复杂些:目标类型的确认会涉及到其它两个语言特性:重载解析(Overload resolution)和参数类型推导(Type argument inference)。重载解析会为一个给定的方法调用(method invocation)寻找最合适的方法声明(method declaration)。由于不同的声明具有不同的签名,当 lambda 表达式作为方法参数时,重载解析就会影响到 lambda 表达式的目标类型。编译器会通过它所得之的信息来做出决定。如果 lambda

25、 表达式具有显式类型(参数类型被显式指定),编译器就可以直接 使用lambda 表达式的返回类型;如果lambda表达式具有隐式类型(参数类型被推导而知),重载解析则会忽略 lambda 表达式函数体而只依赖 lambda 表达式参数的数量。如果在解析方法声明时存在二义性(ambiguous),我们就需要利用转型(cast)或显式 lambda 表达式来提供更多的类型信息。如果 lambda 表达式的返回类型依赖于其参数的类型,那么 lambda 表达式函数体有可能可以给编译器提供额外的信息,以便其推导参数类型。12List ps = .Stream names = ps.stream().m

26、ap(p - p.getName();在上面的代码中,ps的类型是List,所以ps.stream()的返回类型是Stream。map()方法接收一个类型为Function的函数式接口,这里T的类型即是Stream元素的类型,也就是Person,而R的类型未知。由于在重载解析之后 lambda 表达式的目标类型仍然未知,我们就需要推导R的类型:通过对 lambda 表达式函数体进行类型检查,我们发现函数体返回String,因此R的类型是String,因而map()返回Stream。绝大多数情况下编译器都能解析出正确的类型,但如果碰到无法解析的情况,我们则需要:o 使用显式 lambda 表达式

27、(为参数p提供显式类型)以提供额外的类型信息o 把 lambda 表达式转型为Functiono 为泛型参数R提供一个实际类型。(.map(p - p.getName())lambda 表达式本身也可以为它自己的函数体提供目标类型,也就是说 lambda 表达式可以通过外部目标类型推导出其内部的返回类型,这意味着我们可以方便的编写一个返回函数的函数:1Supplier c = () - () - System.out.println(hi); ;类似的,条件表达式可以把目标类型“分发”给其子表达式:1Callable c = flag ? () -23) : () -42);最后,转型表达式(

28、Cast expression)可以显式提供 lambda 表达式的类型,这个特性在无法确认目标类型时非常有用:12/ Object o = () - System.out.println(hi); ; 这段代码是非法的Object o = (Runnable) () - System.out.println(hi); ;除此之外,当重载的方法都拥有函数式接口时,转型可以帮助解决重载解析时出现的二义性。目标类型这个概念不仅仅适用于 lambda 表达式,泛型方法调用和“菱形”构造方法调用也可以从目标类型中受益,下面的代码在 Java SE 7 是非法的,但在 Java SE 8 中是合法的:1

29、23List ls = Collections.checkedList(new ArrayList(), String.class);Set si = flag ? Collections.singleton(23) : Collections.emptySet();6. 词法作用域(Lexical scoping)在内部类中使用变量名(以及this)非常容易出错。内部类中通过继承得到的成员(包括来自Object的方法)可能会把外部类的成员掩盖(shadow),此外未限定(unqualified)的this引用会指向内部类自己而非外部类。相对于内部类,lambda 表达式的语义就十分简单:它不会从超类(supertype)中继承任何变量名,也不会引入一个新的作用域。lambda 表达式基于词法作用域,也就是说 lambda 表达式函数体里面的变量和它外部环境的变量具有相同的语义(也包括 lambda 表达式的形式参数)。此外,this 关键字及其引用在 lambda 表达式内部和外部也拥有相同的语义。为了进一步说明词法作用域的优点,请参考下面的代码,它会把Hello, world!打印两遍:123456789101

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1