編者注:Java 8已經公佈有一段時間了,種種跡象代表Java 8是一個有重大改變的發行版。javascript
在Java Code Geeks上已經有大量的關於Java 8 的教程了,像玩轉Java 8——lambda與併發,Java 8 Date Time API 教程: LocalDateTime和在Java 8中抽象類與接口的比較。html
咱們也在其餘地方引用了15個必讀的Java 8教程。固然,咱們也探究了Java 8的一些不足之處,好比Java 8的「黑暗面」。java
如今,是時候把全部Java 8的重要特性收集整理成一篇單獨的文章了,但願這篇文章能給你帶來閱讀上的樂趣。開始吧!git
2.1 Lambdas表達式與Functional接口程序員
2.2 接口的默認與靜態方法github
2.3 方法引用spring
2.4 重複註解shell
2.5 更好的類型推測機制express
2.6 擴展註解的支持apache
3.1 參數名字
4.1 Optional
4.2 Streams
4.5 Base64
4.6 並行(parallel)數組
4.7 併發(Concurrency)
5.1 Nashorn引擎: jjs
5.2 類依賴分析器: jdeps
毫無疑問,Java 8發行版是自Java 5(發行於2004,已通過了至關一段時間了)以來最具革命性的版本。Java 8 爲Java語言、編譯器、類庫、開發工具與JVM(Java虛擬機)帶來了大量新特性。在這篇教程中,咱們將一一探索這些變化,並用真實的例子說明它們適用的場景。
這篇教程由如下幾部分組成,它們分別涉及到Java平臺某一特定方面的內容:
無論怎麼說,Java 8都是一個變化巨大的版本。你可能認爲Java 8耗費了大量的時間才得以完成是爲了實現了每一個Java程序員所期待的特性。在這個小節裏,咱們將會涉及到這些特性的大部分。
Lambda表達式(也稱爲閉包)是整個Java 8發行版中最受期待的在Java語言層面上的改變,Lambda容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中),或者把代碼當作數據:函數式程序員對這一律念很是熟悉。在JVM平臺上的不少語言(Groovy,Scala,……)從一開始就有Lambda,可是Java程序員不得不使用毫無新意的匿名類來代替lambda。
關於Lambda設計的討論佔用了大量的時間與社區的努力。可喜的是,最終找到了一個平衡點,使得可使用一種即簡潔又緊湊的新方式來構造Lambdas。在最簡單的形式中,一個lambda能夠由用逗號分隔的參數列表、–>符號與函數體三部分表示。例如:
1
|
Arrays.asList(
"a"
,
"b"
,
"d"
).forEach( e -> System.out.println( e ) );
|
請注意參數e的類型是由編譯器推測出來的。同時,你也能夠經過把參數類型與參數包括在括號中的形式直接給出參數的類型:
1
|
Arrays.asList(
"a"
,
"b"
,
"d"
).forEach( ( String e ) -> System.out.println( e ) );
|
在某些狀況下lambda的函數體會更加複雜,這時能夠把函數體放到在一對花括號中,就像在Java中定義普通函數同樣。例如:
1
2
3
4
|
Arrays.asList(
"a"
,
"b"
,
"d"
).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
|
Lambda能夠引用類的成員變量與局部變量(若是這些變量不是final的話,它們會被隱含的轉爲final,這樣效率更高)。例如,下面兩個代碼片斷是等價的:
1
2
3
|
String separator =
","
;
Arrays.asList(
"a"
,
"b"
,
"d"
).forEach(
( String e ) -> System.out.print( e + separator ) );
|
和:
1
2
3
|
final
String separator =
","
;
Arrays.asList(
"a"
,
"b"
,
"d"
).forEach(
( String e ) -> System.out.print( e + separator ) );
|
Lambda可能會返回一個值。返回值的類型也是由編譯器推測出來的。若是lambda的函數體只有一行的話,那麼沒有必要顯式使用return語句。下面兩個代碼片斷是等價的:
1
|
Arrays.asList(
"a"
,
"b"
,
"d"
).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
|
和:
1
2
3
4
|
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 8中全部類庫的已有接口都添加了@FunctionalInterface註解)。讓咱們看一下這種函數式接口的定義:
1
2
3
4
|
@FunctionalInterface
public
interface
Functional {
void
method();
}
|
須要記住的一件事是:默認方法與靜態方法並不影響函數式接口的契約,能夠任意使用:
1
2
3
4
5
6
7
|
@FunctionalInterface
public
interface
FunctionalDefaultMethods {
void
method();
default
void
defaultMethod() {
}
}
|
Lambda是Java 8最大的賣點。它具備吸引愈來愈多程序員到Java平臺上的潛力,而且可以在純Java語言環境中提供一種優雅的方式來支持函數式編程。更多詳情能夠參考官方文檔。
Java 8用默認方法與靜態方法這兩個新概念來擴展接口的聲明。默認方法使接口有點像Traits(Scala中特徵(trait)相似於Java中的Interface,但它能夠包含實現代碼,也就是目前Java8新增的功能),但與傳統的接口又有些不同,它容許在已有的接口中添加新方法,而同時又保持了與舊版本代碼的兼容性。
默認方法與抽象方法不一樣之處在於抽象方法必需要求實現,可是默認方法則沒有這個要求。相反,每一個接口都必須提供一個所謂的默認實現,這樣全部的接口實現者將會默認繼承它(若是有必要的話,能夠覆蓋這個默認實現)。讓咱們看看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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(),Defaulable接口的實現者之一DefaultableImpl實現了這個接口,而且讓默認方法保持原樣。Defaulable接口的另外一個實現者OverridableImpl用本身的方法覆蓋了默認方法。
Java 8帶來的另外一個有趣的特性是接口能夠聲明(而且能夠提供實現)靜態方法。例如:
1
2
3
4
5
6
|
private
interface
DefaulableFactory {
// Interfaces now allow static methods
static
Defaulable create( Supplier< Defaulable > supplier ) {
return
supplier.get();
}
}
|
下面的一小段代碼片斷把上面的默認方法與靜態方法黏合到一塊兒。
1
2
3
4
5
6
7
|
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() );
}
|
這個程序的控制檯輸出以下:
1
2
|
Default implementation
Overridden implementation
|
在JVM中,默認方法的實現是很是高效的,而且經過字節碼指令爲方法調用提供了支持。默認方法容許繼續使用現有的Java接口,而同時可以保障正常的編譯過程。這方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……
儘管默認方法很是強大,可是在使用默認方法時咱們須要當心注意一個地方:在聲明一個默認方法前,請仔細思考是否是真的有必要使用默認方法,由於默認方法會帶給程序歧義,而且在複雜的繼承體系中容易產生編譯錯誤。更多詳情請參考官方文檔
方法引用提供了很是有用的語法,能夠直接引用已有Java類或對象(實例)的方法或構造器。與lambda聯合使用,方法引用可使語言的構造更緊湊簡潔,減小冗餘代碼。
下面,咱們以定義了4個方法的Car這個類做爲例子,區分Java中支持的4種不一樣的方法引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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。請注意構造器沒有參數。
1
2
|
final
Car car = Car.create( Car::
new
);
final
List< Car > cars = Arrays.asList( car );
|
第二種方法引用是靜態方法引用,它的語法是Class::static_method。請注意這個方法接受一個Car類型的參數。
1
|
cars.forEach( Car::collide );
|
第三種方法引用是特定類的任意對象的方法引用,它的語法是Class::method。請注意,這個方法沒有參數。
1
|
cars.forEach( Car::repair );
|
最後,第四種方法引用是特定對象的方法引用,它的語法是instance::method。請注意,這個方法接受一個Car類型的參數
1
2
|
final
Car police = Car.create( Car::
new
);
cars.forEach( police::follow );
|
運行上面的Java程序在控制檯上會有下面的輸出(Car的實例可能不同):
1
2
3
|
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
|
關於方法引用的更多詳情請參考官方文檔。
自從Java 5引入了註解機制,這一特性就變得很是流行而且廣爲使用。然而,使用註解的一個限制是相同的註解在同一位置只能聲明一次,不能聲明屢次。Java 8打破了這條規則,引入了重複註解機制,這樣相同的註解能夠在同一地方聲明屢次。
重複註解機制自己必須用@Repeatable註解。事實上,這並非語言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。讓咱們看一個快速入門的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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編譯器並不想讓程序員意識到Filters的存在。這樣,接口Filterable就擁有了兩次Filter(並無提到Filter)註解。
同時,反射相關的API提供了新的函數getAnnotationsByType()來返回重複註解的類型(請注意Filterable.class.getAnnotation( Filters.class )經編譯器處理後將會返回Filters的實例)。
程序輸出結果以下:
1
2
|
filter1
filter2
|
更多詳情請參考官方文檔
Java 8在類型推測方面有了很大的提升。在不少狀況下,編譯器能夠推測出肯定的參數類型,這樣就能使代碼更整潔。讓咱們看一個例子:
1
2
3
4
5
6
7
8
9
10
11
|
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 >類型的用法。
1
2
3
4
5
6
7
8
|
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()。
Java 8擴展了註解的上下文。如今幾乎能夠爲任何東西添加註解:局部變量、泛型類、父類與接口的實現,就連方法的異常也能添加註解。下面演示幾個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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_USE和ElementType.TYPE_PARAMETER是兩個新添加的用於描述適當的註解上下文的元素類型。在Java語言中,註解處理API也有小的改動來識別新增的類型註解。
很長一段時間裏,Java程序員一直在發明不一樣的方式使得方法參數的名字能保留在Java字節碼中,而且可以在運行時獲取它們(好比,Paranamer類庫)。最終,在Java 8中把這個強烈要求的功能添加到語言層面(經過反射API與Parameter.getName()方法)與字節碼文件(經過新版的javac的–parameters選項)中。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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參數來編譯這個類,而後運行這個類,會獲得下面的輸出:
1
|
Parameter: arg0
|
若是使用–parameters參數來編譯這個類,程序的結構會有所不一樣(參數的真實名字將會顯示出來):
1
|
Parameter: args
|
對於有經驗的Maven用戶,經過maven-compiler-plugin的配置能夠將-parameters參數添加到編譯器中去。
1
2
3
4
5
6
7
8
9
10
|
<
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 8最新發布的Eclipse Kepler SR2(請檢查這裏的下載說明)提供了很是實用的配置選項,能夠經過下圖的配置方式來控制編譯器行爲
圖1. 配置Eclipse工程使之支持Java 8編譯器的新特性——parameters參數此外,Parameter類有一個很方便的方法isNamePresent()來驗證是否能夠獲取參數的名字。
Java 8 經過增長大量新類,擴展已有類的功能的方式來改善對併發編程、函數式編程、日期/時間相關操做以及其餘更多方面的支持。
到目前爲止,臭名昭著的空指針異常是致使Java應用程序失敗的最多見緣由。之前,爲了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava經過使用檢查空值的方式來防止代碼污染,它鼓勵程序員寫更乾淨的代碼。受到Google Guava的啓發,Optional類已經成爲Java 8類庫的一部分。
Optional其實是個容器:它能夠保存類型T的值,或者僅僅保存null。Optional提供不少有用的方法,這樣咱們就不用顯式進行空值檢測。更多詳情請參考官方文檔。
咱們下面用兩個小例子來演示如何使用Optional類:一個容許爲空值,一個不容許爲空值。
1
2
3
4
|
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。爲了防止Optional爲空值,orElseGet()方法經過回調函數來產生一個默認值。map()函數對當前Optional的值進行轉化,而後返回一個新的Optional實例。orElse()方法和orElseGet()方法相似,可是orElse接受一個默認值而不是一個回調函數。下面是這個程序的輸出:
1
2
3
|
Full Name is
set
?
false
Full Name: [none]
Hey Stranger!
|
讓咱們來看看另外一個例子:
1
2
3
4
5
|
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();
|
下面是程序的輸出:
1
2
3
|
First Name is
set
?
true
First Name: Tom
Hey Tom!
|
更多詳情請參考官方文檔
最新添加的Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。這是目前爲止對Java類庫最好的補充,由於Stream API能夠極大提供Java程序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。
Stream API極大簡化了集合框架的處理(但它的處理的範圍不只僅限於集合框架的處理,這點後面咱們會看到)。讓咱們以一個簡單的Task類爲例進行介紹:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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類有一個分數的概念(或者說是僞複雜度),其次是還有一個值能夠爲OPEN或CLOSED的狀態.讓咱們引入一個Task的小集合做爲演示例子:
1
2
3
4
5
|
final
Collection< Task > tasks = Arrays.asList(
new
Task( Status.OPEN,
5
),
new
Task( Status.OPEN,
13
),
new
Task( Status.CLOSED,
8
)
);
|
咱們下面要討論的第一個問題是全部狀態爲OPEN的任務一共有多少分數?在Java 8之前,通常的解決方式用foreach循環,可是在Java 8裏面咱們可使用stream:一串支持連續、並行彙集操做的元素。
1
2
3
4
5
6
7
8
|
// 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 );
|
程序在控制檯上的輸出以下:
1
|
Total points: 18
|
這裏有幾個注意事項。第一,task集合被轉換化爲其相應的stream表示。而後,filter操做過濾掉狀態爲CLOSED的task。下一步,mapToInt操做經過Task::getPoints這種方式調用每一個task實例的getPoints方法把Task的stream轉化爲Integer的stream。最後,用sum函數把全部的分數加起來,獲得最終的結果。
在繼續講解下面的例子以前,關於stream有一些須要注意的地方(詳情在這裏).stream操做被分紅了中間操做與最終操做這兩種。
中間操做返回一個新的stream對象。中間操做老是採用惰性求值方式,運行一個像filter這樣的中間操做實際上沒有進行任何過濾,相反它在遍歷元素時會產生了一個新的stream對象,這個新的stream對象包含原始stream
中符合給定謂詞的全部元素。
像forEach、sum這樣的最終操做可能直接遍歷stream,產生一個結果或反作用。當最終操做執行結束以後,stream管道被認爲已經被消耗了,沒有可能再被使用了。在大多數狀況下,最終操做都是採用及早求值方式,及早完成底層數據源的遍歷。
stream另外一個有價值的地方是可以原生支持並行處理。讓咱們來看看這個算task分數和的例子。
1
2
3
4
5
6
7
8
|
// 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 );
|
這個例子和第一個例子很類似,但這個例子的不一樣之處在於這個程序是並行運行的,其次使用reduce方法來算最終的結果。
下面是這個例子在控制檯的輸出:
1
|
Total points (all tasks): 26.0
|
常常會有這個一個需求:咱們須要按照某種準則來對集合中的元素進行分組。Stream也能夠處理這樣的需求,下面是一個例子:
1
2
3
4
5
|
// Group tasks by their status
final
Map< Status, List< Task > > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
|
這個例子的控制檯輸出以下:
1
|
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
|
讓咱們來計算整個集合中每一個task分數(或權重)的平均值來結束task的例子。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 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 );
|
下面是這個例子的控制檯輸出:
1
|
[19%, 50%, 30%]
|
最後,就像前面提到的,Stream API不只僅處理Java集合框架。像從文本文件中逐行讀取數據這樣典型的I/O操做也很適合用Stream API來處理。下面用一個例子來應證這一點。
1
2
3
4
|
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對現代軟件開發範式的迴應。更多詳情請參考官方文檔。
Java 8經過發佈新的Date-Time API (JSR 310)來進一步增強對日期與時間的處理。對日期與時間的操做一直是Java程序員最痛苦的地方之一。標準的 java.util.Date以及後來的java.util.Calendar一點沒有改善這種狀況(能夠這麼說,它們必定程度上更加複雜)。
這種狀況直接致使了Joda-Time——一個可替換標準日期/時間處理且功能很是強大的Java API的誕生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影響,而且吸收了其精髓。新的java.time包涵蓋了全部處理日期,時間,日期/時間,時區,時刻(instants),過程(during)與時鐘(clock)的操做。在設計新版API時,十分注重與舊版API的兼容性:不容許有任何的改變(從java.util.Calendar中獲得的深入教訓)。若是須要修改,會返回這個類的一個新實例。
讓咱們用例子來看一下新版API主要類的使用方法。第一個是Clock類,它經過指定一個時區,而後就能夠獲取到當前的時刻,日期與時間。Clock能夠替換System.currentTimeMillis()與TimeZone.getDefault()。
1
2
3
4
|
// Get the system clock as UTC offset
final
Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
|
下面是程序在控制檯上的輸出:
1
2
|
2014-04-12T15:19:29.282Z
1397315969360
|
咱們須要關注的其餘類是LocaleDate與LocalTime。LocaleDate只持有ISO-8601格式且無時區信息的日期部分。相應的,LocaleTime只持有ISO-8601格式且無時區信息的時間部分。LocaleDate與LocalTime均可以從Clock中獲得。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 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 );
|
下面是程序在控制檯上的輸出:
1
2
3
4
|
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
|
LocaleDateTime把LocaleDate與LocaleTime的功能合併起來,它持有的是ISO-8601格式無時區信息的日期與時間。下面是一個快速入門的例子。
1
2
3
4
|