這些Java9 超牛的新特性,你竟然還沒用過?
目錄結(jié)構(gòu)變化
有關(guān)jdk9的下載安裝與環(huán)境配置在這里就不作介紹了,直接來看看它與jdk8的第一個(gè)區(qū)別,目錄結(jié)構(gòu)的變化。

上圖是jdk8的目錄結(jié)構(gòu),下圖是jdk9的目錄結(jié)構(gòu):

兩者最明顯的區(qū)別在于jdk9中已經(jīng)不包含jre了,其它內(nèi)容變化倒是不大。
模塊化
我們知道,Java編寫的項(xiàng)目是比較臃腫的,編譯運(yùn)行需要耗費(fèi)大量的時(shí)間,為此,java9提供了模塊化,使得開發(fā)者可以指定項(xiàng)目具體需要使用哪些類庫(kù),以排除無關(guān)緊要的jar包,增加項(xiàng)目運(yùn)行效率。
首先創(chuàng)建一個(gè)Java項(xiàng)目:

在該項(xiàng)目下創(chuàng)建兩個(gè)模塊,創(chuàng)建方法為 右擊項(xiàng)目-->New-->Module:


模塊創(chuàng)建完成后,在module-1中編寫一個(gè)Bean:
package com.wwj.bean;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
然后我們?cè)趍odule-2中創(chuàng)建一個(gè)測(cè)試文件:

會(huì)發(fā)現(xiàn)在module-2中是無法使用module-1中的Person類的,這是因?yàn)閖dk9對(duì)項(xiàng)目進(jìn)行了模塊化,若想要使用到其它模塊的類,需要作如下操作。
在module-1中創(chuàng)建module-info文件:

編寫如下內(nèi)容:
module module1 {
// 導(dǎo)出包
exports com.wwj.bean;
}
并在module-2中創(chuàng)建module-info文件,編寫如下內(nèi)容:
module module2 {
// 引入模塊
requires module1;
}
這樣我們就可以順利地使用到module-1中com.wwj.bean包下的類了:

再舉個(gè)例子,比如你想打印日志,你就需要使用Logger類,然而:

此時(shí)Logger類也是報(bào)錯(cuò)的,而且你會(huì)發(fā)現(xiàn)導(dǎo)包是導(dǎo)入不了的,此時(shí)我們就需要在module-info中引入Logger模塊:
module module2 {
requires module1;
requires java.logging;
}
這樣就能夠使用Logger類了:

通過這樣的方式,使得虛擬機(jī)在加載項(xiàng)目時(shí)只會(huì)去加載module-info中配置的模塊,從而大大提升了運(yùn)行效率。
jshell命令
在jdk9之前,我們?nèi)羰窍雸?zhí)行一個(gè)非常簡(jiǎn)單的程序,比如做一個(gè)加法,你需要?jiǎng)?chuàng)建java文件,然后編譯執(zhí)行。
這顯然非常繁瑣,那它能不能夠像Python那樣有一個(gè)交互式的編程環(huán)境呢?為此,jdk9提供了jshell。
使用方法非常簡(jiǎn)單,在cmd窗口中輸入jshell:

在jshell中,我們能夠進(jìn)行輸出、定義變量、計(jì)算等等很多操作,jshell也會(huì)在我們按下回車后立即給予我們反饋:

創(chuàng)建方法并調(diào)用:

jshell還提供了一些非常好用的命令,比如:/list,通過它能夠查看歷史執(zhí)行的命令:
jshell> /list
1 : System.out.println("Hello World
3 : System.out.println("Hello World
4 : int i = 10;
5 : int j = 10;
6 : int result = i + j;
7 : System.out.println(result);
8 : public int add(int i,int j){
return i + j;
}
9 : System.out.println(add(i,j));
/imports,查看導(dǎo)入的包:
jshell> /imports
| import java.io.*
| import java.math.*
| import java.net.*
| import java.nio.file.*
| import java.util.*
| import java.util.concurrent.*
| import java.util.function.*
| import java.util.prefs.*
| import java.util.regex.*
| import java.util.stream.*
/vars,查看定義的變量:
jshell> /vars
| int i = 10
| int j = 10
| int result = 20
/methods,查看定義的方法:
jshell> /methods
| int add(int,int)
/edit,彈出對(duì)話框用于修改代碼:

前言多版本兼容Jar包
當(dāng)一個(gè)新版本的jdk出現(xiàn)時(shí),開發(fā)者并不愿意立馬將其開發(fā)環(huán)境切換到新的版本,因?yàn)樗暮芏囗?xiàng)目還是用之前的jdk進(jìn)行開發(fā)的,當(dāng)切換了新版本的jdk后,很可能會(huì)因?yàn)槠洳患嫒菀恍├系膉ar包從而導(dǎo)致項(xiàng)目出錯(cuò)。
jdk9考慮到了這一點(diǎn),其多版本兼容jar包的功能可以使開發(fā)者創(chuàng)建僅在特定版本的java環(huán)境中運(yùn)行庫(kù)程序選擇使用的版本。
現(xiàn)在有這樣一個(gè)項(xiàng)目,其中有兩個(gè)包,一個(gè)java包,一個(gè)java-9包。
java包中有兩個(gè)類,分別是:
public class Generator {
public Set<String> createStrings() {
Set<String> strings = new HashSet<String>();
strings.add("Java");
strings.add("8");
return strings;
}
}
public class Application {
public static void testMultiJar(){
Generator gen = new Generator();
System.out.println("Generated strings: " + gen.createStrings());
}
}
而java-9中的類為:
public class Generator {
public Set<String> createStrings() {
return Set.of("Java", "9");
}
}
現(xiàn)在我們將對(duì)這個(gè)項(xiàng)目進(jìn)行打包,得到一個(gè).jar文件——multijar.jar。
下面就來測(cè)試一下,新建一個(gè)Java8的項(xiàng)目,并編寫測(cè)試代碼:
public class MultiJar {
public static void main(String[] args) {
Application.testMultiJar();
}
}
記得將剛才的jar包導(dǎo)入到項(xiàng)目中,運(yùn)行結(jié)果為:
Generated strings: [Java, 8]
我們?cè)賹⑦@段代碼放到Java9環(huán)境的項(xiàng)目中運(yùn)行一下,得到結(jié)果:
Generated strings: [9, Java]
可以看到,同一段代碼在不同環(huán)境下會(huì)有對(duì)應(yīng)的不同表示,這就是多版本兼容的jar包。
接口可以定義私有方法了
從jdk9開始,接口可以定義私有方法了,具體的話也沒有什么好說的,直接看代碼:
public interface InterfaceTest {
//jdk7中只能聲明全局常量(使用public static final修飾)和抽象方法(使用public abstract修飾)
int num = 10;
void add(int i,int j);
//jdk8中還能夠聲明靜態(tài)方法和默認(rèn)方法
static void staticMethod(){
}
default void defaultMethod(){
}
//jdk9中能夠定義私有方法
private void privateMethod(){
}
}
集合中的泛型
在jdk8以前,我們?nèi)粝攵x一個(gè)帶有泛型的集合,必須這樣編寫:
Set<String> set = new HashSet<String>();
而在jdk8中,我們可以省略后面的泛型,因?yàn)樗梢赃M(jìn)行類型的自動(dòng)推斷:
Set<String> set = new HashSet<>();
在jdk9中,我們還能夠?qū)线M(jìn)行如下編寫:
Set<String> set = new HashSet<>(){};
這行代碼的意思是創(chuàng)建一個(gè)繼承于HashSet的匿名子類對(duì)象,它將與Set共同使用泛型,那么這樣有什么好處呢?
好處在于當(dāng)你需要改造Set中的某個(gè)方法時(shí),能夠很方便地實(shí)現(xiàn),比如:
public static void main(String[] args) {
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.add(s + "--");
}
};
set.add("zhangsan");
set.add("lisi");
set.add("wangwu");
for (String str : set) {
System.out.println(str);
}
}
運(yùn)行結(jié)果:
wangwu--
zhangsan--
lisi--
異常處理
對(duì)于IO流的異常處理一直為人所詬病,傳統(tǒng)的異常處理過程如下:
FileInputStream in = null;
try {
in = new FileInputStream("");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
可以看到代碼非常的臃腫,在jdk8中,我們還有另外一套解決方案:
try (FileInputStream in = new FileInputStream("")) {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
將資源放在try語句的括號(hào)內(nèi),我們就不需要手動(dòng)去關(guān)閉流資源了。
而在jdk9中,我們可以在try()中調(diào)用已經(jīng)實(shí)例化的資源對(duì)象:
InputStreamReader reader = new InputStreamReader(System.in);
try (reader) {
reader.read();
} catch (IOException e) {
e.printStackTrace();
}
這種方式在jdk9之前是不支持的。
下劃線的使用限制
在jdk8中,下劃線是可以單獨(dú)作為變量名進(jìn)行定義的:
int _ = 100;
而jdk9中禁止了這種變量的定義:

前言String存儲(chǔ)結(jié)構(gòu)的變化
在jdk8中,字符串的底層采用的是char數(shù)組:

而在jdk9中,它不再使用char數(shù)組實(shí)現(xiàn),取而代之的是byte數(shù)組:

因?yàn)樵赨TF-16編碼中,一個(gè)字符會(huì)占用兩個(gè)字節(jié),而大部分情況下,開發(fā)者使用的String中包含了較多的字母和數(shù)字,它們均只用一個(gè)字節(jié)就能夠存儲(chǔ),所以采用char數(shù)組存儲(chǔ)字符串會(huì)造成大量資源的浪費(fèi),為此,jdk9中特別設(shè)計(jì)了String的實(shí)現(xiàn),將其底層改為了byte數(shù)組。
只讀集合
jdk8中提供了unmodifiableList()方法來將一個(gè)集合轉(zhuǎn)變?yōu)橹蛔x集合:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<String> readList = Collections.unmodifiableList(list);
// 只讀集合不允許添加元素
// readList.add("d");
readList.forEach(System.out::println);
}
若是想創(chuàng)建只讀的Set集合,只需修改方法名即可:
public static void main(String[] args) {
Set<Integer> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)));
set.forEach(System.out::println);
}
只讀的map集合也是如此:
public static void main(String[] args) {
Map<Object, Object> map = Collections.unmodifiableMap(new HashMap<>() {
{
put("zhangsan", 20);
put("lisi", 21);
put("wangwu", 22);
}
});
map.forEach((k,v) -> System.out.println(k + ":" + v));
}
注意這里使用到了jdk9中的另一新特性來初始化Map集合,這在集合中的泛型已經(jīng)介紹過了。
以上均是jdk8中創(chuàng)建只讀集合的方式,在jdk9中,它的創(chuàng)建方式只會(huì)更加簡(jiǎn)單:
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.forEach(System.out::println);
}
通過of()方法創(chuàng)建的集合它就是一個(gè)只讀集合,是不可以再對(duì)其進(jìn)行修改的。
然后是Set集合和Map集合:
public static void main(String[] args) {
//創(chuàng)建只讀Set
Set<Integer> set = Set.of(1, 2, 3, 4);
//創(chuàng)建只讀Map
Map<String, Integer> map = Map.of("zhangsan", 20, "lisi", 21, "wangwu", 22);
//創(chuàng)建只讀Map的第二種方式
Map<String, Integer> map = Map.ofEntries(Map.entry("zhangsan", 20), Map.entry("lisi", 21));
}
Stream的增強(qiáng)
首先是takeWhile方法:
public static void main(String[] args) {
// takeWhile
List<Integer> list = Arrays.asList(1,3,2,5,4);
Stream<Integer> stream = list.stream();
Stream<Integer> newStream = stream.takeWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在該場(chǎng)景中,takeWhile方法的作用是從集合第一個(gè)元素開始查找小于3的元素,第一個(gè)元素1小于3;第二個(gè)元素3不小于3,此時(shí)后面的所有元素都會(huì)被舍棄,所以運(yùn)行結(jié)果為:
1
其次是dropWhile方法:
public static void main(String[] args) {
// dropWhile
List<Integer> list = Arrays.asList(1,3,2,5,4);
Stream<Integer> stream = list.stream();
Stream<Integer> newStream = stream.dropWhile(x -> x < 3);
newStream.forEach(System.out::println);
}
在該場(chǎng)景中,dropWhile方法的作用是從集合第一個(gè)元素開始查找小于3的元素,第一個(gè)元素1小于3,會(huì)被舍棄;第二個(gè)元素3不小于3,此時(shí)后面的所有元素都被保留,所以運(yùn)行結(jié)果為:
3
2
5
4
最后是ofNullable方法,它允許Stream中存放單個(gè)null值:
public static void main(String[] args) {
//ofNullable
Stream<Object> stream = Stream.ofNullable(null);
System.out.println(stream.count());
}
這樣是沒有任何錯(cuò)誤的,運(yùn)行結(jié)果為:
0
HttpClient
jdk9中提供了HttpClient來實(shí)現(xiàn)網(wǎng)絡(luò)連接,用法如下:
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.version().name());
System.out.println(response.body());
}
運(yùn)行結(jié)果:
200
HTTP_1_1
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
......
前言Java編譯工具的升級(jí)
jdk9中升級(jí)了java的編譯工具,它提供了sjavac指令用于在多核處理器情況下提升jdk的編譯速度。
