JDK 7,8,9 進行對比 新特性
JDK 新特性
主要對 java 7,8,9 進行對比
JDK 7 新特性
在2011年7月28日,Oracle正式釋出了Java 7
1、switch中使用String
JDK7以前在switch 只支援
- 基本資料型別:byte, short, char, int
- 包裝資料型別:Byte, Short, Character, Integer
- 列舉型別:Enum
在JDK7 開始支援字串型別:String
在JDK1.5之前,switch迴圈只支援byte、short、char、int四種資料型別.
JDK1.5 在switch迴圈中增加了列舉類與byte、short、char、int的包裝類,對四個包裝類的支援是因為java編譯器在底層手動進行拆箱,而對列舉類的支援是因為列舉類有一個ordinal方法,該方法實際上是一個int型別的數值.
jdk1.7開始支援String型別,但實際上String型別有一個hashCode演算法,結果也是int型別.而byte、short、char型別可以在不損失精度的情況下向上轉型成int型別.所以總的來說,可以認為switch中只支援int
String s = "a";
switch (s) {
case "a":
System.out.println("is a");
break;
case "b":
System.out.println("is b");
break;
default:
System.out.println("is c");
break;
}
2、try-with-resources
java7以前對某些資源的操作是需要手動關閉,如InputStream,Writes,Sockets,Sql等,需要在finally中進行關閉資源的操作,現在不需要使用finally來保證開啟的流被正確關閉,現在是自動完成的,會自動釋放資源,確保每一個資源在處理完成後都會關閉,就不需要我們程式碼去close();
在採用try-with-resources方式後,不需要再次宣告流的關閉。
可以使用try-with-resources的資源有:
任何實現了java.lang.AutoCloseable介面和java.io.Closeable介面的物件。為了支援這個行為,所有可關閉的類將被修改為可以實現一個Closable(可關閉的)介面。
jdk 1.7 之前
OutputStream fos = null;
try {
fos = new FileOutputStream("D:/file");
} finally {
fos.close();
}
JDK1.7 之後
try(OutputStream fos = new FileOutputStream("D:/file");){
// 不需要再次指明fos.close();
}
3、捕獲多個異常
java7以前在一個方法丟擲多個異常時,只能一個個的catch,這樣程式碼會有多個catch,顯得很不友好,現在只需一個catch語句,多個異常型別用"|"隔開。
JDK1.7 之前
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
JDK1.7 之後
try {
result = field.get(obj);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
4 泛型例項化型別自動推斷
運用泛型例項化型別自動推斷,對通用例項建立(diamond)的type引用進行了改進
JDK1.7 之前
List<String> list = new ArrayList<String>();
JDK1.7 之後
List<String> list = new ArrayList<>();
5、增加二進位制表示
Java7前支援十進位制(123)、八進位制(0123)、十六進位制(0X12AB)
Java7增加二進位制表示(0B11110001、0b11110001)
JDK1.7 之後
int binary = 0b0001_1001;
System.out.println("binary is :"+binary);
binary is :25
6、數字中可新增分隔符
Java7中支援在數字中間增加’_'作為分隔符,分隔長int以及long(也支援double,float),顯示更直觀,如(12_123_456)。
下劃線只能在數字中間,編譯時編譯器自動刪除數字中的下劃線。
JDK1.7 之後
int intOne = 1_000_000;
long longOne = 1_000_000;
double doubleOne = 1_000_000;
float floatOne = 1_000_000;
JDK 8 新特性
1.Lambda表示式
2.Stream函式式操作流元素集合
3.介面新增:預設方法與靜態方法
4.方法引用,與Lambda表示式聯合使用
5.引入重複註解
6.型別註解
7.最新的Date/Time API (JSR 310)
8.新增base64加解密API
9.陣列並行(parallel)操作
10.JVM的PermGen空間被移除:取代它的是Metaspace(JEP 122)元空間
1、Lambda 表示式
Lambda表示式(也稱為閉包)是整個Java 8發行版中最受期待的在Java語言層面上的改變,Lambda允許把函式作為一個方法的引數(函式作為引數傳遞進方法中).
首先,看看之前的程式碼:
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
現在可以寫成這樣:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
看到了吧,程式碼變得更段且更具有可讀性,但是實際上還可以寫得更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
對於函式體只有一行程式碼的,你可以去掉大括號{}以及return關鍵字,但是你還可以寫得更短點:
Collections.sort(names, (a, b) -> b.compareTo(a));
2、方法與建構函式引用
方法引用是用來直接訪問類或者例項的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由相容的函式式介面構成的目標型別上下文。計算時,方法引用會建立函式式介面的一個例項。
方法引用提供了非常有用的語法,可以直接引用已有Java類或物件(例項)的方法或構造器。與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗餘程式碼。
當Lambda表示式中只是執行一個方法呼叫時,不用Lambda表示式,直接通過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表示式。
注意方法引用是一個Lambda表示式,其中方法引用的操作符是雙冒號"::"。
簡單地說,就是一個Lambda表示式。在Java 8中,我們會使用Lambda表示式建立匿名方法,但是有時候,我們的Lambda表示式可能僅僅呼叫一個已存在的方法,而不做任何其它事,對於這種情況,通過一個方法名字來引用這個已存在的方法會更加清晰,Java 8的方法引用允許我們這樣做。方法引用是一個更加緊湊,易讀的Lambda表示式,注意方法引用是一個Lambda表示式,其中方法引用的操作符是雙冒號"::"。
例子:
假如我們有一個Person物件,要對它排序。
package com.demo.model;
import java.time.LocalDate;
public class Person {
public Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
String name;
LocalDate birthday;
public LocalDate getBirthday() {
return birthday;
}
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
@Override
public String toString() {
return this.name;
}
}
原始寫法,使用匿名類:
// 使用匿名類
Arrays.sort(pArr, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
});
其中,Arrays類的sort方法定義如下:
public static <T> void sort(T[] a, Comparator<? super T> c)
這裡,我們首先要注意Comparator介面是一個函式式介面,因此我們可以使用Lambda表示式,而不需要定義一個實現Comparator介面的類,並建立它的例項物件,傳給sort方法。
使用Lambda表示式,我們可以這樣寫:
改進一,使用Lambda表示式,未呼叫已存在的方法
//使用lambda表示式
Arrays.sort(pArr, (Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
});
然而,在以上程式碼中,關於兩個人生日的比較方法在Person類中已經定義了,因此,我們可以直接使用已存在的Person.compareByAge方法。
改進二,使用Lambda表示式,呼叫已存在的方法
//使用lambda表示式和類的靜態方法
Arrays.sort(pArr, (a ,b) -> Person.compareByAge(a, b));
因為這個Lambda表示式呼叫了一個已存在的方法,因此,我們可以直接使用方法引用來替代這個Lambda表示式。
改進三,使用方法引用
//使用方法引用,引用的是類的靜態方法
Arrays.sort(pArr, Person::compareByAge);
在以上程式碼中,方法引用Person::compareByAge在語義上與Lambda表示式 (a, b) -> Person.compareByAge(a, b) 是等同的
,都有如下特性:
- 真實的引數是拷貝自
Comparator<Person>.compare
方法,即(Person, Person); - 表示式體呼叫Person.compareByAge方法。
四種方法引用型別
型別 | 示例 |
---|---|
引用靜態方法 | ContainingClass::staticMethodName |
引用某個物件的例項方法 | containingObject::instanceMethodName |
引用某個型別的任意物件的例項方法 | ContainingType::methodName |
引用構造方法 | ClassName::new |
3、介面預設實現方法
Java 8允許我們給介面新增一個非抽象的方法實現,只需要使用default關鍵字即可,這個特徵又叫做擴充套件方法。
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
4、新工具
新的編譯工具,如:Nashorn引擎 jjs、 類依賴分析器jdeps。
4.1、 Nashorn引擎:jjs
jjs是一個基於標準Nashorn引擎的命令列工具,可以接受js原始碼並執行。例如,我們寫一個func.js檔案,內容如下:
function f() {
return 1;
};
print( f() + 1 );
4.2、 類依賴分析器:jdeps
jdeps是一個相當棒的命令列工具,它可以展示包層級和類層級的Java類依賴關係,它以.class檔案、目錄或者Jar檔案為輸入,然後會把依賴關係輸出到控制檯
4.3、JVM的新特性
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM引數方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize。
5、Stream API
Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種宣告的方式處理資料。
Stream 使用一種類似用 SQL 語句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。
元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。
5.1 什麼是 Stream?
Stream(流)是一個來自資料來源的元素佇列並支援聚合操作
元素
是特定型別的物件,形成一個佇列。 Java中的Stream並不會儲存元素,而是按需計算。資料來源
流的來源。 可以是集合,陣列,I/O channel, 產生器generator 等。
聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:
Pipelining
: 中間操作都會返回流物件本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。內部迭代
: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。
5.2 生成流
在 Java 8 中, 集合介面有兩個方法來生成流:
- stream() − 為集合建立序列流。
- parallelStream() − 為集合建立並行流。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
5.3 forEach
Stream 提供了新的方法 ‘forEach’ 來迭代流中的每個資料。以下程式碼片段使用 forEach 輸出了10個隨機數:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
5.4 map
map 方法用於對映每個元素到對應的結果,以下程式碼片段使用 map 輸出了元素對應的平方數:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 獲取對應的平方數
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
5.5 filter
filter 方法用於通過設定的條件過濾出元素。以下程式碼片段使用 filter 方法過濾出空字串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 獲取空字串的數量
int count = strings.stream().filter(string -> string.isEmpty()).count();
5.6 limit
limit 方法用於獲取指定數量的流。 以下程式碼片段使用 limit 方法打印出 10 條資料:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
5.7 sorted
sorted 方法用於對流進行排序。以下程式碼片段使用 sorted 方法對輸出的 10 個隨機數進行排序:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
5.8 並行(parallel)程式
parallelStream 是流並行處理程式的代替方法。以下例項我們使用 parallelStream 來輸出空字串的數量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 獲取空字串的數量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
我們可以很容易的在順序執行和並行直接切換。
5.9 Collectors
Collectors 類實現了很多歸約操作,例如將流轉換成集合和聚合元素。Collectors 可用於返回列表或字串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("篩選列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合併字串: " + mergedString);
5.10 統計
另外,一些產生統計結果的收集器也非常有用。它們主要用於int、double、long等基本型別上,它們可以用來產生類似如下的統計結果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的數 : " + stats.getMax());
System.out.println("列表中最小的數 : " + stats.getMin());
System.out.println("所有數之和 : " + stats.getSum());
System.out.println("平均數 : " + stats.getAverage());
6、Date Time API
加強對日期與時間的處理。
6.1 LocalDate/LocalTime/LocalDateTime
LocalDate為日期處理類、LocalTime為時間處理類、LocalDateTime為日期時間處理類,方法都類似,具體可以看API文件或原始碼,選取幾個代表性的方法做下介紹。
now相關的方法可以獲取當前日期或時間,of方法可以建立對應的日期或時間,parse方法可以解析日期或時間,get方法可以獲取日期或時間資訊,with方法可以設定日期或時間資訊,plus或minus方法可以增減日期或時間資訊;
6.2 TemporalAdjusters
這個類在日期調整時非常有用,比如得到當月的第一天、最後一天,當年的第一天、最後一天,下一週或前一週的某天等。
6.3 DateTimeFormatter
以前日期格式化一般用SimpleDateFormat類,但是不怎麼好用,現在1.8引入了DateTimeFormatter類,預設定義了很多常量格式(ISO打頭的),在使用的時候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把當前日期格式化成yyyy-MM-dd hh:mm:ss的形式:
LocalDateTime dt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(dtf.format(dt));
7、Optional 類
Optional 類是一個可以為null的容器物件。如果值存在則isPresent()方法會返回true,呼叫get()方法會返回該物件。
Optional 是個容器:它可以儲存型別T的值,或者僅僅儲存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
Optional 類的引入很好的解決空指標異常。
Optional.of()或者Optional.ofNullable():建立Optional物件,差別在於of不允許引數是null,而ofNullable則無限制。
// 引數不能是null
Optional<Integer> optional1 = Optional.of(1);
// 引數可以是null
Optional<Integer> optional2 = Optional.ofNullable(null);
// 引數可以是非null
Optional<Integer> optional3 = Optional.ofNullable(2);
Optional.empty():所有null包裝成的Optional物件:
Optional<Integer> optional1 = Optional.ofNullable(null);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1 == optional2);// true
System.out.println(optional1 == Optional.<Integer>empty());// true
Object o1 = Optional.<Integer>empty();
Object o2 = Optional.<String>empty();
System.out.println(o1 == o2);// true:
isPresent():判斷值是否存在
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// isPresent判斷值是否存在
System.out.println(optional1.isPresent() == true);
System.out.println(optional2.isPresent() == false);
ifPresent(Consumer consumer):如果option物件儲存的值不是null,則呼叫consumer物件,否則不呼叫
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// 如果不是null,呼叫Consumer
optional1.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
// null,不呼叫Consumer
optional2.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
orElse(value):如果optional物件儲存的值不是null,則返回原來的值,否則返回value
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// orElse
System.out.println(optional1.orElse(1000) == 1);// true
System.out.println(optional2.orElse(1000) == 1000);// true
orElseGet(Supplier supplier):功能與orElse一樣,只不過orElseGet引數是一個物件
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1.orElseGet(() -> {
return 1000;
}) == 1);//true
System.out.println(optional2.orElseGet(() -> {
return 1000;
}) == 1000);//true
orElseThrow():值不存在則丟擲異常,存在則什麼不做,有點類似Guava的Precoditions
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
optional1.orElseThrow(()->{throw new IllegalStateException();});
try
{
// 丟擲異常
optional2.orElseThrow(()->{throw new IllegalStateException();});
}
catch(IllegalStateException e )
{
e.printStackTrace();
}
filter(Predicate):判斷Optional物件中儲存的值是否滿足Predicate,並返回新的Optional。
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<Integer> filter1 = optional1.filter((a) -> a == null);
Optional<Integer> filter2 = optional1.filter((a) -> a == 1);
Optional<Integer> filter3 = optional2.filter((a) -> a == null);
System.out.println(filter1.isPresent());// false
System.out.println(filter2.isPresent());// true
System.out.println(filter2.get().intValue() == 1);// true
System.out.println(filter3.isPresent());// false
map(Function):對Optional中儲存的值進行函式運算,並返回新的Optional(可以是任何型別)
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<String> str1Optional = optional1.map((a) -> "key" + a);
Optional<String> str2Optional = optional2.map((a) -> "key" + a);
System.out.println(str1Optional.get());// key1
System.out.println(str2Optional.isPresent());// false
flatMap():功能與map()相似,差別請看如下程式碼。flatMap方法與map方法類似,區別在於mapping函式的返回值不同。map方法的mapping函式返回值可以是任何型別T,而flatMap方法的mapping函式必須是Optional。
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Optional<String>> str1Optional = optional1.map((a) -> {
return Optional.<String>of("key" + a);
});
Optional<String> str2Optional = optional1.flatMap((a) -> {
return Optional.<String>of("key" + a);
});
System.out.println(str1Optional.get().get());// key1
System.out.println(str2Optional.get());// key1
8、Nashorn, JavaScript 引擎
Java 8提供了一個新的Nashorn javascript引擎,它允許我們在JVM上執行特定的javascript應用。
Nashorn JavaScript引擎可以在Java程式碼中程式設計呼叫,也可以通過命令列工具jjs使用,它在$JAVA_HOME/bin中。如果打算使用jjs,你可能希望設定符號連結來簡化訪問:
$ cd /usr/bin
$ ln -s $JAVA_HOME/bin/jjs jjs
$ jjs
jjs> print('Hello World');
這個教程專注於在Java程式碼中呼叫Nashron,所以讓我們先跳過jjs。Java程式碼中簡單的HelloWorld如下所示:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");
為了在Java中執行JavaScript,你首先要通過javax.script包建立指令碼引擎。這個包已經在Rhino(來源於Mozilla、Java中的遺留JS引擎)中使用了。
JavaScript程式碼既可以通過傳遞JavaScript程式碼字串,也可以傳遞指向你的JS指令碼檔案的FileReader來執行:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Nashorn JavaScript基於ECMAScript 5.1,但是它的後續版本會對ES6提供支援:
Nashorn的當前策略遵循ECMAScript規範。當我們在JDK8中釋出它時,它將基於ECMAScript 5.1。Nashorn未來的主要釋出基於ECMAScript 6。
Nashorn定義了大量對ECMAScript標準的語言和API擴充套件。但是首先讓我們看一看Java和JavaScript程式碼如何互動。
在JavaScript中呼叫Java方法
在JavaScript中呼叫Java方法十分容易。我們首先需要定義一個Java靜態方法。
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
Java類可以通過Java.typeAPI擴充套件在JavaScript中引用。它就和Java程式碼中的import類似。只要定義了Java型別,我們就可以自然地呼叫靜態方法fun1(),然後像sout列印資訊。由於方法是靜態的,我們不需要首先建立例項。
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
在使用JavaScript原生型別呼叫Java方法時,Nashorn 如何處理型別轉換?讓我們通過簡單的例子來弄清楚。
下面的Java方法簡單列印了方法引數的實際型別:
static void fun2(Object object) {
System.out.println(object.getClass());
}
為了理解背後如何處理型別轉換,我們使用不同的JavaScript型別來呼叫這個方法:
MyJavaClass.fun2(123);
// class java.lang.Integer
MyJavaClass.fun2(49.99);
// class java.lang.Double
MyJavaClass.fun2(true);
// class java.lang.Boolean
MyJavaClass.fun2("hi there")
// class java.lang.String
MyJavaClass.fun2(new Number(23));
// class jdk.nashorn.internal.objects.NativeNumber
MyJavaClass.fun2(new Date());
// class jdk.nashorn.internal.objects.NativeDate
MyJavaClass.fun2(new RegExp());
// class jdk.nashorn.internal.objects.NativeRegExp
MyJavaClass.fun2({foo: 'bar'});
// class jdk.nashorn.internal.scripts.JO4
JavaScript原始型別轉換為合適的Java包裝類,而JavaScript原生物件會使用內部的介面卡類來表示。要記住jdk.nashorn.internal中的類可能會有所變化,所以不應該在客戶端面向這些類來程式設計。
任何標記為“內部”的東西都可能會從你那裡發生改變。
ScriptObjectMirror
在向Java傳遞原生JavaScript物件時,你可以使用ScriptObjectMirror類,它實際上是底層JavaScript物件的Java表示。ScriptObjectMirror實現了Map介面,位於jdk.nashorn.api中。這個包中的類可以用於客戶端程式碼。
下面的例子將引數型別從Object改為ScriptObjectMirror,所