1. 程式人生 > >Java 8 特性 – 終極指南

Java 8 特性 – 終極指南

第一次嘗試翻譯文章,有錯誤請見諒:)

編者注:Java 8出現在公眾視野中已經有一段時間了,在這期間,種種跡象都表明Java 8是一個非常重要的版本。

為了您閱讀的便利,我們把Java 8主要特性都收集起來放在本文中,希望您能喜歡。

目錄

1. 導言

2.1. Lambdas 和 函式介面

2.2. 介面的Default和Static 方法

2.3.方法引用

3. Java編譯器中的新特性

3.1. 引數命名

4.2. 流

4.4. Nashorn JavaScript 引擎

4.7. 兵併發

5. Java的新工具

6. JVM新特性

7. 總結

1. 導言

毋庸置疑,Java 8是自Java 5(2004年釋出)以來 Java世界最重大的事件。Java語言,編譯器,標準庫,工具和JVM本身都有許多新的特性。在這個指南中,我們將一起來看看所有這些改變,並且用一些真實的例子演示對應的使用場景。

本指南由多部分組成,每一部分都講述了Java平臺的以下某一方面

  • 語言
  • 編譯器
  • 標準庫
  • 工具
  • 執行時 (JVM)

2. Java語言中的新特性

無論從哪種意義上來說,Java 8都是一個主要的版本更新。你可以說它為了實現所有Java程式設計師所期待的特性,導致它花了如此長的時間才定稿。在本節我們會覆蓋大部分的Java語言新特性

2.1. Lambdas and 函式介面

Lambdas(

也稱作閉包)是整個Java 8中最大的也是最令人期待的Java語言的改變。他允許我們把函式作為方法的引數(將函式在方法間傳遞)也可以說像對待資料一樣對待程式碼:這是每個使用函式式語言的開發者所非常熟悉的。許多JVM平臺上的語言也從一開始就支援lambdas,但是Java開發者卻沒有別的選擇,只能用匿名類來模擬lambdas效果。

人們已經對Lambdas的設計進行了很長時間的討論,Java社群也為此做出了許多努力。最終大家找到了一個平衡點,使得我們得到了一個新的簡明並且緊湊的語言結構。在它最簡單的形式裡, 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 ) );

如果lamdba的程式體比較複雜,我們也可以像通常的Java函式定義一樣用花括號把它包起來。如:

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

Lambdas可以引用類成員和本地變數(如果他們不是final的,Java會把他們隱式得轉成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 ) );

Lambdas 也可以有返回值。返回值的型別同樣會由編譯器推斷出來。如果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;
} );

 Java語言設計者為如何把現有的功能變成lambda相容絞盡腦汁。函式式介面就是一個產物。函式式介面是一個只有單個方法的介面。他可能會被隱式的轉換成lambda表示式。java.lang.Runnablejava.util.concurrent.Callable是函式式介面兩個很好的例子。在實際中,函式式介面是非常容易被破壞的,假設有人對介面定義添加了一個方法,那這個介面就不再是函式式介面,然後就會造成編譯失敗。為了克服這個缺點,Java 8增加了一個特別的annotation @FunctionalInterface。用這個annotation可以顯示的把一個介面宣告稱函式式介面(所有已經存在於Java庫中的函式式介面都已經加上了這個annotation)。讓我們來看一下這個簡單的函式式介面的定義:

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

有一點需要記住的是:default和static方法不會破壞函式式介面的約定,所以我們可以在函式式介面中使用它們。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
        
    default void defaultMethod() {            
    }        
}

LambdasJava8是最大賣點。他擁有一切吸引越來越多的開發者來Java這一偉大平臺的潛力,他也在純Java語言中對函數語言程式設計概念提供了最先進的支援。

2.2. 介面的Default和Static方法

Java 8對介面的宣告進行了擴充套件,引進了兩個新概念:defaultstatic方法。Default方法使得介面有點像traits,但又不是為了完全相同的目的。他們允許我們對已有的介面新增新的方法而不破壞已有的介面實現程式碼的二進位制相容性。

Default方法和abstract方法的區別是abstract方法是必須要被實現的。但是default方法不一定。每個介面要對default方法提供所謂的預設實現,這樣所有的實現這個介面的類都會預設繼承這個方法(當然實現類也可以過載這個方法的預設實現)。讓我們來看看下面這個例子:

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

Defaulable介面用default關鍵字聲明瞭一個名字叫notRequired()default方法。DefaultableImpl類實現了這個介面但沒有去改變default方法。另一個OverridableImpl類則對default方法提供了自己的實現。

 Java 8的另外一個有意思的特性是介面能夠宣告(並且提供實現)static method。這裡是一個例子:

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

下面這段程式碼把上面例子裡的default方法和static方法結合了起來:

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中的default方法的實現是非常高效的,JVM對方法的呼叫提供了位元組碼指令級的支援。Default方法使得已有的Java介面能夠進化但不破壞已有程式碼的編譯。一個很好的例子就是Java 8java.util.Collection介面中添加了多如牛毛的方法:stream(), parallelStream(), forEach(), removeIf(),….

雖然Default方法很強大,但是我們使用起來需要特別小心:在宣告一個default方法的時候必須要多想想是不是真的有必要,因為在複雜的繼承體系下下他可能會引起混淆和編譯錯誤。在官方文件(http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html)中可以找到更多的細節。

2.3. 方法引用

方法引用提供了實用的語法使得我們能直接引用Java類和物件(例項)的方法和建構函式。方法引用結合Lambdas表示式讓Java不再需要樣板,使得結構看起來既緊湊又簡明,。

下面的Car類是具有不同的方法定義的一個例子,讓我們來區分一下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 );

第三類是對某個型別的任意物件的例項方法的引用,語法為Class::method。請注意此方法不接受任何引數

cars.forEach( Car::repair );

最後,第四類是對具體類例項的方法的引用,語法為instance::method。請注意此方法接受一個Car型別的引數。

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

執行這些例子會在控制檯得到以下輸出(具體的Car例項會有不一樣):

Collided [email protected]a81197d
Repaired [email protected]a81197d
Following the [email protected]a81197d

2.4. 重複註解(Repeating annotations)

Java5引入annotation的支援以來,這個特性就非常流行並且被廣泛使用。但是annotation使用的一個限制就是同一個annotation不能夠在同一個地方重複宣告。Java8打破了這個規則,引入了重複註解。它允許同樣的annotation在同一地方被宣告多次。

重複註解本身需要以@Repeatable annotation註解。事實上,這不是一個語言的改變,因為他底層的實現技術沒有變化,所以它更多地像是一個編譯器的小花招。讓我們來看一個簡單的例子:

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

在上面的例子中,有一個annotationFilter,被@Repeatable(Filter.class)註解。Filters是其實只是Filter annotation的容器,但是Java編譯器很努力得把它在開發者面前隱藏起來。因此, Filter annotationFilterable介面上聲明瞭兩次(完全不用到Filters)。

同樣的,反射API提供了新的getAnnotationsByType()方法,該方法會返回具體某一型別的重複註解(請注意Filterable.class.getAnnotation(Filters.class)會返回被編譯器注入的Filters例項)

程式的輸出是這樣的:

filter1
filter2

更詳細資訊請參考官方文件http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html  

2.5. 更好的型別推斷

Java 8的編譯器在型別推斷方面有了長足的進步。在很多情況下,明確的引數引數能夠被編譯器推斷出來,這樣程式碼就會簡潔很多。讓我們看一個例子:

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

下面是關於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() );
    }
}

引數Value.defaultValue()的型別是被推斷出來的,它不需要被提供。在Java 7,上面的例子不會通過編譯,我們必須寫成Value.<String>defaultValue();

2.6. 擴充套件的註解支援

Java 8擴充套件了註解可能使用的情況。現在幾乎所有東西都能被註解:本地變數,泛型,超類和介面,甚至是方法的異常宣告。下面是一些例子:

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

ElementType.TYPE_USEElementType.TYPE_PARAMETER是兩個描述註解使用環境的新的元素型別。為了識別這些新的註解型別,Annotation Processing API也進行了一些小的改變。

3. Java編譯器裡的新特性

3.1. 引數名

多年來,Java開發者發明了許多不同的方式去保持Java位元組碼中的方法的引數名字,並是它們在執行時可用(Paranamer library https://github.com/paul-hammant/paranamer )。終於,Java 8把這個令人期待的特性加入了語言(使用反射APIParameter.getName())和位元組碼(使用新的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() );
        }
    }
}

如果你不使用-parameters引數去編譯這個類,那在執行的時候你會看到:

Parameter: arg0

加上-parameters引數之後編譯,程式的輸出就不一樣了(實際的引數名字會被顯示出來):

Parameter: args

為有經驗的Maven使用者考慮,在maven-compiler-pluginconfiguration, -parameters引數可以被配置並應用到編譯器。

<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>

最新的支援Java 8Eclipse Kepler SR2版本提供瞭如下圖所示的配置選項來控制這個編譯器的引數。如下圖所示

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

圖1. 配置Eclipse工程以支援Java8編譯器的-parameters引數。

另外,在Parameter類裡提供了一個很有用的方法叫isNamePersent()可以被用來驗證方法名是否存在。

4. Java標準庫中的新特性

Java 8為了提供對現代化併發,函式式變成,日期/時間等等更好的支援,增加和擴充套件了許多類。

4.1. Optional

著名的NullPointerExceptionhttp://examples.javacodegeeks.com/java-basics/exceptions/java-lang-nullpointerexception-how-to-handle-null-pointer-exception/)是到目前為止導致Java應用執行失敗最常見的原因的。很多年以前,偉大的Google Guavahttp://code.google.com/p/guava-libraries/)專案引入了Optionals作為NullPointerExceptions的一個解決方法,它阻止了到處存在的null檢查對程式碼的汙染,而鼓勵開發者寫更乾淨的程式碼。受Google Guava的啟發,現在Optional成為了Java 8標準庫的一部分。

Optional是一個容器:它可以持有某個型別T的值,也可以是null。它通過提供很多有用的方法使得對null的顯式的檢查不再有必要。更多詳情請參考官方文件http://docs.oracle.com/javase/8/docs/api/

我們來看看Optional的兩個小例子:用於可以為null的值和用於不允許為null的值。

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!" ) );

isPresent()方法對非null值返回true,對null值返回false. orElseGet()方法接收一個產生預設值的函式,當Optional持有null值時會使用這個函式來產生預設值返回。map()方法把現有的Optional值轉換成新值並返回一個新的Optional例項。orElse()方法和orElseGet()方法類似,只是他接收一個預設值而不是一個產生預設值的方法。下面是這段程式碼的輸出:

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

我們來看一下另外一個例子(譯者注:Optional.of的引數如果是null會丟擲NullPointerException):

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. Streams

新增加的Stream API(java.util.stream)Java引進了現實世界的函數語言程式設計。這是到目前為止加入Java標準庫的最複雜的特性。它讓Java開發者能寫高效,乾淨和簡明的程式碼,從而極大的提高生產率。

Stream API極大的簡化了集合操作(我們後面會看到,它並不僅僅限於集合操作)。讓我們從一個簡單的Task類開始

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

Task具有points的概念(或者可以看作偽複雜度),並且有OPENCLOSED兩種狀態。然後我們把task放入一個小集合中

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

我們要解決的第一個問題是:這裡的OPEN task總共有多少個points?Java 8之前,通常的做法是用類似foreach迭代。但在Java 8中,答案會是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 );

程式碼輸出為:

Total points: 18

這裡發生了一些事情. 首先,task集合被轉換成它的stream的表現形式。然後streamfilter操作把所有狀態為CLOSEDtask都過濾掉了。下一步,mapToInt操作用每個task例項上的Task::getPoints方法把Taskstream轉換成Integerstream。最後,sum方法把所有的taskpoints加起來,產生了最終的結果。

中間操作會返回一個新的stream。他們總是延遲(lazy)的,執行一個類似filter樣的中間操作並不會真正的進行任何過濾操作,而只會產生一個新的stream。這個新的stream在遍歷的時候只會包含最初的stream中符合斷言的元素。

終端操作,如forEachsum, 會遍歷stream產生一個結果,或者一個副作用。在終端操作完成後,stream管道被認為消耗了,就不能夠在被使用了。在幾乎所有的情況下,終端操作都是即時的(eager)完成資料來源的遍歷。

Stream的另外一個有價值的特性是它生來就支援並行處理。讓我們看看下面這個例子,它把所有taskpoint加起來。

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

這和之前的那個例子很像,除了我們嘗試並行處理所有的task,並且用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例子的結果,我們來根據每個taskpoint來計算它在整個集合中所佔的比例(也可以稱為權重)。

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

控制檯輸出為:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

最後,我們曾說過,Stream API不僅僅能用在Java集合上。像讀寫文字檔案行等典型的I/O操作也能很好的被stream處理。下面這個小例子能證明這點:

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加上一個額外的close處理器。close處理器會在stream上的close()方法被呼叫的時候觸發。

Stream API加上Lambdas,方法引用,和介面的DefaultStatic方法是Java 8對軟體開發中現代程式設計正規化(paradigm?)的迴應。更多詳情請參見官方文件(http://docs.oracle.com/javase/tutorial/collections/streams/index.html

4.3. Date/Time API (JSR 310)

Java 8的另一個改進是關於日期時間的管理。他提供了新的Date-Time API(JSR 310 https://jcp.org/en/jsr/detail?id=310)。日期時間的操作是Java開發者的最痛點之一。標準的java.util.Datejava.util.Calendar一點都沒有改善這個狀態(也有爭議說它們使得情況更加混亂)。

這也是為什麼產生了Joda-Time: 一個可選的非常好的Java date/time API Java 8新的Date-Time APIJoda-Time影響頗深,它吸取了Joda-time中最好的思想。新的java.time包包含了日期,時間,日期/時間,時區,時刻,時間跨度,時鐘操作的所有API。在設計這些API的時候,不可變性被慎重考慮了:你不能夠對這些物件做任何改變(從java.util.Calendar中學到的深刻教訓)。如果需要改變,這些API會返回一個新的例項。

讓我們看看Date Time API中主要的類和它們用法的例子吧。第一個類是Clock它提供了對當前時刻以及以某個時區表示的日期和時間的操作。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

我們再來看看新類LocalDateLocalTimeLocalDate持有ISO-8601日曆體系中的不帶時區資訊的日期部分。相應的,LocalTime持有ISO-8601日曆體系中的不帶時區資訊的時間部分。LocalDateLocalTime都可以從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 );

下面是輸出:

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

LocalDateTime包含了LocalDateLocalTime.它持有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 );

輸出為:

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

下面是輸出:

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類:由秒和納秒組成的一段時間。它使得我們計算兩個日期的間隔非常簡單。讓我們來看看例子:

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

例子裡計算了2個日期,2014/04/162015/04/16之間的duration(用天和小時表示)。下面是控制檯輸出:

Duration in days: 365
Duration in hours: 8783

Java 8的新日期/時間API給我們的印象非常積極。部分原因是因為他是基於Joda-Time的,具有紮實的實戰基礎,另一部分是因為這次它終於慎重的解決問題並且聽取了開發者的聲音。更多詳情請參見官方文件http://docs.oracle.com/javase/tutorial/datetime/index.html

4.4. Nashorn JavaScript engine

Java 8帶來了新的Nashorn JavaScript引擎(http://docs.oracle.com/javase/tutorial/datetime/index.html),它允許在JVM上開發和執行某些JavaScript的應用。Nashorn JavaScript引擎是javax.script.ScriptEngine的一種實現,它遵循了同樣一組規則,允許JavaJavaScript互相呼叫。這裡是一個小例子:

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

下面是輸出:

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

4.5. Base64

Base64編碼的支援終於在java 8中被加入了Java標準庫。從下面的例子可以看出,它非常容易被使用:

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

上面例子的輸出顯示了編碼後和解碼後的文字。

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

 Base64類同樣提供了對URL友好的編碼解碼器和對MIME友好的編碼解碼器(Base64.getUrlEncoder()/Base64.getUrlDecoder(), Base64.getMimeEncoder()/Base64.getMimeDecoder()

4.6. 並行陣列

Java 8為並行陣列處理添加了許多新的方法。其中最重要的可能要數parallelSort()了,它可以在多核機器上顯著提高排序速度。下面的小例子演示了這些新的並行處理方法(parallelXxx)

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

這段程式碼用parallelSetAll()填充了20000個隨機值到一個數組中。然後用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. 併發

為了支援streams中新增的聚合操作和lambda表示式,java.util.concurrent.ConcurrentHashMap類也添加了一些新的方法。同樣,為了支援一種通用的池,java.util.concurrent.ForkJoinPool也加入了新的方法。

新加入的java.util.concurrent.locks.StampedLock類提供了基於容量的三模式讀寫控制(它可以看作是不那麼流行的java.util.concurrent.locks.ReadWriteLock的一個更好的替換方案)

以下類被新增到了java.util.concurrent.atomic包中:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

5. 新的Java工具

Java 8提供了一組命令列工具。在這部分我們來一起看看他們中最有趣的部分。

5.1. Nashorn 引擎: jjs

jjs是一個基於獨立Nashorn引擎的一個命令列工具。它接受一組JavaScript原始檔作為引數並執行他們。讓我們建立一個func.js作為例子:

function f() { 
     return 1; 
}; 

print( f() + 1 );

為了從命令列執行它,我們把它作為引數傳給jjs:

jjs func.js

控制檯輸出將是:

2

更多資訊請參見官方文件http://docs.oracle.com/javase/8/docs/technotes/tools/unix/jjs.html

5.2. 類依賴分析器: jdeps

jdeps是一個非常好的命令列工具。他能顯示Java類檔案在包或者類級別的依賴關係。他接受.class檔案,目錄 或者JAR file作為輸入。預設jdeps會把依賴關係輸出到系統輸出(控制檯)

讓我們來看流行的Spring框架的依賴報告吧。為了讓這個例子比較簡短,我們只分析一個jar檔案: org.springframework.core-3.0.5.RELESE.jar.

jdeps org.springframework.core-3.0.5.RELEASE.jar

這個命令的輸出有非常多,我們在這裡只看一下其中的一部分。依賴關係以包分組,如果classpath中找不到依賴的包,它就會顯示not found.

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

更多詳情請參見官方文件http://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html.

6. Java執行時(JVM)中的新特性

永久代(PermGen)被移掉,取而代之的是元空間Metaspace(http://www.javacodegeeks.com/2013/02/java-8-from-permgen-to-metaspace.html)JEP_122http://openjdk.java.net/jeps/122)。相應的,JVM配置項-XX:PermSize-XX:MaxPermSize也被-XX:MetaSpaceSize-XXMaxMetaspaceSize替代了。

7. 總結

未來將在這裡:Java 8通過讓開發者更有效率的特性,使得Java這個偉大的平臺更上一層樓。現在把生產系統遷移到Java 8可能還為時過早,但在接下去的幾個月後,它的接受程度必定會慢慢的增長。不過現在應該是正確的時間開始準備讓你的程式碼和Java 8相容,然後等Java 8被證明足夠安全和穩定之後可以容易地遷移過去。

作為社群對Java 8的接受程度的證明,Pivotal最近釋出了可用於生產環境的支援Java 8Spring 4.0.3

歡迎對Java 8激動人心新特性做出你的評價

8. 資源

以下是一些關於java 8特性更深入的討論:


相關推薦

Java 8 特性終極指南

第一次嘗試翻譯文章,有錯誤請見諒:) 編者注:Java 8出現在公眾視野中已經有一段時間了,在這期間,種種跡象都表明Java 8是一個非常重要的版本。 為了您閱讀的便利,我們把Java 8主要特性都收集起來放在本文中,希望您能喜歡。 目錄 1. 導言 2.1. Lambdas 和 函式介面 2.2

Java 8 特性終極手冊

原文連結,原文作者:,譯者:Justin,校對:郭蕾 1.簡介 毫無疑問,Java 8是自Java  5(2004年)釋出以來Java語言最大的一次版本升級,Java 8帶來了很多的新特性,比如編譯器、類庫、開發工具和JVM(Java虛擬機器)。在這篇教程中我們將會學習這些新特性,並通過真實例

夯實Java基礎系列21:Java8新特性終極指南

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡檢視 https://github.com/h2pl/Java-Tutorial 喜歡的話麻煩點下Star哈 文章首發於我的個人部落格: www.how2playlife.com 這是一個Java8新增特性的總

java實現websocket 終極指南

CA sys 攔截 tex ebo encoding ++ RKE UNC 1、pom中添加依賴 <dependency> <groupId>org.springframework</groupId>

五、Java 8特性 泛型的目標型別推斷(一)

一、前言 眾所周知,日期是商業邏輯計算一個關鍵的部分,任何企業應用程式都需要處理時間問題。應用程式需要知道當前的時間點和下一個時間點,有時它們還必須計算這兩個時間點之間的路徑。但java之前的日期做法太令人噁心了,我們先來吐槽一下。 二、吐槽java.util.Date跟

Java 8特性探究(1):通往lambda之路

函式式介面 函式式介面(functional interface 也叫功能性介面,其實是同一個東西)。簡單來說,函式式介面是隻包含一個方法的介面。比如Java標準庫中的java.lang.Runnable和 java.util.Comparator都是典型的函式式介面。java 8提供 @F

Java 8 Optional 良心指南,建議收藏

想學習,永遠都不晚,尤其是針對 Java 8 裡面的好東西,Optional 就是其中之一,該類提供了一種用於表示可選值而非空引用的類級別解決方案。作為一名 Java 程式設計師,我真的是煩透了 NullPointerException(NPE),儘管和它熟得就像一位老朋友,知道它也是迫不得已——程式正在使用

Java 8特性終極手冊整理

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

Java 5/Java 6/Java7/Java 8特性收集

lan 鏈接 develop new strong tar chrom eve ref 前言: Java 8對應的JDK版本為JDK8,而官網下載回來安裝的時候,文件夾上寫的是JDK1.8,同一個意思。(而這個版本命名也是有規律的,以此類推) 一、Java 5 1、h

Java 8特性1-函數式接口

實例 his sys subject 生成 license object類 acc class類 Java 8 新特性1-函數式接口 (原) Lambda表達式基本結構: (param1,param2,param3) -> {代碼塊} 例1: package

Java 8特性:5-Supplier、IntSupplier、BinaryOperator接口

point except java 8 htm import void int() uci cti (原) 這個接口很簡單,裏面只有一個抽象方法,沒有default和靜態方法。 /* * Copyright (c) 2012, 2013, Oracle and/or

JAVA 8特性 (值得學習)

java 8 新特性JAVA 8 已經出現好長時間了,大的互聯網公司很多都已經使用了,甚至很多知名互聯網公司踩過很多坑,也有一些大牛分享出了他們的實戰經驗。去很多知名的互聯網公司經常會被面試官問,你了解java 8嗎?你知道它的一些新特性嗎?好像似乎成了一面面試官必問的一道題目。這篇博文,只是簡答的介紹了一下

Java 8特性:4-Optional類

get方法 syn 序列 new ret 有一個 例子 使用 n) (原) 先看看上面的說明: /** * A container object which may or may not contain a non-null value. * If a value

Java--8--新特性--Lambda

value 需要 員工信息 span final oid function get test java9 都出來了,我才開始接觸到java8的新特性,有點脫節啊。。 Lambda是一個匿名函數,可以理解為一段可以傳遞的代碼,將代碼像數據一樣傳遞,下面是一個小例子。 pub

Java 8特性之接口改善(八惡人-1)

1.8 我想 when 直接 有一個 圖片 class java類 聖誕節 Daisy Donergue 多莫歌·黛西 “By woman, you mean her?” 她也能叫女人?   Java 8在13年9月發布,寫這篇博文的時間已經是17年12月份了。

Java 8特性之 並行和並行數組(八惡人-8

都是 class chm 請求 external syntax 匹配 main jvm Jody Domingre 多莫歌·喬迪 “How you doing, dummy?” 你還好嗎,傻瓜 一、基本介紹   Java8不僅增加了Stream,而且還增加了para

Java 8特性 - Lambda表達式(一)

ava 鏈接 article post lambda targe dash lambda表達式 java8 鏈接 Java8新特性——Lambda表達式(一)Java 8 新特性 - Lambda表達式(一)

Java 8特性

語法 空指針異常 有用 編程 using javac www. strong network Java 8 (又稱為 jdk 1.8) 是 Java 語言開發的一個主要版本。 Oracle 公司於 2014 年 3 月 18 日發布 Java 8 ,它支持函數式編程,新的

Java之Date Time API (Java 8特性)

今天 utc eating mes interval etime api int isa Java 8 – Date Time APIJava 8 comes with a much improved and much required change in the way

Java 8特性-菜鳥教程 (3) -Java 8 函數式接口

但是 style vax arr 結果 友好 face todo 兩個 Java 8 函數式接口 函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。 函數式接口可以被隱式轉換為lambda表達式。 函數式接口