Java 8 特性 – 終極手冊

jopen 9年前發布 | 43K 次閱讀 Java 8 Java開發

原文鏈接原文作者:Andrey Redko ,譯者:Justin,校對:郭蕾

1.簡介

毫無疑問,Java 8是自Java 5(2004年)發布以來Java語言最大的一次版本升級,Java 8帶來了很多的新特性,比如編譯器、類庫、開發工具和JVM(Java虛擬機)。在這篇教程中我們將會學習這些新特性,并通過真實例子演示說明它們適用的場景

本教程由下面幾部分組成,它們分別涉及到Java平臺某一特定方面的內容:

  • 語言
  • 編譯器
  • 類庫
  • 開發工具
  • 運行時(Java虛擬機)
  • </ul>

    2.Java的新特性

    總體來說,Java 8是一個大的版本升級。有人可能會說,Java 8的新特性非常令人期待,但是也要花費大量的時間去學習。這一節我們會講到這些新特性。

    2.1 Lambda表達式和函數式接口

    Lambda表達式(也叫做閉包)是Java 8中最大的也是期待已久的變化。它允許我們將一個函數當作方法的參數(傳遞函數),或者說把代碼當作數據,這是每個函數式編程者熟悉的概念。很多基于JVM平臺的語言一開始就支持Lambda表達式,但是Java程序員沒有選擇,只能使用匿名內部類來替代Lambda表達式。

    Lambda表達式的設計被討論了很久,而且花費了很多的功夫來交流。不過最后取得了一個折中的辦法,得到了一個新的簡明并且緊湊的Lambda表達式結構。最簡單的Lambda表達式可以用逗號分隔的參數列表、->符號和功能語句塊來表示。示例如下:

    Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

    請注意到編譯器會根據上下文來推測參數的類型,或者你也可以顯示地指定參數類型,只需要將類型包在括號里。舉個例子:

    Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

    如果Lambda的功能語句塊太復雜,我們可以用大括號包起來,跟普通的Java方法一樣,如下:

    String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach(
        ( String e ) -> System.out.print( e + separator ) );

    Lambda表達式可能會引用類的成員或者局部變量(會被隱式地轉變成final類型),下面兩種寫法的效果是一樣的:

    String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach(
        ( String e ) -> System.out.print( e + separator ) );

    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表達式友好兼容。于是就有了函數接口這個概念。函數接口是一種只有一個方法的接口,像這樣地,函數接口可以隱式地轉換成lambda表達式。

    java.lang.Runnable 和java.util.concurrent.Callable是函數接口兩個最好的例子。但是在實踐中,函數接口是非常脆弱的,只要有人在接口里添加多一個方法,那么這個接口就不是函數接口了,就會導致編譯失敗。Java 8提供了一個特殊的注解@FunctionalInterface來克服上面提到的脆弱性并且顯示地表明函數接口的目的(java里所有現存的接口都已經加上了@FunctionalInterface)。讓我們看看一個簡單的函數接口定義:

    @FunctionalInterface
    public interface Functional {
        void method();
    }

    我們要記住默認的方法和靜態方法(下一節會具體解釋)不會違反函數接口的約定,例子如下:

    @FunctionalInterface
    public interface FunctionalDefaultMethods {
        void method();

    default void defaultMethod() {
    }
    

    }</pre>

    支持Lambda是Java 8最大的賣點,他有巨大的潛力吸引越來越多的開發人員轉到這個開發平臺來,并且在純Java里提供最新的函數式編程的概念。對于更多的細節,請參考官方文檔

    2.2 接口的默認方法和靜態方法

    Java 8增加了兩個新的概念在接口聲明的時候:默認和靜態方法。默認方法和Trait有些類似,但是目標不一樣。默認方法允許我們在接口里添加新的方法,而不會破壞實現這個接口的已有類的兼容性,也就是說不會強迫實現接口的類實現默認方法。

    默認方法和抽象方法的區別是抽象方法必須要被實現,默認方法不是。作為替代方式,接口可以提供一個默認的方法實現,所有這個接口的實現類都會通過繼承得倒這個方法(如果有需要也可以重寫這個方法),讓我們來看看下面的例子:

    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"; } }</pre>

    接口Defaulable使用default關鍵字聲明了一個默認方法notRequired(),類DefaultableImpl實現了Defaulable接口,沒有對默認方法做任何修改。另外一個類OverridableImpl重寫類默認實現,提供了自己的實現方法。

    Java 8 的另外一個有意思的新特性是接口里可以聲明靜態方法,并且可以實現。例子如下:

    private interface DefaulableFactory {
        // Interfaces now allow static methods
        static Defaulable create( Supplier< Defaulable > supplier ) {
            return supplier.get();
        }
    }

    下面是把接口的靜態方法和默認方法放在一起的示例(::new 是構造方法引用,后面會有詳細描述):

    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() );
    

    }</pre>

    控制臺的輸出如下:

    Default implementation
    Overridden implementation

    JVM平臺的接口的默認方法實現是很高效的,并且方法調用的字節碼指令支持默認方法。默認方法使已經存在的接口可以修改而不會影響編譯的過程。java.util.Collection中添加的額外方法就是最好的例子:stream(), parallelStream(), forEach(), removeIf()

    雖然默認方法很強大,但是使用之前一定要仔細考慮是不是真的需要使用默認方法,因為在層級很復雜的情況下很容易引起模糊不清甚至變異錯誤。更多的詳細信息請參考官方文檔

    2.3 方法引用

    方法引用提供了一個很有用的語義來直接訪問類或者實例的已經存在的方法或者構造方法。結合Lambda表達式,方法引用使語法結構緊湊簡明。不需要復雜的引用。

    下面我們用Car 這個類來做示例,Car這個類有不同的方法定義。讓我們來看看java 8支持的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() );
    }
    

    }</pre>

    第一種方法引用是構造方法引用,語法是: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 );

    第三種方法引用是類實例的方法引用,語法是:Class::method請注意方法沒有參數。

    cars.forEach( Car::repair );

    最后一種方法引用是引用特殊類的方法,語法是:instance::method請注意只接受Car類型的一個參數。

    final Car police = Car.create( Car::new );
    cars.forEach( police::follow );

    運行這些例子我們將會在控制臺得到如下信息(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

    關于方法引用更多的示例和詳細信息,請參考官方文檔

    2.4 重復注釋

    自從Java 5支持注釋以來,注釋變得特別受歡迎因而被廣泛使用。但是有一個限制,同一個地方的不能使用同一個注釋超過一次。 Java 8打破了這個規則,引入了重復注釋,允許相同注釋在聲明使用的時候重復使用超過一次。

    重復注釋本身需要被@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() );
        }
    }
    

    }</pre>

    我們可以看到,注釋Filter被@Repeatable( Filters.class )注釋。Filters 只是一個容器,它持有Filter, 編譯器盡力向程序員隱藏它的存在。通過這樣的方式,Filterable接口可以被Filter注釋兩次。

    另外,反射的API提供一個新方法getAnnotationsByType() 來返回重復注釋的類型(請注意Filterable.class.getAnnotation( Filters.class )將會返回編譯器注入的Filters實例)。

    程序的輸出將會是這樣:

    filter1
    filter2

    更多詳細信息請參考官方文檔

    2.5 更好的類型推斷

    Java 8在類型推斷方面改進了很多,在很多情況下,編譯器可以推斷參數的類型,從而保持代碼的整潔。讓我們看看例子:

    package com.javacodegeeks.java8.type.inference;

    package com.javacodegeeks.java8.type.inference;

    public class Value<T> { public static<T> T defaultValue() { return null; }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
    

    }</pre>

    這里是Value< String >的用法

    package com.javacodegeeks.java8.type.inference;

    public class TypeInference { public static void main(String[] args) { final Value<String> value = new Value<>(); value.getOrDefault( "22", Value.defaultValue() ); } }</pre>

    參數Value.defaultValue()的類型被編譯器推斷出來,不需要顯式地提供類型。在java 7, 相同的代碼不會被編譯,需要寫成:Value.< String >defaultValue()

    2.6 注解的擴展

    Java 8擴展了注解可以使用的范圍,現在我們幾乎可以在所有的地方:局部變量、泛型、超類和接口實現、甚至是方法的Exception聲明。一些例子如下:

    package com.javacodegeeks.java8.annotations;

    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<>();
    }
    

    }</pre>

    Java 8 新增加了兩個注解的程序元素類型ElementType.TYPE_USE ElementType.TYPE_PARAMETER ,這兩個新類型描述了可以使用注解的新場合。注解處理API(Annotation Processing API)也做了一些細微的改動,來識別這些新添加的注解類型。

    3.Java編譯器的新特性

    3.1 參數名字

    很長時間以來,Java程序員想盡辦法把參數名字保存在java字節碼里,并且讓這些參數名字在運行時可用。Java 8 終于把這個需求加入到了Java語言(使用反射API和Parameter.getName() 方法)和字節碼里(使用java編譯命令javac的–parameters參數)。

    package com.javacodegeeks.java8.parameter.names;

    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() ); } } }</pre>

    如果你編譯這個class的時候沒有添加參數–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>

    最新版的Eclipse Kepler SR2 提供了編譯設置項,如下圖所示:

    Java 8 特性 – 終極手冊

    Picture 1. Configuring Eclipse projects to support new Java 8 compiler –parameters argument.

    額外的,有一個方便的方法Parameter.isNamePresent() 來驗證參數名是不是可用。

     

    4.Java 庫的新特性

    Java 8 新添加了很多類,并且擴展了很多現有的類來更好地支持現代并發、函數式編程、日期\時間等等。

    4.1 Optional

    著名的NullPointerException 是引起系統失敗最常見的原因。很久以前Google Guava項目引入了Optional作為解決空指針異常的一種方式,不贊成代碼被null檢查的代碼污染,期望程序員寫整潔的代碼。受Google Guava的鼓勵,Optional 現在是Java 8庫的一部分。

    Optional 只是一個容器,它可以保存一些類型的值或者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!" ) );

    如果Optional實例有非空的值,方法 isPresent() 返回true否則返回false。方法orElseGet提供了回退機制,當Optional的值為空時接受一個方法返回默認值。map()方法轉化Optional當前的值并且返回一個新的Optional實例。orElse方法和orElseGet類似,但是它不接受一個方法,而是接受一個默認值。上面代碼運行結果如下:

    Full Name is set? false
    Full Name: [none]
    Hey Stranger!

    讓我們大概看看另外一個例子。

    Optional< String > firstName = Optional.of( "Tom" );
    System.out.println( "First Name is set? " + firstName.isPresent() );        
    System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
    System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    System.out.println();

    輸出如下:

    First Name is set? true
    First Name: Tom
    Hey Tom!

    更多詳細信息請參考官方文檔

    4.2 Stream

    新增加的Stream API (java.util.stream)引入了在Java里可以工作的函數式編程。這是目前為止對java庫最大的一次功能添加,希望程序員通過編寫有效、整潔和簡明的代碼,能夠大大提高生產率。

    Stream API讓集合處理簡化了很多(我們后面會看到不僅限于Java集合類)。讓我們從一個簡單的類Task開始來看看Stream的用法。

    public class Streams {
    private enum Status {
    OPEN, CLOSED
    };

    private static final class Task { private final Status status; private final Integer points;

    Task( final Status status, final Integer points ) { this.status = status; this.points = points; }

    public Integer getPoints() { return points; }

    public Status getStatus() { return status; }

    @Override public String toString() { return String.format( "[%s, %d]", status, points ); } } }</pre>

    Task類有一個分數的概念(或者說是偽復雜度),其次是還有一個值可以為OPEN或CLOSED的狀態.讓我們引入一個Task的小集合作為演示例子:

    final Collection< Task > tasks = Arrays.asList(
        new Task( Status.OPEN, 5 ),
        new Task( Status.OPEN, 13 ),
        new Task( Status.CLOSED, 8 ) 
    );

    第一個問題是所有的開放的Task的點數是多少?在java 8 之前,通常的做法是用foreach迭代。但是Java8里頭我們會用Stream。Stream是多個元素的序列,支持串行和并行操作。

    // Calculate total points of all active tasks using sum()
    final long totalPointsOfOpenTasks = tasks
        .stream()
        .filter( task -> task.getStatus() == Status.OPEN )
        .mapToInt( Task::getPoints )
        .sum();

    System.out.println( "Total points: " + totalPointsOfOpenTasks );</pre>

    控制臺的輸出將會是:

    Total points: 18
    上面代碼執行的流程是這樣的,首先Task集合會被轉化為Stream表示,然后filter操作會過濾掉所有關閉的Task,接下來使用Task::getPoints 方法取得每個Task實例的點數,mapToInt方法會把Task Stream轉換成Integer Stream,最后使用Sum方法將所有的點數加起來得到最終的結果。

    在我們看下一個例子之前,我們要記住一些關于Stream的說明。Stream操作被分為中間操作和終點操作。

    中間操作返回一個新的Stream。這些中間操作是延遲的,執行一個中間操作比如filter實際上不會真的做過濾操作,而是創建一個新的Stream,當這個新的Stream被遍歷的時候,它里頭會包含有原來Stream里符合過濾條件的元素。

    終點操作比如說forEach或者sum會遍歷Stream從而產生最終結果或附帶結果。終點操作執行完之后,Stream管道就被消費完了,不再可用。在幾乎所有的情況下,終點操作都是即時完成對數據的遍歷操作。

    Stream的另外一個價值是Stream創造性地支持并行處理。讓我們看看下面這個例子,這個例子把所有task的點數加起來。

    // Calculate total points of all tasks
    final double totalPoints = tasks
       .stream()
       .parallel()
       .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
       .reduce( 0, Integer::sum );

    System.out.println( "Total points (all tasks): " + totalPoints );</pre>

    這個例子跟上面那個非常像,除了這個例子里使用了parallel()方法 并且計算最終結果的時候使用了reduce方法。

    輸出如下:

    Total points (all tasks): 26.0
    經常會有這個一個需求:我們需要按照某種準則來對集合中的元素進行分組。Stream也可以處理這樣的需求,下面是一個例子:

    // Group tasks by their status
    final Map< Status, List< Task > > map = tasks
        .stream()
        .collect( Collectors.groupingBy( Task::getStatus ) );
    System.out.println( map );

    控制臺的輸出如下:

    {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
    讓我們來計算整個集合中每個task分數(或權重)的平均值來結束task的例子。

    // Calculate the weight of each tasks (as percent of total points) 
    final Collection< String > result = tasks
        .stream()                                        // Stream< String >
        .mapToInt( Task::getPoints )                     // IntStream
        .asLongStream()                                  // LongStream
        .mapToDouble( points -> points / totalPoints )   // DoubleStream
        .boxed()                                         // Stream< Double >
        .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
        .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
        .collect( Collectors.toList() );                 // List< String >

    System.out.println( result );</pre>

    控制臺輸出如下:

    [19%, 50%, 30%]

    最后,就像前面提到的,Stream API不僅僅處理Java集合框架。像從文本文件中逐行讀取數據這樣典型的I/O操作也很適合用Stream API來處理。下面用一個例子來應證這一點。

    final Path path = new File( filename ).toPath();
    try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
        lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
    }

    Stream的方法onClose 返回一個等價的有額外句柄的Stream,當Stream的close()方法被調用的時候這個句柄會被執行。

    Stream API、Lambda表達式還有接口默認方法和靜態方法支持的方法引用,是Java 8對軟件開發的現代范式的響應。

     

    4.3日期時間API(JSR310

    Java 8引入了新的日期時間API(JSR 310)改進了日期時間的管理。日期和時間管理一直是Java開發人員最痛苦的問題。java.util.Date和后來的java.util.Calendar一點也沒有改變這個情況(甚至讓人們更加迷茫)。

    因為上面這些原因,產生了Joda-Time ,可以替換Java的日期時間API。Joda-Time深刻影響了 Java 8新的日期時間API,Java 8吸收了Joda-Time 的精華。新的java.time包包含了所有關于日期、時間、日期時間、時區、Instant(跟日期類似但精確到納秒)、duration(持續時間)和時鐘操作的類。設計這些API的時候很認真地考慮了這些類的不變性(從java.util.Calendar吸取的痛苦教訓)。如果需要修改時間對象,會返回一個新的實例。

    讓我們看看一些關鍵的類和用法示例。第一個類是Clock,Clock使用時區來訪問當前的instant, date和time。Clock類可以替換 System.currentTimeMillis()TimeZone.getDefault().

    // Get the system clock as UTC offset
    final Clock clock = Clock.systemUTC();
    System.out.println( clock.instant() );
    System.out.println( clock.millis() );

    控制臺輸出如下:

    2014-04-12T15:19:29.282Z
    1397315969360

    其他類我們看看LocalTime和LocalDate。LocalDate只保存有ISO-8601日期系統的日期部分,有時區信息,相應地,LocalTime只保存ISO-8601日期系統的時間部分,沒有時區信息。LocalDate和LocalTime都可以從Clock對象創建。

    // Get the local date and local time
    final LocalDate date = LocalDate.now();
    final LocalDate dateFromClock = LocalDate.now( clock );

    System.out.println( date ); System.out.println( dateFromClock );

    // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock );

    System.out.println( time ); System.out.println( timeFromClock );</pre>

    控制臺輸出如下:

    2014-04-12
    2014-04-12
    11:25:54.568
    15:25:54.568

    LocalDateTime類合并了LocalDate和LocalTime,它保存有ISO-8601日期系統的日期和時間,但是沒有時區信息。讓我們看一個簡單的例子。

    // Get the local date/time
    final LocalDateTime datetime = LocalDateTime.now();
    final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

    System.out.println( datetime ); System.out.println( datetimeFromClock );</pre>

    輸出如下:

    2014-04-12T11:37:52.309
    2014-04-12T15:37:52.309

    如果您需要一個類持有日期時間和時區信息,可以使用ZonedDateTime,它保存有ISO-8601日期系統的日期和時間,而且有時區信息。讓我們看一些例子:

    // Get the zoned date/time
    final ZonedDateTime zonedDatetime = ZonedDateTime.now();
    final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
    final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

    System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone );</pre>

    輸出如下:
    2014-04-12T11:47:01.017-04:00[America/New_York]
    2014-04-12T15:47:01.017Z
    2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

    最后讓我們看看Duration類,Duration持有的時間精確到納秒。它讓我們很容易計算兩個日期中間的差異。讓我們來看一下:

    // Get duration between two dates
    final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
    final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

    final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() );</pre>

    上面的例子計算了兩個日期(2014年4月16日和2014年5月16日)之間的持續時間(基于天數和小時)輸出如下:

    Duration in days: 365
    Duration in hours: 8783

    對于Java 8的新日期時間的總體印象還是比較積極的。一部分是因為有經歷實戰的Joda-Time的基礎,還有一部分是因為日期時間終于被認真對待而且聽取了開發人員的聲音。關于更多的詳細信息,請參考官方文檔

     

    4.4 Nashorn javascript引擎

    Java 8提供了一個新的Nashorn javascript引擎,它允許我們在JVM上運行特定的javascript應用。Nashorn javascript引擎只是javax.script.ScriptEngine另一個實現,而且規則也一樣,允許Java和JavaScript互相操作。這里有個小例子:

    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName( "JavaScript" );

    System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );</pre>

    輸出如下:

    jdk.nashorn.api.scripting.NashornScriptEngine
    Result: 2
    4.5 Base64

    對Base64的支持最終成了Java 8標準庫的一部分,非常簡單易用:

    package com.javacodegeeks.java8.base64;

    import java.nio.charset.StandardCharsets; import java.util.Base64;

    public class Base64s { public static void main(String[] args) { final String text = "Base64 finally in Java 8!";

    final String encoded = Base64 .getEncoder() .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); System.out.println( encoded );

    final String decoded = new String( Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 ); System.out.println( decoded ); } }</pre>

    控制臺輸出的編碼和解碼的字符串

    QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
    Base64 finally in Java 8!

    新的Base64API也支持URL和MINE的編碼解碼。

    (Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder()).

     

    4.6 并行數組

    Java 8新增加了很多方法支持并行的數組處理。最重要的大概是parallelSort()這個方法顯著地使排序在多核計算機上速度加快。下面的小例子演示了這個新的方法(parallelXXX)的行為。

    </pre>
    <pre class="brush:java">package com.javacodegeeks.java8.parallel.arrays;

    import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom;

    public class ParallelArrays { public static void main( String[] args ) { long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    
        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
    

    }</pre> <pre></pre>

    這一小段代碼使用parallelSetAll() t方法填充這個長度是2000的數組,然后使用parallelSort() 排序。這個程序輸出了排序前和排序后的10個數字來驗證數組真的已經被排序了。示例可能的輸出如下(請注意這些數字是隨機產生的)

    Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
    Sorted: 39 220 263 268 325 607 655 678 723 793

    4.7 并發

    在新增Stream機制與lambda的基礎之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持聚集操作。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)(請查看我們關于Java 并發的免費課程)。

    新增的java.util.concurrent.locks.StampedLock類提供一直基于容量的鎖,這種鎖有三個模型來控制讀寫操作(它被認為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。

    在java.util.concurrent.atomic包中還增加了下面這些類:

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder
    • </ul>

      5. 新的工具

      Java 8 提供了一些新的命令行工具,在這節里我們將會介紹它們中最有趣的部分。

      5.1 Nashorn引擎:jjs

      jjs是個基于Nashorn引擎的命令行工具。它接受一些JavaScript源代碼為參數,并且執行這些源代碼。例如,我們創建一個具有如下內容的func.js文件:

      </span>
      <pre>
      function f() {
      return 1;
      };

      print( f() + 1 );</pre>

      我們可以把這個文件作為參數傳遞給jjs使得這個文件可以在命令行中執行

      <span style="font-size: 13px;">jjs func.js</span>

      輸出結果如下

      2

      更多的詳細信息請參考官方文檔

       

      5.2 類依賴分析工具:jdeps

      Jdeps是一個功能強大的命令行工具,它可以幫我們顯示出包層級或者類層級java類文件的依賴關系。它接受class文件、目錄、jar文件作為輸入,默認情況下,jdeps會輸出到控制臺。

      作為例子,讓我們看看現在很流行的Spring框架的庫的依賴關系報告。為了讓報告短一些,我們只分析一個jar: org.springframework.core-3.0.5.RELEASE.jar.

      jdeps org.springframework.core-3.0.5.RELEASE.jar 這個命令輸出內容很多,我們只看其中的一部分,這些依賴關系根絕包來分組,如果依賴關系在classpath里找不到,就會顯示not found.

      </pre>
      <pre class="brush:java">org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
         org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
            -> java.io
            -> java.lang
            -> java.lang.annotation
            -> java.lang.ref
            -> java.lang.reflect
            -> java.util
            -> java.util.concurrent
            -> org.apache.commons.logging                         not found
            -> org.springframework.asm                            not found
            -> org.springframework.asm.commons                    not found
         org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
            -> java.lang
            -> java.lang.annotation
            -> java.lang.reflect
            -> java.util</pre>
      <pre>

      更多的詳細信息請參考官方文檔

      6. JVM的新特性

      JVM內存永久區已經被metaspace替換(JEP 122)。JVM參數 -XX:PermSize 和 –XX:MaxPermSizeXX:MetaSpaceSize-XX:MaxMetaspaceSize代替

      7. 結論

      更多展望:Java 8通過發布一些可以增加程序員生產力的特性來推進這個偉大的平臺的進步。現在把生產環境遷移到Java 8還為時尚早,但是在接下來的幾個月里,它會被大眾慢慢的接受。毫無疑問,現在是時候讓你的代碼與Java 8兼容,并且在Java 8足夠安全穩定的時候遷移到Java 8。

      作為社區對Java 8的認可,最近Pivotal發布了可在生產環境下支持Java 8的Spring Framework 4.0.3

      如果你喜歡這篇文章,請訂閱我們的郵件列表來查看每周的更新以及免費贈送的白皮書。對于更高級的教程,請查看我們的[JCG學院][JCG]。

      我們歡迎你對Java 8中激動人心的特性進行評論!

      8. 資源

      下面一些文章從不同層面上深度討論了Java 8的特性

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!