Java 8 新特性概述

函數式接口java

Java 8 引入的一個核心概念是函數式接口(Functional Interfaces)。經過在接口裏面添加一個抽象方法,這些方法能夠直接從接口中運行。若是一個接口定義個惟一一個抽象方法,那麼這個接口就成爲函數式接口。同時,引入了一個新的註解:@FunctionalInterface。能夠把他它放在一個接口前,表示這個接口是一個函數式接口。這個註解是非必須的,只要接口只包含一個方法的接口,虛擬機會自動判斷,不過最好在接口上使用註解 @FunctionalInterface 進行聲明。在接口中添加了 @FunctionalInterface 的接口,只容許有一個抽象方法,不然編譯器也會報錯。程序員

java.lang.Runnable 就是一個函數式接口。算法

@FunctionalInterface

public interface Runnable {

public abstract void run();

}


函數式接口的重要屬性是:咱們可以使用 Lambda 實例化它們,Lambda 表達式讓你可以將函數做爲方法參數,或者將代碼做爲數據對待。Lambda 表達式的引入給開發者帶來了很多優勢:在 Java 8 以前,匿名內部類,監聽器和事件處理器的使用都顯得很冗長,代碼可讀性不好,Lambda 表達式的應用則使代碼變得更加緊湊,可讀性加強;Lambda 表達式使並行操做大集合變得很方便,能夠充分發揮多核 CPU 的優點,更易於爲多核處理器編寫代碼;Lambda 表達式express

Lambda 表達式由三個部分組成:第一部分爲一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數;第二部分爲一個箭頭符號:->;第三部分爲方法體,能夠是表達式和代碼塊。語法以下:編程

1. 方法體爲表達式,該表達式的值做爲返回值返回。數組

(parameters) -> expression

2. 方法體爲代碼塊,必須用 {} 來包裹起來,且須要一個 return 返回值,但若函數式接口裏面方法返回值是 void,則無需返回值。安全

(parameters) -> { statements; }

例如,下面是使用匿名內部類和 Lambda 表達式的代碼比較。服務器

下面是用匿名內部類的代碼:數據結構

button.addActionListener(new ActionListener() {

@Override

public void actionPerformed(ActionEvent e) {

System.out.print("Helllo Lambda in actionPerformed");

}

});

下面是使用 Lambda 表達式後:

button.addActionListener(

\\actionPerformed 有一個參數 e 傳入,因此用 (ActionEvent e)

(ActionEvent e)-> 

System.out.print("Helllo Lambda in actionPerformed")

);


上面是方法體包含了參數傳入 (ActionEvent e),若是沒有參數則只需 ( ),例如 Thread 中的 run 方法就沒有參數傳入,當它使用 Lambda 表達式後:併發

Thread t = new Thread(

\\run 沒有參數傳入,因此用 (), 後面用 {} 包起方法體

() -> {

 System.out.println("Hello from a thread in run");

}

);

經過上面兩個代碼的比較能夠發現使用 Lambda 表達式能夠簡化代碼,並提升代碼的可讀性。

爲了進一步簡化 Lambda 表達式,可使用方法引用。例如,下面三種分別是使用內部類,使用 Lambda 表示式和使用方法引用方式的比較:

//1. 使用內部類

Function<Integer, String> f = new Function<Integer,String>(){

@Override

public String apply(Integer t) {

return null;

}

};

//2. 使用 Lambda 表達式

Function<Integer, String> f2 = (t)->String.valueOf(t); 

//3. 使用方法引用的方式

Function<Integer, String> f1 = String::valueOf;

要使用 Lambda 表達式,須要定義一個函數式接口,這樣每每會讓程序充斥着過量的僅爲 Lambda 表達式服務的函數式接口。爲了減小這樣過量的函數式接口,Java 8 在 java.util.function 中增長了很多新的函數式通用接口。例如:

Function<T, R>:將 T 做爲輸入,返回 R 做爲輸出,他還包含了和其餘函數組合的默認方法。

Predicate<T> :將 T 做爲輸入,返回一個布爾值做爲輸出,該接口包含多種默認方法來將 Predicate 組合成其餘複雜的邏輯(與、或、非)。

Consumer<T> :將 T 做爲輸入,不返回任何內容,表示在單個參數上的操做。
接口的加強

例如,People 類中有一個方法 getMaleList 須要獲取男性的列表,這裏須要定義一個函數式接口 PersonInterface:

interface PersonInterface {

 public boolean test(Person person);

}

public class People {

 private List<Person> persons= new ArrayList<Person>();

 public List<Person> getMaleList(PersonInterface filter) {

 List<Person> res = new ArrayList<Person>();

 persons.forEach(

 (Person person) -> 

 {

 if (filter.test(person)) {//調用 PersonInterface 的方法

 res.add(person);

 }

 }

 );

 return res;

 }

}

爲了去除 PersonInterface 這個函數式接口,能夠用通用函數式接口 Predicate 替代以下:

class People{

 private List<Person> persons= new ArrayList<Person>();

 public List<Person> getMaleList(Predicate<Person> predicate) {

 List<Person> res = new ArrayList<Person>();

 persons.forEach(

 person -> {

 if (predicate.test(person)) {//調用 Predicate 的抽象方法 test

 res.add(person);

 }

 });

 return res;

 }

}

Java 8 對接口作了進一步的加強。在接口中能夠添加使用 default 關鍵字修飾的非抽象方法。還能夠在接口中定義靜態方法。現在,接口看上去與抽象類的功能愈來愈相似了。

默認方法

Java 8 還容許咱們給接口添加一個非抽象的方法實現,只須要使用 default 關鍵字便可,這個特徵又叫作擴展方法。在實現該接口時,該默認擴展方法在子類上能夠直接使用,它的使用方式相似於抽象類中非抽象成員方法。但擴展方法不可以重載 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重載。

例如,下面接口中定義了一個默認方法 count(),該方法能夠在子類中直接使用。
靜態方法

public interface DefaultFunInterface {

//定義默認方法 count

default int count(){

return 1;

}

}

public class SubDefaultFunClass implements DefaultFunInterface {

public static void main(String[] args){

//實例化一個子類對象,改子類對象能夠直接調用父接口中的默認方法 count

 SubDefaultFunClass sub = new SubDefaultFunClass();

sub.count();

}

}

在接口中,還容許定義靜態的方法。接口中的靜態方法能夠直接用接口來調用。

例如,下面接口中定義了一個靜態方法 find,該方法能夠直接用 StaticFunInterface .find() 來調用。
集合之流式操做

public interface StaticFunInterface {

public static int find(){

return 1;

}

}

public class TestStaticFun {

public static void main(String[] args){

//接口中定義了靜態方法 find 直接被調用

StaticFunInterface.fine();

}

}

Java 8 引入了流式操做(Stream),經過該操做能夠實現對集合(Collection)的並行處理和函數式操做。根據操做返回的結果不一樣,流式操做分爲中間操做和最終操做兩種。最終操做返回一特定類型的結果,而中間操做返回流自己,這樣就能夠將多個操做依次串聯起來。根據流的併發性,流又能夠分爲串行和並行兩種。流式操做實現了集合的過濾、排序、映射等功能。

Stream 和 Collection 集合的區別:Collection 是一種靜態的內存數據結構,而 Stream 是有關計算的。前者是主要面向內存,存儲在內存中,後者主要是面向 CPU,經過 CPU 實現計算。

串行和並行的流

流有串行和並行兩種,串行流上的操做是在一個線程中依次完成,而並行流則是在多個線程上同時執行。並行與串行的流能夠相互切換:經過 stream.sequential() 返回串行的流,經過 stream.parallel() 返回並行的流。相比較串行的流,並行的流能夠很大程度上提升程序的執行效率。

下面是分別用串行和並行的方式對集合進行排序。

串行排序:

List<String> list = new ArrayList<String>();

for(int i=0;i<1000000;i++){

double d = Math.random()*1000;

list.add(d+"");

}

long start = System.nanoTime();//獲取系統開始排序的時間點

int count= (int) ((Stream) list.stream().sequential()).sorted().count();

long end = System.nanoTime();//獲取系統結束排序的時間點

long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//獲得串行排序所用的時間

System.out.println(ms+」ms」);


並行排序:

List<String> list = new ArrayList<String>();

for(int i=0;i<1000000;i++){

double d = Math.random()*1000;

list.add(d+"");

}

long start = System.nanoTime();//獲取系統開始排序的時間點

int count = (int)((Stream) list.stream().parallel()).sorted().count();

long end = System.nanoTime();//獲取系統結束排序的時間點

long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//獲得並行排序所用的時間

System.out.println(ms+」ms」);

串行輸出爲 1200ms,並行輸出爲 800ms。可見,並行排序的時間相比較串行排序時間要少很多。

中間操做

該操做會保持 stream 處於中間狀態,容許作進一步的操做。它返回的仍是的 Stream,容許更多的鏈式操做。常見的中間操做有:

filter():對元素進行過濾;

sorted():對元素排序;

map():元素的映射;

distinct():去除重複元素;

subStream():獲取子 Stream 等。

例如,下面是對一個字符串集合進行過濾,返回以「s」開頭的字符串集合,並將該集合依次打印出來:

list.stream()

.filter((s) -> s.startsWith("s"))

.forEach(System.out::println);

這裏的 filter(...) 就是一箇中間操做,該中間操做能夠鏈式地應用其餘 Stream 操做。

終止操做

該操做必須是流的最後一個操做,一旦被調用,Stream 就到了一個終止狀態,並且不能再使用了。常見的終止操做有:

forEach():對每一個元素作處理;

toArray():把元素導出到數組;

findFirst():返回第一個匹配的元素;

anyMatch():是否有匹配的元素等。

例如,下面是對一個字符串集合進行過濾,返回以「s」開頭的字符串集合,並將該集合依次打印出來:

list.stream() //獲取列表的 stream 操做對象

.filter((s) -> s.startsWith("s"))//對這個流作過濾操做

.forEach(System.out::println);

這裏的 forEach(...) 就是一個終止操做,該操做以後不能再鏈式的添加其餘操做了。

註解的更新

對於註解,Java 8 主要有兩點改進:類型註解和重複註解。

Java 8 的類型註解擴展了註解使用的範圍。在該版本以前,註解只能是在聲明的地方使用。如今幾乎能夠爲任何東西添加註解:局部變量、類與接口,就連方法的異常也能添加註解。新增的兩個註釋的程序元素類型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用來描述註解的新場合。ElementType.TYPE_PARAMETER 表示該註解能寫在類型變量的聲明語句中。而 ElementType.TYPE_USE 表示該註解能寫在使用類型的任何語句中(例如聲明語句、泛型和強制轉換語句中的類型)。

對類型註解的支持,加強了經過靜態分析工具發現錯誤的能力。原先只能在運行時發現的問題能夠提早在編譯的時候被排查出來。Java 8 自己雖然沒有自帶類型檢測的框架,但能夠經過使用 Checker Framework 這樣的第三方工具,自動檢查和確認軟件的缺陷,提升生產效率。

例如,下面的代碼能夠經過編譯,可是運行時會報 NullPointerException 的異常。

public class TestAnno {

public static void main(String[] args) {

Object obj = null;

obj.toString();

}

}

 

爲了能在編譯期間就自動檢查出這類異常,能夠經過類型註解結合 Checker Framework 提早排查出來:

import org.checkerframework.checker.nullness.qual.NonNull;

public class TestAnno {

public static void main(String[] args) {

@NonNull Object obj = null;

obj.toString();

}

}

 

 

編譯時自動檢測結果以下:

C:\workspace\TestJava8\src\TestAnno.java:4: Warning:

  (assignment.type.incompatible) $$ 2 $$ null $$ @UnknownInitialization @NonNull Object $$ ( 152, 156 )

  $$ incompatible types in assignment.

@NonNull Object obj = null;

 ^

 found : null

 required: @UnknownInitialization @NonNull Object

 

另外,在該版本以前使用註解的一個限制是相同的註解在同一位置只能聲明一次,不能聲明屢次。Java 8 引入了重複註解機制,這樣相同的註解能夠在同一地方聲明屢次。重複註解機制自己必須用 @Repeatable 註解。

例如,下面就是用 @Repeatable 重複註解的例子:

@Retention(RetentionPolicy.RUNTIME) \\該註解存在於類文件中並在運行時能夠經過反射獲取

@interface Annots {

Annot[] value();

} 

@Retention(RetentionPolicy.RUNTIME) \\該註解存在於類文件中並在運行時能夠經過反射獲取

@Repeatable(Annots.class)

@interface Annot {

String value();

}

@Annot("a1")@Annot("a2")

public class Test {

public static void main(String[] args) {

Annots annots1 = Test.class.getAnnotation(Annots.class);

System.out.println(annots1.value()[0]+","+annots1.value()[1]); 

// 輸出: @Annot(value=a1),@Annot(value=a2)

Annot[] annots2 = Test.class.getAnnotationsByType(Annot.class);

System.out.println(annots2[0]+","+annots2[1]); 

// 輸出: @Annot(value=a1),@Annot(value=a2)

}

}

 

註釋 Annot 被 @Repeatable( Annots.class ) 註解。Annots 只是一個容器,它包含 Annot 數組, 編譯器盡力向程序員隱藏它的存在。經過這樣的方式,Test 類能夠被 Annot 註解兩次。重複註釋的類型能夠經過 getAnnotationsByType() 方法來返回。

安全性

現今,互聯網環境中存在各類各類潛在的威脅,對於 Java 平臺來講,安全顯得特別重要。爲了保證新版本具備更高的安全性,Java 8 在安全性上對許多方面進行了加強,也爲此推遲了它的發佈日期。下面例舉其中幾個關於安全性的更新:

支持更強的基於密碼的加密算法。基於 AES 的加密算法,例如 PBEWithSHA256AndAES_128 和 PBEWithSHA512AndAES_256,已經被加入進來。

在客戶端,TLS1.1 和 TLS1.2 被設爲默認啓動。而且能夠經過新的系統屬性包 jdk.tls.client.protocols 來對它進行配置。

Keystore 的加強,包含新的 Keystore 類型 java.security.DomainLoadStoreParameter 和爲 Keytool 這個安全鑰匙和證書的管理工具添加新的命令行選項-importpassword。同時,添加和更新了一些關於安全性的 API 來支持 KeyStore 的更新。

支持安全的隨機數發生器。若是隨機數來源於隨機性不高的種子,那麼那些用隨機數來產生密鑰或者散列敏感信息的系統就更易受攻擊。SecureRandom 這個類的 getInstanceStrong 方法現在能夠獲取各個平臺最強的隨機數對象實例,經過這個實例生成像 RSA 私鑰和公鑰這樣具備較高熵的隨機數。

JSSE(Java(TM) Secure Socket Extension)服務器端開始支持 SSL/TLS 服務器名字識別 SNI(Server Name Indication)擴展。SNI 擴展目的是 SSL/TLS 協議能夠經過 SNI 擴展來識別客戶端試圖經過握手協議鏈接的服務器名字。在 Java 7 中只在客戶端默認啓動 SNI 擴展。現在,在 JSSE 服務器端也開始支持 SNI 擴展了。

安全性比較差的加密方法被默認禁用。默認不支持 DES 相關的 Kerberos 5 加密方法。若是必定要使用這類弱加密方法須要在 krb5.conf 文件中添加 allow_weak_crypto=true。考慮到這類加密方法安全性極差,開發者應該儘可能避免使用它。

IO/NIO 的改進

Java 8 對 IO/NIO 也作了一些改進。主要包括:改進了 java.nio.charset.Charset 的實現,使編碼和解碼的效率得以提高,也精簡了 jre/lib/charsets.jar 包;優化了 String(byte[],*) 構造方法和 String.getBytes() 方法的性能;還增長了一些新的 IO/NIO 方法,使用這些方法能夠從文件或者輸入流中獲取流(java.util.stream.Stream),經過對流的操做,能夠簡化文本行處理、目錄遍歷和文件查找。

新增的 API 以下:

BufferedReader.line(): 返回文本行的流 Stream<String>

File.lines(Path, Charset):返回文本行的流 Stream<String>

File.list(Path): 遍歷當前目錄下的文件和目錄

File.walk(Path, int, FileVisitOption): 遍歷某一個目錄下的全部文件和指定深度的子目錄

File.find(Path, int, BiPredicate, FileVisitOption... ): 查找相應的文件

下面就是用流式操做列出當前目錄下的全部文件和目錄:

Files.list(new File(".").toPath())

 .forEach(System.out::println);

全球化功能

Java 8 版本還完善了全球化功能:支持新的 Unicode 6.2.0 標準,新增了日曆和本地化的 API,改進了日期時間的管理等。

Java 的日期與時間 API 問題由來已久,Java 8 以前的版本中關於時間、日期及其餘時間日期格式化類因爲線程安全、重量級、序列化成本高等問題而飽受批評。Java 8 吸取了 Joda-Time 的精華,以一個新的開始爲 Java 建立優秀的 API。新的 java.time 中包含了全部關於時鐘(Clock),本地日期(LocalDate)、本地時間(LocalTime)、本地日期時間(LocalDateTime)、時區(ZonedDateTime)和持續時間(Duration)的類。歷史悠久的 Date 類新增了 toInstant() 方法,用於把 Date 轉換成新的表示形式。這些新增的本地化時間日期 API 大大簡化了了日期時間和本地化的管理。

例如,下面是對 LocalDate,LocalTime 的簡單應用:

//LocalDate

LocalDate localDate = LocalDate.now(); //獲取本地日期

localDate = LocalDate.ofYearDay(2014, 200); // 得到 2014 年的第 200 天 

System.out.println(localDate.toString());//輸出:2014-07-19

localDate = LocalDate.of(2014, Month.SEPTEMBER, 10); //2014 年 9 月 10 日 

System.out.println(localDate.toString());//輸出:2014-09-10

//LocalTime

LocalTime localTime = LocalTime.now(); //獲取當前時間

System.out.println(localTime.toString());//輸出當前時間

localTime = LocalTime.of(10, 20, 50);//得到 10:20:50 的時間點

System.out.println(localTime.toString());//輸出: 10:20:50

//Clock 時鐘

Clock clock = Clock.systemDefaultZone();//獲取系統默認時區 (當前瞬時時間 )

long millis = clock.millis();//

 

Java 8 開發環境

隨着 Java 8 正式發佈,許多 IDE 也開始提供對 Java 8 的支持。Eclipse 是 Java 開發人員最爲經常使用集成開發環境,在最新的 Eclipse Kepler 4.3.2 版本中已經默認增長了對 Java 8 的支持。要想在 Eclipse Kepler 的前期版本中添加對 Java 8 的支持,能夠經過下面步驟來完成:

1. 選擇 "Help > Eclipse Marketplace..."。

2. 在搜索框中輸入 "Java 8 Kepler"。

3. 點擊安裝 Java 8 support for Eclipse Kepler SR2。

圖 1. 安裝 Java 8 support for Eclipse Kepler SR2

接下來,就能夠開啓 Java 8 編程之旅。

圖 2. Eclipse 編寫的 Java 8 程序和運行結果:

結束語

Java 8 正式版是一個有重大改變的版本,該版本對 Java 作了重大改進。本文經過文字描述及代碼實例對新版本中主要新特性作了介紹:函數式接口、Lambda 表達式、集合的流式操做、註解、安全性、IO/NIO、全球化功能。除了文中介紹的這些重要的新功能以外,Java 8 還對 java 工具包 JDBC、Java DB、JavaFX 等方面都有許多改進和加強。這些新增功能簡化了開發,提高了代碼可讀性,加強了代碼的安全性,提升了代碼的執行效率,爲開發者帶來了全新的 Java 開發體驗,從而推進了 Java 這個平臺的前進

相關文章
相關標籤/搜索