前言:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,参考了https://www.jianshu.com/p/5b800057f2d8 ,为了加深印象,提升学习动力,达到学习编码和学习英语的双重目的,希望是一个好的开端。
1.介绍:
毫无疑问,Java 8 release在java世界里自从java5(2004年发布的)开始是最好的版本,作为Java语言,在编译,库,工具和jvm中带来了大量的新的特性,在这份指导中,我们将会查看所有这些改变并且通过真实的例子演示不同的使用场景。
这份指导由几部分组成,每一部分都涉及平台的特定的部分:
-language
-compiler
-libraries
-tools
-runtime (JVM)
2.在java语言中的新特性
java8无论怎样都是一个重大的版本,一种说法是为了实现每一个开发者都在寻找的新特性,它花费了很长时间来完成,在这部分章节中,我们将会覆盖大部分。
2.1 Lambdas 和函数式接口
Lambdas (也被称为闭包closures)在整个java8中是最大且最值得期待的改变,允许我们把函数当作一个参数,或者把代码当成数据:这个概念functional developer者是非常熟悉的。许多在jvm平台上的语言(Groovy,Scala)自从诞生的那天起都有lambdas ,java开发者却没有,只能使用匿名来代替lambdas。
lambdas的设计讨论花费了很大的时间和社区努力,但是最终找到了折中的设计,一种简洁又紧凑的结构,在一个最简单的形式中,一个lambda可以通过参数列表用逗号分隔,一个->符号和语句块代表,例如
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
请注意参数e的类型是被编译器推断(inferred)出来的,另外你也可以显式的提供这个参数的类型,,包装一下在括号中的这个定义,例如
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
为了不让lambd的主体变得更复杂,可以把主体用方括号包括起来,就像在Java中定义的普通方法一样,例如
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
final String 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 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
语言设计者下了很大劲去思考如何让已经存在的功能对lambda友好,结果就是 functional interfaces,函数式接口产生了。 java.lang.Runnable和java.util.concurrent.Callables是非常好的两个函数式接口的例子,在实践中,函数式接口是脆弱的,如果有人把另一个方法添加到定义的接口中,那就不再是函数接口了,编译过程就会失败。为了克服这个脆弱性,显式的声明这个接口的函数式意图,java8添加了一个特别的注解@FunctionalInterface,看一下这个简单函数式接口的定义:
@FunctionalInterface
public interface Functional {
void method();
}
记住一件事:默认的和静态的方法可以被声明并且不会打破函数式接口的规则:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
lambda是java8的最大的亮点,它完全由有潜力吸引越来越多的开发者到这个伟大的平台,在纯java中为函数使编程的概念提供强有力的支持,更到详情请参考official documentation.
2.2接口的默认和静态的方法
java8用两个新的概念扩展了接口定义:默认和静态的方法,默认方法使接口类似与traits 但是服务于不同的目标,他们允许往已经存在的接口中添加新的方法,而不会打破那些老版本接口中代码的二进制兼容性(compatibility )。
默认方法与抽象方法不同的方法就是抽象方法要求必须实现,默认方法不需要。取而代之的是每个接口必须提供所谓的默认实现,所有接口的的实现类都会默认继承它,如果需要还可以重写,看下面的例子:
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
}
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
这个接口使用关键字default 声明了一个默认的方法notRequired(),有一个类DefaultableImpl实现了这个接口,保留了这个方法的默认实现,另外一个类OverridableImpl 重写了这个默认实现方法,作为他自己拥有的。
另一个Java8 提供的有趣特性是接口可以声明一个静态的实现方法,看例子:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面的代码片段把上面例子的默认方法和静态方法整合到了一起。
public static void main( 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.util.Collection接口中:stream(), parallelStream(), forEach(), removeIf(), …
虽然比较强大,但是默认方法应该谨慎使用:在被声明默认方法之前最好多考虑一下,他是否真的是需要的,因为在复杂的结构中可能会导致困惑和编译错误,更多详情参考官方文档official documentation.
2.3 方法引用
方法引用提供了对现有的方法,类的构造函数或者对象实例的直接引用的很有用的语法,结合lambda表达式,方法引用使语言结构更加紧凑简洁避免使用样板。
看下面,把类Car使用4种不同的方法定义作为一个例子,让我们区分被方法引用支持的4种类型,
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一种方法引用的类型是构造函数引用,语法是Class::new,带泛型时语法是Class<T>::new,请注意构造函数没有参数
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
第二种方法引用是对静态方法的引用,使用的语法是Class::static_method,请注意这个方法只接受一个类型是Car的参数。
cars.forEach( Car::collide );
第三种方法引用是对特定类型的任意对象的实例方法(非静态)的引用,请注意这个方法没有参数,使用的语法是instance::method
cars.forEach( Car::repair );
最后,第四种方法引用是对特定类实例对象的实例方法的引用,使用的语法是instance::method请注意这个方法只接受一个Car类型的参数.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
将所有示例作为一个java程序运行,将会输出已下内容到控制台"
Collided java8.Car@7cca494b
Repaired java8.Car@7cca494b
Following the java8.Car@7cca494b
方法引用的更多详情和示例请参考 official documentation.
2.4 重复注释
自从java5引入了 注解支持以来,这个特性就非常受欢迎且广泛使用,然而注解使用有一个限制就是相同的注解不能在同一个位置被声明多次,java8的打破了这个限制并且引入了重复注解,这将允许同一个注解在它被声明的位置使用多次。
这个重复注解应该添加一个@Repeatable注解,事实上这不是一个语言的改变,而是一个编译技巧,它的底层技术没有变,看一下这个简单的示例:
package com.javacodegeeks.java8.repeatable.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface Filters {
Filter[] value();
}
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
}
正如看到的一样,有一个被@Repeatable( Filters.class )注解修饰的注解类Filter,Filters 只是Filter的一个持有者,java编译器让它的存在对开发者隐藏,所以这个接口Filterable被Filter注解声明定义了两次(没有提及Filters)。
反射API也提供了新的方法getAnnotationsByType()来返回某些类型的重复注解(注意,Filterable.class.getAnnotation( Filters.class ) 方法将会返回被编译器注入的Filters注解的实例)
这个程序输出:
filter1
filter2
更多详情请参看official documentation
2.5 更好的类型推断
java8编译器在类型推断方面改进了很多,编译器可以推断显示类型参数,以保持代码整洁,看下面的例子
public class Value< T > {
public static<T> T defaultValue() {
return null;
}
public T getOrDefault( T value, T defaultValue ) {
return ( value != null ) ? value : defaultValue;
}
}
下面是Value<String>的用法
public class TypeInference {
public static void main(String[] args) {
final Value< String > value = new Value<>();
value.getOrDefault( "22", Value.defaultValue() );
}
}
Value.defaultValue()这个参数类型是被推断出来的,可以不提供参数类型,在Java7种,这个示例编译不会通过,应该改写成Value.< String >defaultValue()。
2.6 扩展注解支持
Java8扩展可能使用注解的上下文context,现在可以注解大部分东西:本地变量,泛型,父类和实现的接口,甚至方法的异常声明,下面几个例子说明:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是java8新加的元素类型,描述适用的应用上下文,处理注释的API也进行了一些小的修改以识别java语言中的新的类型注释。
3. java编译器的新特性
3.1参数名
长期以来,Java开发人员一直在发明不同的方式来保存java字节码中的方法参数名,并使其在运行期可用((for example, Paranamer library)
),最终,Java8把这个要求很高的特性加入到了语言(通过反射API和Parameter.getName())和字节码中(使用新的javac编译器参数-parameters)
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterNames {
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod( "main", String[].class );
for( final Parameter parameter: method.getParameters() ) {
System.out.println( "Parameter: " + parameter.getName() );
}
}
}
如果你在不使用 –parameters参数的情况下编译这个类,然后运行这个程序,你将会看到如下输出:
Parameter: arg0
如果加上–parameters参数进行编译,然后运行,输出结果就会不同(真实的参数名会被打印出来):
Parameter: args
对于有Maven使用经验的人来说,这个–parameters可以通过使用maven-compiler-plugin的配置部分添加到编译器中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
此外,为了验证参数名字的可用性,Parameter类提供了一个方便的方法isNamePresent() 。
4. java库的新特性
Java8加入了大量的新的类,扩展了已经存在的类,就是为了对现代并发,函数编程,日期/时间等等提供更好的支持。
4.1 Optional
著名的NullPointerException空指针异常到目前为止是造成java程序失败的最大原因,很久以前,这个伟大的Google Guava项目引入了Optional作为解决空指针异常的方案,用空检查来防止代码污染,鼓励程序员写出干净的代码,受 Google Guava
的启发,现在Optional成为了java8库的一部分。
Optional只是一个容器:它可以保存T类型的值或者就是个Null值,它提供了很多有用的方法,这样就不需要进行显式的Null值检查了,更多详情参考 官方Java 8 文档
我们看一下两个对Optional使用的小例子:一个可以是空值,一个不能是空指。
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
如果Option实例有一个非Null的值,这个isPresent()方法会返回一个true,否则是false。
1.
- 文本1
jskagkeriogaerwig;
一盏灯, 一片昏黄;一简书, 一杯淡茶。 守着那一份
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
gfjdshkwiog