從Java8到Java17,這些新特性讓你的程式碼起飛!

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、CRM 等等功能:
  • Boot 倉庫:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 倉庫:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 雙版本 

前言

一直想找時間做一篇關於Java新特性的盤點清單,一切以實用為主,不多贅述,不講空談,不整虛頭巴腦的概念,從實戰的角度出發,根據實際開發需求,盤點值得使用的新特性。
因此對於垃圾回收器、效能提升等不會直接在編碼層面體現的特性,不在此次盤點範圍內。
耐心看完,你一定有所收穫。

基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

正文

介面私有方法(Java9)

眾所周知,在Java9之前,interface介面只能定義abstract抽象方法和default預設方法。
如果有多個預設方法使用了相同的處理邏輯,那隻能寫重複程式碼,或者再單獨建個類進行呼叫。
Java9解決了此類問題,其允許在介面中定義private私有方法,減少重用程式碼和多餘的類。
比如下面這個例子:
publicinterfaceMyInterface{defaultvoidmethod1(){        System.out.println("Default method1");        commonMethod();    }defaultvoidmethod2(){        System.out.println("Default method2");        commonMethod();    }/** * 這是一段通用的處理邏輯 */privatevoidcommonMethod(){        System.out.println("Common method in the interface");    }}
在這個示例中,MyInterface介面有兩個預設方法method1()method2(),它們都呼叫了私有方法commonMethod(),避免了程式碼重複。
並且當實現MyInterface時,只需要呼叫method1()或者method2(),無需關心其共同呼叫的commonMethod()的具體實現。

Optional增強(Java9)

stream()
在Java 9之前,如果想對Optional物件中的值進行操作,還得使用ifPresent()方法或者orElse()方法。例如,以下是一個Java 8的例子:
Optional<String> optional = ...;optional.ifPresent(value -> System.out.println(value.length()));
在Java 9中,可以直接使用stream()方法和Stream的map()方法來達到相同的效果,程式碼如下:
Optional<String> optional = ...;optional.stream().map(String::length).forEach(System.out::println);
這個例子可能不太好,但還是能看出來有了stream()方法後,對於物件的操作也變得方便了許多。
ifPresentOrElse()
這個方法允許提供兩個Runnable,第一個在Optional物件包含值時執行,第二個在Optional物件為空時執行。例如下面這兩段程式碼,對比了Java8和Java9中不同的處理:
Optional<String> optionalValue = Optional.of("Hello");// Java 8if (optionalValue.isPresent()) { System.out.println("Value is present: " + optionalValue.get());else { System.out.println("Value is absent");}// Java 9optionalValue.ifPresentOrElse(  value -> System.out.println("Value is present: " + value),  () -> System.out.println("Value is absent"));
透過ifPresentOrElse方法,可以更加簡潔地處理類似情況,而不再需要頻繁使用條件語句。
or()
這個方法允許你在Optional物件為空時,提供一個備選的Optional物件。例如:
Optional<String> optional = Optional.empty();Optional<String> backup = Optional.of("Backup value");Optional<String> result = optional.or(() -> backup);System.out.println(result.get());  // Prints "Backup value"
isEmpty()
用於檢查Optional物件是否為空。例如:
Optional<String> optional = Optional.empty();if (optional.isEmpty()) {    System.out.println("Optional is empty");}  // Prints "Optional is empty"
其實這個方法等價於!optionalValue.isPresent(),只是不再需要取反,一定程度上能夠減少心智負擔。

Stream API增強(Java9)

takeWhile()
這個方法接收一個指定條件,它可以從一個有序的Stream中取出滿足條件的所有元素,一旦遇到不滿足條件的元素,就會停止處理後續元素。例如:
Stream.of("a""b""c""de""f")    .takeWhile(s -> s.length() == 1)    .forEach(System.out::print);  // Prints "abc"
在這個例子中,我們使用takeWhile()方法從一個Stream中取出所有長度為1的字串,直到遇到一個長度不為1的字串。
dropWhile()
該方法和takeWhile邏輯正好相反,透過指定條件來丟棄Stream流中滿足條件的元素,一旦遇到不滿足條件的元素,才會開始處理後續元素。
Stream.of("a""b""c""de""f")    .dropWhile(s -> s.length() == 1)    .forEach(System.out::print);  // Prints "def"
在這個例子中,使用dropWhile()方法丟棄所有長度為1的字串,直到遇到一個長度不為1的字串才開始處理後續的邏輯。
ofNullable()
該方法允許我們使用Optional物件來建立流。如果提供的元素為非空,則生成一個包含該元素的流;如果提供的元素為空,則生成一個空流。
Stream.ofNullable(null).forEach(System.out::print);  // Prints nothing
iterate()
該方法提供了一個新的過載形式,允許我們指定一個條件來定義流的終止條件,這樣可以更靈活地控制Stream流的生成。
Stream<Integer> stream = Stream.iterate(1, n -> n < 10, n -> n * 2);        stream.forEach(System.out::print);// Prints 1248

區域性變數型別推斷(Java10)

區域性變數型別推斷,其實就是引入了var關鍵字,類似js的var或者kotlin的val,在定義變數時不需要明確指定變數的型別,編譯器將根據上下文自動推斷。
但是要注意的是,var關鍵字僅適用於區域性變數,包括如下場景:
  • 方法的區域性變數
  • for迴圈中的索引變數
  • try-with-resources語句中的變數
可以看下面的示例:
var str = "Hello, World!";  // Stringvar num = 123;  // intvar list = new ArrayList<String>();  // ArrayList<String>for(var i = 0; i < 10; i++) { // int    System.out.println(i);}try(var reader = new BufferedReader(new FileReader("file.txt"))) { // BufferedReader}
在這個示例中,str、num和list的型別全都是由編譯器自動推斷的。可以想見,在開發時熟練運用var關鍵字,必然能顯著提升開發效率。

新的HTP客戶端(Java11)

在Java 11中,引入了一個新的HTTP客戶端API,替代了老舊的HttpURLConnection API。新的HTTP客戶端API支援HTTP/1.1和HTTP/2,以及同步和非同步程式設計模式,整體上來看確實更簡單易用。
新的HTTP客戶端API主要包括以下幾個部分:
  • HttpClient: 用來發送HttpRequest並返回HttpResponse。
  • HttpRequest: 用來建立請求,支援設定HTTP方法(GET、POST等)、URI、頭部、請求體等。
  • HttpResponse: 包含狀態程式碼、頭部和響應體。
  • BodyHandler、BodyPublisher和BodySubscriber: 這些都是處理HTTP請求體和響應體的工具。
以下是一個傳送GET請求的例子:
HttpClient client = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder()    .uri(new URI("http://example.com"))    .build();HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.statusCode());System.out.println(response.body());
可以很明顯看到程式碼量少了一大截,沒有太多的心智負擔。
並且新的客戶端還支援非同步請求,看這個例子:
HttpClient client = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder()    .uri(new URI("http://example.com"))    .build();CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());future.thenAccept(response -> {    System.out.println(response.statusCode());    System.out.println(response.body());});
利用CompletableFuture處理回撥,程式不會被阻塞,更加靈活和高效,可以同時處理多個HTTP請求或者同時進行其他任務。

Switch表示式增強(Java12)

在之前的版本中,switch只能作為語句來使用,看下面的示例:
int day = 2;String dayOfWeek;switch (day) {case1        dayOfWeek = "Monday";break;case2        dayOfWeek = "Tuesday";break;case3        dayOfWeek = "Wednesday";break;case4        dayOfWeek = "Thursday";break;case5        dayOfWeek = "Friday";break;case6        dayOfWeek = "Saturday";break;case7        dayOfWeek = "Sunday";break;defaultthrownew IllegalArgumentException("Invalid day of week: " + day);}System.out.println(dayOfWeek);  // Prints "Tuesday"
在Java12以後,switch可以直接作為表示式使用,直接看示例:
int day = 2;String dayOfWeek = switch (day) {case1 -> "Monday";case2 -> "Tuesday";case3 -> "Wednesday";case4 -> "Thursday";case5 -> "Friday";case6 -> "Saturday";case7 -> "Sunday";default -> thrownew IllegalArgumentException("Invalid day of week: " + day);};System.out.println(dayOfWeek);  // Prints "Tuesday"
無論是程式碼量還是可讀性上都有了改進。
對於過去很排斥switch,寧願用if-elseif的的同學,可能是個新的選擇。

文字塊(Java13)

在之前的版本中,要寫一個多行字串,得用許多的跳脫字元和字串連線,既繁瑣又容易出錯,程式碼看著還醜陋,比如下面這樣:
String html = "<html>\n" +"    <body>\n" +"        <p>Hello, world</p>\n" +"    </body>\n" +"</html>\n";System.out.println(html);
真是狗皮膏藥一樣,又臭又長。
這時候就很羨慕python或者js了,看看它們的多行字串,真是優雅,比如下面這段python的示例:
html = """<html>    <body>        <p>Hello, world</p>    </body></html>"""print(html)
什麼時候咱們Javaer也能這樣寫,就泰褲辣。
然後它就來了,Java自己的多行文字塊,直接看例子:
String html = """              <html>                  <body>                      <p>Hello, world</p>                  </body>              </html>              """;System.out.println(html);
並且多行文字快還支援字串插值,可以用${}來包含一個表示式,表示式的結果會被插入到字串中,比如下面這樣:
String name = "world";String greeting = """                   Hello, ${name}!                   Welcome to our website.                   """;System.out.println(greeting);
在這個例子中,name變數的值被插入到了greeting字串中。輸出的結果是:
Hello, world!Welcome to our website.
這使得在字串中包含動態內容變得更簡單,你可以在${}中包含任何Java表示式,包括變數、算術表示式、方法呼叫等。
等等,你說這跟Python怎麼這麼像?這……文化人的事情,什麼像不像的。

Record類(Java14)

在Java14中引入了一個新的關鍵字record,這是一種特殊的類,用來建立只包含資料的類。並且Record類是不可變的,所有欄位都是隻讀的,無法修改它們的值。
目前來看,一般用於替換過去的各類DTO、BO等僅用於資料傳輸的實體類,但是如果原有的類存在繼承關係,或者內部存在其他方法邏輯,那就不適用record類了。
看這個例子:
public record Point(int x, int y){ }
在這個例子裡,我們建立了Point類,編譯時還會自動生成一個建構函式、equals()hashCode()toString()方法,以及每個欄位的getter方法。
其實這個Record類和Kotlin的Data類存在諸多相似之處,但是也存在差異,主要體現在解構宣告上。
Kotlin的Data類支援解構宣告,如下所示:
val point = Point(12)val (x, y) = point
可以直接解構宣告x和y變數,並且能被初始化為point物件的x和y欄位的值。
這是Java的Record類所不具備的能力,所以還有待進步。

instanceof增強(Java16)

在Java 16以前,當我們使用instanceof來檢查一個物件是否是某個型別的例項時,如果檢查成功,還得顯式地將這個物件轉型為對應型別,然後才能呼叫這個型別的方法或訪問它的欄位。例如:
Object obj = ...;if (obj instanceof String) {    String str = (String) obj;    System.out.println(str.length());}
在這個例子中,我們首先檢查obj物件是否是String型別的例項,然後將obj物件轉型為String型別,並將結果賦值給str變數,最後呼叫str變數的length()方法。
但是在Java 16中,可以在instanceof運算子後面直接定義一個變數,這個變數會自動被初始化為轉型後的物件,可以直接使用這個變數,再也不用顯式轉型。例如:
Object obj = ...;if (obj instanceof String str) {    System.out.println(str.length());}
我們在instanceof運算子後面定義了一個str變數,這個變數自動被初始化為obj物件轉型為String型別後的結果,然後我們直接呼叫str變數的length()方法,無需顯式轉型。
這又是一個利於開發的特性,讓程式碼更加簡潔和直觀。

密封類和介面(Java17)

密封類和介面的主要目標是允許開發者對類或介面的繼承進行更精確的控制。
在Java17之前的版本中,我們無法限制哪些類可以繼承我們的類,或者哪些類可以實現我們的介面。
但是在Java 17中,透過使用sealednon-sealedpermits關鍵字,我們可以明確指定哪些類可以繼承我們的類或介面。
具體來說,當我們定義一個密封類或介面時,我們需要使用sealed關鍵字,並透過permits子句列出所有允許的子類或實現。
例如,如果我們有一個密封類Shape,我們可能只允許Circle和Square類繼承它:
publicabstract sealed classShapepermitsCircleSquare{//...}
在這個例子中,只有Circle和Square類可以繼承Shape類。
此外,我們還可以使用non-sealed關鍵字來指定某個類或介面可以被任何類繼承或實現。例如:
public non-sealed classShape{//...}
在這個例子中,任何類都可以繼承Shape類。
關於這個密封類的特性,我還沒有get到明確的使用場景,可能更適用於框架開發,比如定義了某些介面或類,但不需要它們被其他地方實現或繼承。
根據找到的相關資料,說密封類可以提供額外的型別安全性,在編譯時就能確定所有可能的子型別,避免在執行時去處理未知型別導致的潛在問題,使程式碼更加健壯。
如果有更清晰的應用場景,煩請各位同學不吝賜教~
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

結尾

雖然很多公司還在用Java8,但是並不能阻礙學習的腳步,在自己的專案裡試一下這些新特性,一定會大呼過癮。

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。

文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章