Java 8 特性 – 終極手冊

Java 8 特性 – 終極手冊


原文地址

http://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html


1.簡介

毫無疑問,Java 8是自Java  5發佈以來最最重要的一次發佈,這次發佈給Java帶來了很多的新特性,包括編譯器、Java庫、工具和JVM本身等等。在這份指南里我們將會瀏覽所有的這些改變,並且演示在不同場景下的用法。

本指南由下面及部分組成,每部分都涉及到Java平臺的細節

  • 語言
  • 編譯器
  • Java庫
  • 工具
  • 運行期(Java虛擬機)

2.Java的新特性

有人也許會說爲了實現那些每個Java程序員都期待的新特性花費了非常多的時間,但是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方法一樣,栗子如下:

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

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

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

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

控制檯的輸出如下:

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

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

我們可以看到,註釋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;

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擴展了註釋可以使用的範圍,現在我們幾乎可以在所有的地方:局部變量、泛型、超類和接口實現、甚至是方法的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<>();
}
}

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

如果你編譯這個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 提供了編譯設置項,如下圖所示:


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

Task有一個屬性是點數(虛擬的任務複雜度),Task可以是開放的或者關閉的。讓我們引入一個小的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 );

控制檯的輸出將會是:

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

這個例子跟上面那個非常像,除了這個例子裏使用了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 );

控制檯輸出如下:

[19%, 50%, 30%]

之前我們提到Stream API不僅僅可以用在Java集合上,從文本文件一行一行讀取文本內容很容易從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,當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 );

控制檯輸出如下:

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

輸出如下:

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

上面的例子計算了兩個日期(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;" ) );

 

輸出如下:

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

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

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

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

(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() 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   併發

Java 8在java.util.concurrent.ConcurrentHashMap新添加了方法來支持聚集操作。這些聚集操作基於Java 8新引入的Stream和lambda表達式。 java.util.concurrent.ForkJoinPool也添加了新的方法來支持Common Pool(Common pool可以被所有的ForkAndJoinTask共用)

Java8新添加的類 java.util.concurrent.locks.StampedLock提供基於能力的鎖,這個鎖提供三種模式來對對鞋進行訪問控制(可能可以考慮用它來替換不太有名的 java.util.concurrent.locks.ReadWriteLock)。

在 java.util.concurrent.atomic裏也添加了下面一些新的類

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

 

5         新的工具

Java 8 提供了一些新的命令行工具,這一章我們會粗略地看一下這些工具有意思的地方。

5.1  Nashorn引擎:jjs

jjs是一個基於標準的Noshorn引擎,它接受javascript文件列表作爲參數。作爲例子,讓我們創建一個func.js, 內容如下:

function f() {
return 1;
};

print( f() + 1 );

運行這個文件,把這個文件作爲參數傳給jjs

jjs func.js

輸出結果如下

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.

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

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

6         JVM的新特性

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

7         結論

未來就在這裏,Java8通過提供新的特性讓開發人員提高生產率從而推動Java這個平臺前進。現在將生產系統移植到Java還太早,但是接下來的幾個月它的採用率會慢慢地增長。不管怎麼樣,現在是開始準備讓你的代碼兼容Java 8,準備好使用Java 8,當Java 8已經被證實足夠安全和穩定就可以使用了。

作爲社區對Java 8的支持,Pivotal發佈了Spring 4.0.3, 支持Java 8。

 8         資源

更多的一些資源更深入地討論了Java 8的一些特性:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章