深入学习Java8Lambda(语言篇——lambda表达式的深入学习)关于1.深入理解Java8Lambda(语言篇——lambda,方法引用,目标类型和默认方法)2.深入理解Java8Lambda(类库篇——StreamsAPI,Collector和并行)3.深入理解Java8Lambda(原理篇——Java编译器如何处理lambda)本文是深入理解Java8Lambda系列的第一篇,主要介绍Java8新增的语言特性(比如lambda和方法引用),语言概念(比如目标类型和变量捕获)以及设计思路。本文是对BrianGoetz的StateofLambda一文的翻译,那么问题来了:为什么要翻译这个系列?1.工作之后,我开始大量使用Java2.公司将会在不久的未来使用Java83.作为资质平庸的开发者,我需要打一点提前量,以免到时拙计4.为了学习Java8(主要是其中的lambda及相关库),我先后阅读了Oracle的官方文档,CayHorstmann(CoreJava的作者)的Java8fortheReallyImpatient和RichardWarburton的Java8Lambdas5.但我感到并没有多大收获,Oracle的官方文档涉及了lambda表达式的每一个概念,但都是点到辄止;后两本书(尤其是Java8Lambdas)花了大量篇幅介绍Javalambda及其类库,但实质内容不多,读完了还是没有对Javalambda产生一个清晰的认识6.关键在于这些文章和书都没有解决我对Javalambda的困惑,比如:Java8中的lambda为什么要设计成这样?(为什么要一个lambda对应一个接口?而不是StructuralTyping?)lambda和匿名类型的关系是什么?lambda是匿名对象的语法糖吗?Java8是如何对lambda进行类型推导的?它的类型推导做到了什么程度?Java8为什么要引入默认方法?Java编译器如何处理lambda?等等……7.之后我在Google搜索这些问题,然后就找到BrianGoetz的三篇关于Javalambda的文章(StateofLambda,StateofLambdalibrariesversion和Translationoflambda),读完之后上面的问题都得到了解决8.为了加深理解,我决定翻译这一系列文章警告(Caveats)如果你不知道什么是函数式编程,或者不了解map,filter,reduce这些常用的高阶函数,那么你不适合阅读本文,请先学习函数式编程基础(比如这本书)。StateofLambdabyBrianGoetzThehigh-levelgoalofProjectLambdaistoenableprogrammingpatternsthatrequiremodelingcodeasdatatobeconvenientandidiomaticinJava.关于本文介绍了JavaSE8中新引入的lambda语言特性以及这些特性背后的设计思想。这些特性包括:olambda表达式(又被成为“闭包”或“匿名方法”)o方法引用和构造方法引用o扩展的目标类型和类型推导o接口中的默认方法和静态方法1.背景Java是一门面向对象编程语言。面向对象编程语言和函数式编程语言中的基本元素(BasicValues)都可以动态封装程序行为:面向对象编程语言使用带有方法的对象封装行为,函数式编程语言使用函数封装行为。但这个相同点并不明显,因为Java对象往往比较“重量级”:实例化一个类型往往会涉及不同的类,并需要初始化类里的字段和方法。不过有些Java对象只是对单个函数的封装。例如下面这个典型用例:JavaAPI中定义了一个接口(一般被称为回调接口),用户通过提供这个接口的实例来传入指定行为,例如:123publicinterfaceActionListener{voidactionPerformed(ActionEvente);}这里并不需要专门定义一个类来实现ActionListener,因为它只会在调用处被使用一次。用户一般会使用匿名类型把行为内联(inline):12345button.addActionListener(newActionListener(){publicvoidactionPerformed(ActionEvente){ui.dazzle(e.getModifiers());}});很多库都依赖于上面的模式。对于并行API更是如此,因为我们需要把待执行的代码提供给并行API,并行编程是一个非常值得研究的领域,因为在这里摩尔定律得到了重生:尽管我们没有更快的CPU核心(core),但是我们有更多的CPU核心。而串行API就只能使用有限的计算能力。随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Modelcodeasdata)的方法。匿名内部类并不是一个好的选择,因为:1.语法过于冗余2.匿名类中的this和变量名容易使人产生误解3.类型载入和实例创建语义不够灵活4.无法捕获非final的局部变量5.无法对控制流进行抽象上面的多数问题均在JavaSE8中得以解决:o通过提供更简洁的语法和局部作用域规则,JavaSE8彻底解决了问题1和问题2o通过提供更加灵活而且便于优化的表达式语义,JavaSE8绕开了问题3o通过允许编译器推断变量的“常量性”(finality),JavaSE8减轻了问题4带来的困扰不过,JavaSE8的目标并非解决所有上述问题。因此捕获可变变量(问题4)和非局部控制流(问题5)并不在JavaSE8的范畴之内。(尽管我们可能会在未来提供对这些特性的支持)2.函数式接口(Functionalinterfaces)尽管匿名内部类有着种种限制和问题,但是它有一个良好的特性,它和Java类型系统结合的十分紧密:每一个函数对象都对应一个接口类型。之所以说这个特性是良好的,是因为:o接口是Java类型系统的一部分o接口天然就拥有其运行时表示(Runtimerepresentation)o接口可以通过Javadoc注释来表达一些非正式的协定(contract),例如,通过注释说明该操作应可交换(commutative)上面提到的ActionListener接口只有一个方法,大多数回调接口都拥有这个特征:比如Runnable接口和Comparator接口。我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即单抽象方法类型(SingleAbstractMethod))我们并不需要额外的工作来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断过程并非简单的对接口方法计数:一个接口可能冗余的定义了一个Object已经提供的方法,比如toString(),或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API作者们可以通过@FunctionalInterface注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。实现函数式类型的另一种方式是引入一个全新的结构化函数类型,我们也称其为“箭头”类型。例如,一个接收String和Object并返回int的函数类型可以被表示为(String,Object)-int。我们仔细考虑了这个方式,但出于下面的原因,最终将其否定:o它会为Java类型系统引入额外的复杂度,并带来结构类型(StructuralType)和指名类型(NominalType)的混用。(Java几乎全部使用指名类型)o它会导致类库风格的分歧——一些类库会继续使用回调接口,而另一些类库会使用结构化函数类型o它的语法会变得十分笨拙,尤其在包含受检异常(checkedexception)之后o每个函数类型很难拥有其运行时表示,这意味着开发者会受到类型擦除(erasure)的困扰和局限。比如说,我们无法对方法m(T-U)和m(X-Y)进行重载(Overload)所以我们选择了“使用已知类型”这条路——因为现有的类库大量使用了函数式接口,通过沿用这种模式,我们使得现有类库能够直接使用lambda表达式。例如下面是JavaSE7中已经存在的函数式接口:ojava.lang.Runnableojava.util.concurrent.Callableojava.security.PrivilegedActionojava.util.Comparatorojava.io.FileFilterojava.beans.PropertyChangeListener除此之外,JavaSE8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:oPredicateT——接收T并返回booleanoConsumerT——接收T,不返回值oFunctionT,R——接收T,返回RoSupplierT——提供T对象(例如工厂),不接收值oUnaryOperatorT——接收T对象,返回ToBinaryOperatorT——接收两个T,返回T除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitivetype)的特化(Specialization)函数式接口,例如IntSupplier和LongBinaryOperator。(我们只为int、long和double提供了特化函数式接口,如果需要使用其它原始类型则需要进行类型转换)同样的我们也提供了一些针对多个参数的函数式接口,例如BiFunctionT,U,R,它接收T对象和U对象,返回R对象。3.lambda表达式(lambdaexpressions)匿名类型最大的问题就在于其冗余的语法。有人戏称匿名类型导致了“高度问题”(heightproblem):比如前面ActionListener的例子里的五行代码中仅有一行在做实际工作。lambda表达式是匿名方法,它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。下面是一些lambda表达式:123(intx,inty)-x+y()-42(Strings)-{System.out.println(s);}第一个lambda表达式接收x和y这两个整形参数并返回它们的和;第二个lambda表达式不接收参数,返回整数‘42’;第三个lambda表达式接收一个字符串并把它打印到控制台,不返回值。lambda表达式的语法由参数列表、箭头符号-和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:o表达式:表达式会被执行然后返回执行结果。o语句块:语句块中的语句会被依次执行,就像方法中的语句一样——return语句会把控制权交给匿名方法的调用者break和continue只能在循环中使用如果函数体有返回值,那么函数体内部的每一条路径都必须返回值表达式函数体适合小型lambda表达式,它消除了return关键字,使得语法更加简洁。lambda表达式也会经常出现在嵌套环境中,比如说作为方法的参数。为了使lambda表达式在这些场景下尽可能简洁,我们去除了不必要的分隔符。不过在某些情况下我们也可以把它分为多行,然后用括号包起来,就像其它普通表达式一样。下面是一些出现在语句中的lambda表达式:12345678FileFilterjava=(Filef)-f.getName().endsWith(*.java);Stringuser=doPrivileged(()-System.getProperty(user.name));newThread(()-{connectToService();sendNotification();}).start();4.目标类型(Targettyping)需要注意的是,函数式接口的名称并不是lambda表达式的一部分。那么问题来了,对