java8新特性

前言:本文翻译自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_USEElementType.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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容