1.介绍
毫无疑问,Java 8发行版是自Java 5(发行于2004,已经过了相当一段时间了)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇教程中,我们将一一探索这些变化,并用真实的例子说明它们适用的场景。
这篇教程由以下几部分组成,它们分别涉及到Java平台某一特定方面的内容:
Java语言
编译器
类库
工具
Java运行时(JVM)
2.Java语言的新特性
下。
2.1 Lambda表达式与Functional接口
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据:函数式程序员对这一概念非常熟悉。在JVM平台上的很多语言(Groovy,Scala,……)从一开始就有Lambda,但是Java程序员不得不使用毫无新意的匿名类来代替lambda。
关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是,最终找到了一个平衡点,使得可以使用一种即简洁又紧凑的新方式来构造Lambdas。在最简单的形式中,一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
Arrays.asList( "a", "b", "d").forEach( e -> System.out.println( e ) );
请注意参数e的类型是由编译器推测出来的。同时,你也可以通过把参数类型与参数包括在括号中的形式直接给出参数的类型:
Arrays.asList( "a", "b", "d").forEach( ( String e ) -> System.out.println( e ) );
在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。例如:
Arrays.asList( "a", "b", "d").forEach( e -> { System.out.print( e ); System.out.print( e ); } );
Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。例如,下面两个代码片段是等价的:
String separator = ","; Arrays.asList( "a", "b", "d").forEach( ( String e ) -> System.out.print( e + separator ) );
和:
finalString separator = ","; Arrays.asList( "a", "b", "d").forEach( ( String e ) -> System.out.print( e + separator ) );
Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片段是等价的:
Arrays.asList( "a", "b", "d").sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和:
Arrays.asList( "a", "b", "d").sort( ( e1, e2 ) -> { intresult = e1.compareTo( e2 ); returnresult; } );
语言设计者投入了大量精力来思考如何使现有的函数友好地支持lambda。最终采取的方法是:增加函数式接口的概念。函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java8增加了一种特殊的注解@FunctionalInterface
(Java8中所有类库的已有接口都添加了@FunctionalInterface
注解)。让我们看一下这种函数式接口的定义:
@FunctionalInterfacepublicinterfaceFunctional { voidmethod(); }
需要记住的一件事是:默认方法与静态方法并不影响函数式接口的契约,可以任意使用:
@FunctionalInterfacepublicinterfaceFunctionalDefaultMethods { voidmethod(); defaultvoiddefaultMethod() { } }
Lambda是Java 8最大的卖点。它具有吸引越来越多程序员到Java平台上的潜力,并且能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程。更多详情可以参考官方文档。
2.2 接口的默认方法与静态方法
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像Traits(Scala中特征(trait)类似于Java中的Interface,但它可以包含实现代码,也就是目前Java8新增的功能),但与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:
privateinterfaceDefaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. defaultString notRequired() { return"Default implementation"; } } privatestaticclassDefaultableImpl implementsDefaulable { } privatestaticclassOverridableImpl implementsDefaulable { @Override publicString notRequired() { return"Overridden implementation"; } }
Defaulable接口用关键字default声明了一个默认方法notRequired(),Defaulable接口的实现者之一DefaultableImpl实现了这个接口,并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。
Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:
privateinterfaceDefaulableFactory { // Interfaces now allow static methods staticDefaulable create( Supplier< Defaulable > supplier ) { returnsupplier.get(); } }
下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。
publicstaticvoidmain( String[] args ) { Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new); System.out.println( defaulable.notRequired() ); defaulable = DefaulableFactory.create( OverridableImpl::new); System.out.println( defaulable.notRequired() ); }
这个程序的控制台输出如下:
Default implementation Overridden implementation
在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf()
,……
尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档
2.3 方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
下面,我们以定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。
publicstaticclassCar { publicstaticCar create( finalSupplier< Car > supplier ) { returnsupplier.get(); } publicstaticvoidcollide( finalCar car ) { System.out.println( "Collided "+ car.toString() ); } publicvoidfollow( finalCar another ) { System.out.println( "Following the "+ another.toString() ); } publicvoidrepair() { System.out.println( "Repaired "+ this.toString() ); } }
第一种方法引用是构造器引用,它的语法是Class::new
,或者更一般的Class< T >::new
。请注意构造器没有参数。
finalCar car = Car.create( Car::new); finalList< Car > cars = Arrays.asList( car );
第二种方法引用是静态方法引用,它的语法是Class::static_method
。请注意这个方法接受一个Car类型的参数。
cars.forEach( Car::collide );
第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method
。请注意,这个方法没有参数。
cars.forEach( Car::repair );
最后,第四种方法引用是特定对象的方法引用,它的语法是instance::method
。请注意,这个方法接受一个Car类型的参数
finalCar police = Car.create( Car::new); cars.forEach( police::follow );
运行上面的Java程序在控制台上会有下面的输出(Car的实例可能不一样):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
关于方法引用的更多详情请参考官方文档。
作者:SIHAIloveYAN
链接:https://www.jianshu.com/p/d4592433af65
。