很多初學Java的小伙伴經常咨詢:
- 到底該安裝哪個版本的JDK比較好?
- Java 8到底還夠不夠用?
- Java 11究竟有什么改進?
- 是不是Java版本越新越好?
- ……
是這樣,官網現在其實都已經出到Java 13版本了,並且提供下載使用。
但目前市場上主流的穩定版當然還得屬Java 8和Java 11,而目前大部分公司的生產環境還是Java 8居多。
所以如果從自學角度出發,我覺得這兩個版本都OK,其他中間的一些比如Java 9、Java 10這些非穩定版就不用考慮了。
Java11 vs Java8
Java 11相對於Java 8確實有一部分進化,除了有很多內部的升級(比如開銷和時延更低的GC、TLS1.3加持等等)之外,對於初學使用者來說也有一些語言使用層面的進化。
正好最近我在自己的個人小項目上嘗試升級使用了一下Java 11(公司項目咱也不敢動、也不敢問,只好動自己的個人項目),因此本文從實際代碼編寫角度來大致體驗一下我個人使用Java 11之后相對Java 8所感覺到的一些比較深刻的進化,官方文檔里說得也非常清楚了:https://docs.oracle.com/en/java/javase/11/
我這次實驗裝的Java 11版本是11.0.6:
下文將要實驗驗證的一些新特性其實也並非Java 11才引入,很多其實在Java 9和Java 10時就已經引入,只不過到了Java 11這個穩定版才沉淀下來。
變量類型推斷
新版Java引入了一個全新的類型關鍵字var,用var來定義的變量不用寫具體類型,編譯器能根據=右邊的實際賦值來自動推斷出變量的類型:
1、普通局部變量
var name = "codesheep"; // 自動推斷name為String類型 System.out.println(name);
怎么樣?是不是有一種在使用類似JavaScript這種弱類型語言的錯覺?
2、for循環中使用
var upList1 = List.of( "劉能", "趙四", "謝廣坤" ); var upList2 = List.of( "永強", "玉田", "劉英" ); var upList3 = List.of( "謝飛機", "蘭妮", "蘭娜" ); var upListAll = List.of( upList1, upList2, upList3 ); for( var i : upListAll ) { // 用var接受局部變量的確非常簡潔! for( var j : i ) { System.out.println(j); } }
這地方就能看出用var定義局部變量的優勢了,假如這個例子中集合里的元素類型更為復雜,是類似List<List<String>>這種嵌套類型的話,var定義就非常簡潔明了!
3、當然,有些情況是不能使用的
//var類型變量一旦賦值后,重新賦不同類型的值是不行的,比如: var name = "codesheep"; name = 666; // 此時編譯會提示不兼容的類型 //定義var類型變量沒有初始化是不行的,比如: var foo; // 此時編譯會提示無法推斷類型 foo = "Foo"; //另外,像類的成員變量類型、方法入參類型、返回值類型等是不能使用var的,比如: public class Test { private var name; // 會提示不允許使用var public void setName( var name ) { // 會提示不允許使用var this.name = name; } public var getName() { // 會提示不允許使用var return name; } }
官方HTTP Client加持
是的!
現在JDK官方就自帶HTTP Client了,位於java.net.http包下,支持發送同步、異步的HTTP請求,這樣一來,以前咱們常用的HTTP請求客戶端諸如:OKHttp、HttpClient這種現在都可以退下了!
//發送同步請求: var request = HttpRequest.newBuilder() .uri( URI.create("https://www.codesheep.cn") ) .GET() .build(); // 同步請求方式,拿到結果前會阻塞當前線程 var httpResponse = HttpClient.newHttpClient() .send( request, HttpResponse.BodyHandlers.ofString()); System.out.println( httpResponse.body() ); // 打印獲取到的網頁內容 //發送異步請求: CompletableFuture<String> future = HttpClient.newHttpClient(). sendAsync( request, HttpResponse.BodyHandlers.ofString() ) .thenApply( HttpResponse::body ); System.out.println("我先繼續干點別的事情..."); System.out.println( future.get() ); // 打印獲取到的網頁內容 //當然你也可以自定義請求頭,比如攜帶JWT Token權限信息去請求等: var requestWithAuth = HttpRequest.newBuilder() .uri( URI.create("http://www.xxxxxx.com/sth") ) .header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNTIwNTE2MTE5NiIsImNyZWF0ZWQiOjE1ODMzMTA2ODk0MzYsImV4cCI6MTU4MzM5NzA4OSwidXNlcmlkIjoxMDAwNH0.OE9R5PxxsvtVJZn8ne-ksTb2aXXi7ipzuW9kbCiQ0uNoW0fJJr_wckLFmgDzxmBs3IdzIhWDAtaSIvmTshK_RQ") .GET() .build(); var response = HttpClient.newHttpClient() .send( requestWithAuth, HttpResponse.BodyHandlers.ofString() ); System.out.println( response.body() ); // 打印獲取到的接口返回內容
String處理增強
//新版字符串String類型增加了諸如:isBlank()、strip()、repeat()等方便的字符串處理方法 String myName = " codesheep "; System.out.println( " ".isBlank() ); // 打印:true System.out.println( " ".isEmpty() ); // 打印:false System.out.println( myName.strip() ); // 打印codesheep,前后空格均移除 System.out.println( myName.stripLeading() ); // 打印codesheep ,僅頭部空格移除 System.out.println( myName.stripTrailing() ); // 打印 codesheep,僅尾部空格移除 System.out.println( myName.repeat(2) ); // 打印 codesheep codesheep
集合增強
主要是增加了諸如of()和copyOf()等方法用於更加方便的創建和復制集合類型
var upList = List.of( "劉能", "趙四", "謝廣坤" ); var upListCopy = List.copyOf( upList ); System.out.println(upList); // 打印 [劉能, 趙四, 謝廣坤] System.out.println(upListCopy); // 打印 [劉能, 趙四, 謝廣坤] var upSet = Set.of("劉能","趙四"); var upSetCopy = Set.copyOf( upSet ); System.out.println(upSet); // 打印 [趙四, 劉能] System.out.println(upSetCopy); // 打印 [趙四, 劉能] var upMap = Map.of("劉能","58歲","趙四","59歲"); var upMapCopy = Map.copyOf( upMap ); System.out.println(upMap); // 打印 {劉能=58歲, 趙四=59歲} System.out.println(upMapCopy); // 打印 {劉能=58歲, 趙四=59歲}
函數式編程增強
我印象最深的是對Stream流增加了諸如takeWhile()和dropWhile()的截止結算方法:
var upList = List.of( "劉能", "趙四", "謝廣坤" ); // 從集合中依次刪除滿足條件的元素,直到不滿足條件為止 var upListSub1 = upList.stream() .dropWhile( item -> item.equals("劉能") ) .collect( Collectors.toList() ); System.out.println(upListSub1); // 打印 [趙四, 謝廣坤] // 從集合中依次獲取滿足條件的元素,知道不滿足條件為止 var upListSub2 = upList.stream() .takeWhile( item -> item.equals("劉能") ) .collect( Collectors.toList() ); System.out.println( upListSub2 ); // 打印 [劉能]
文件讀寫增強
1、Files類增強
我們以前心心念的直接能把文件內容讀取到String以及String回寫到文件的功能終於支持了,可以通過Files類的靜態方法writeString()和readString()完成:
Path path = Paths.get("/Users/CodeSheep/test.txt"); String content = Files.readString(path, StandardCharsets.UTF_8); System.out.println(content); Files.writeString( path, "王老七", StandardCharsets.UTF_8 );
2、InputStream增強
InputStream則增加了一個transferTo()方法,直接將數據丟到OutputStream去:
InputStream inputStream = new FileInputStream( "/Users/CodeSheep/test.txt" ); OutputStream outputStream = new FileOutputStream( "/Users/CodeSheep/test2.txt" ); inputStream.transferTo( outputStream );
支持源文件直接運行(666!)
比如我寫一個最簡單的Hello World程序:
public class Hello {
public static void main( String[] args ) {
System.out.println("hello world");
}
}
並保存為hello.java文件,這時候可以直接用java指令去運行這個Java源文件,直接省去以前javac編譯源文件的過程:
java hello.java
怎么樣?是不是和python源文件的運行有點像?這個信息量就有點大了,大家可以自行腦補一下
案例1
public class Test { public static void main(String[] args) { System.out.println("Test ..."); } }
執行上面的代碼
//jdk11之前 : javac Test.java java Test //jdk11: java Test.java
結論:jdk11中,通過 java xxx.java 命令,就可直接運行源碼文件程序,而且不會產生.class 文件。
案例二
問題:如果一個java文件中存在多個類 ,通過 java xxx.java 運行源碼文件,會執行哪一個main方法 ?
創建一個 Test1.java 文件,代碼如下:
class Test2 { public static void main(String[] args) { System.out.println("Test2"); } } public class Test { public static void main(String[] args) { System.out.println("Test"); } }
通過 java Test.java 運行后輸出 "Test2"。
下面我們顛倒 Test Test2 兩個類的位置:
public class Test { public static void main(String[] args) { System.out.println("Test"); } } class Test2 { public static void main(String[] args) { System.out.println("Test2"); } }
通過 java Test.java 運行后輸出 "Test"。
結論:一個java文件中包含多個類時,java xxx.java 執行排在最上面的一個類的main方法。
案例三
問題:如果一個java文件中類的方法中調用了另一個java文件中類的方法,通過 java xxx.java 運行源碼文件,能運行通過嗎 ?
創建兩個java文件 Student.java 、Teacher.java。
Student.java:
public class Student { public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.toString(); } }
Teacher.java:
public class Teacher { private String name; private String subject; public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setSubject(String subject){ this.subject = subject; } public String getSubject(){ return subject; } }
執行 java Student.java 報錯 :
Student.java:4: 錯誤: 找不到符號 Teacher teacher = new Teacher(); ^ 符號: 類 Teacher 位置: 類 Student Student.java:4: 錯誤: 找不到符號 Teacher teacher = new Teacher(); ^ 符號: 類 Teacher 位置: 類 Student 2 個錯誤
把 Student 和 Teacher 連個類 放在一個java文件中 ,重新運行,運行通過。
結論:java xxx.java 啟動單個Java源代碼文件的程序時,相關個類必須定義在同一個java文件中。
結論
通過上面的三個案例,我得出以下結論:
- jdk11中,通過 java xxx.java 命令,就可直接運行源碼文件程序,而且不會產生.class 文件。
- 一個java文件中包含多個類時,java xxx.java 執行排在最上面的一個類的main方法。
- java xxx.java 啟動單個Java源代碼文件的程序時,相關個類必須定義在同一個java文件中。
小結
Java 11確有很多改進,但還是那句話,對於初學者來說Java 8了,沒必要刻意求新,穩才是最重要的