【原創】JDK 9-17新功能30分鐘詳解-語法篇-var

来源:https://www.cnblogs.com/yeyu456/archive/2022/08/17/16593725.html
-Advertisement-
Play Games

JDK 9-17新功能30分鐘詳解-語法篇-var 介紹 JDK 10 JDK 10新增了新的關鍵字——var,官方文檔說作用是: Enhance the Java Language to extend type inference to declarations of local variable ...


JDK 9-17新功能30分鐘詳解-語法篇-var

介紹

JDK 10

JDK 10新增了新的關鍵字——var,官方文檔說作用是:

Enhance the Java Language to extend type inference to declarations of local variables with initializers

大體意思就是用於帶有初始化的局部變數聲明,廢話不多說,我們直接用具體代碼來展示實際的作用。

List<String> listBefore10 = new ArrayList<>();   # 在JDK10之前
var listAfter10 = new ArrayList<String>();   # 在JDK10之後
listBefore10.add("9");
listAfter10.add("10");

JDK 11

JDK 11對var做了調整,允許var關鍵字用於Lambda函數裡面的參數類型聲明,示例:

var result = Arrays.asList("Java", "11").stream().reduce((var x, var y) -> x + y);
System.out.println(result.orElseThrow());

原理

可以看到使用了var關鍵字後,節省了一點聲明內容,但是仔細一看,例如一個泛型類型從聲明部分,挪到了初始化部分去了。我們直接看反編譯後的class文件:
image
可以看到,其實var關鍵字對於我們來說就是一個語法糖,編譯完成後var聲明的變數類型已經確定下來了,實際運行的時候是無法起到類似於Javascript語言var聲明變數後還能動態更換類型的效果。至於為什麼使用必須同時聲明和初始化的方式,而不是先聲明,後初始化再進行類型推斷的方式,官方大體是基於下麵考慮的

The majority (more than 75% in both JDK and broader corpus) of local variables with initializers were already effectively immutable anyway, meaning that any "nudge" away from mutability that this feature could have provided would have been limited.

超過75%的JDK庫及其相關擴展中,帶有初始化的局部變數,都是有效不可變的,即使提供了延後初始化功能起到的作用也不大。

We chose the restriction ... because it covers a significant fraction of the candidates while maintaining the simplicity of the feature and reducing "action at a distance" errors.

使用這種方式既能覆蓋絕大數使用場景,又能保持功能簡潔,另外一方面也是為了減少可能存在的維護問題,理解的心智成本,例如聲明後經過幾百行的代碼再進行初始化。
具體內容感興趣的可以看下JEPS 286的Scope Choices部分。

限制

1. 必須初始化

var原理大抵是編譯器通過初始化的值推斷聲明的類型,由此引出使用它的一個約束——聲明的同時必須進行初始化。

# 錯誤示例
var listAfter10;
listAfter10 = new ArrayList<String>();
listAfter10.add("10");

用以上代碼直接編譯運行,JDK會報錯,提示:

java: 無法推斷本地變數 listAfter10 的類型
(無法在不帶初始化程式的變數上使用 'var')

如果使用IDE,都不用運行就會直接提示你,例如Intellij IDEA:
image

Cannot infer type:'var' on variable without initializer

回看之前說到的官方聲明,“type inference to declarations of local variables with initializers”,with initializers已經很好說明使用它必須初始化,否則編譯器無法進行類型推斷。

2. 不能為null值

雖然進行初始化,但是使用null值的話,編譯器仍然無法進行類型推斷確定你最終的類型,也會報錯。
image

Cannot infer type:variable initializer is 'null'

3. 不能用於非局部變數

回看之前說到的官方聲明,“type inference to declarations of local variables with initializers”,local variable只能用於局部變數的使用,全局變數或者對象屬性聲明都不行,例如下麵示例是無法正常運行:

# 錯誤示例
public class Java10 {

    public var field = "Not allow here";
}

編譯直接報錯

此處不允許使用 'var'

4. 不能用於Lambda表達式類型的聲明

編譯器不支持推斷匿名函數的類型,例如:

# 錯誤示例
var lambdaVar = (String s) -> s != null && s.length() > 0;

image

Cannot infer type:lambda expression requires an explicit target type

編譯直接報錯

java: 無法推斷本地變數 lambdaVar 的類型
(lambda 表達式需要顯式目標類型)

但是這樣使用是可以的:

# 正確示例
var lambdaVar = (Function<String, Boolean>) (String s) -> s != null && s.length() > 0;

不過這樣寫就是脫褲子放屁了,直接寫在前面聲明不是更好。
亦或者雖然使用了匿名函數,但是其返回值並不是一個Lambda表達式類型,也是可以的。

# 正確示例
var result = Arrays.asList("Java", "10").stream().reduce((x, y) -> x + y);

5. Lambda函數var修飾參數不能與其他類型混合使用

# 錯誤示例
var result = Arrays.asList("Java", "11").stream().reduce((var x, y) -> x + y);
System.out.println(result.orElseThrow());
# 錯誤示例
var result = Arrays.asList("Java", "11").stream().reduce((var x, String y) -> x + y);
System.out.println(result.orElseThrow());

就是同一個匿名方法裡面要不就都是var修飾,要不就都不用,不能一個用,另外一個不用這種混合使用。當然官方說理論上是可行的,但是由於超出本次JEP規範定義,所以保留這些限制條件。

In theory, it would be possible to have a lambda expression like the last line above, which is semi-explicitly typed (or semi-implicitly typed, depending on your point of view). However, it is outside the scope of this JEP because it deeply affects type inference and overload resolution.This is the main reason for keeping the restriction that a lambda expression must specify all manifest parameter types or none.

使用規範

使用var帶來的好處是簡化了開發者的局部變數聲明成本,但是同時也可能造成代碼維護上的不便,特別是開發者和維護者不是同一個人的情況,為此官方也出了一版7個小點的var使用規範

1. 使用有意義的變數名

# 不規範示例
List<Customer> x = dbconn.executeQuery(query);
# 正確示例
var custList = dbconn.executeQuery(query);

2. 局部變數使用範圍儘可能地小

# 不規範示例
var items = new HashSet<Item>(...);
// ... 中間大概隔了幾百行的代碼 ...
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) {
   ...
}

一個方法行數過多,本身已經不利於維護,再加上使用var修飾變數,維護的人可能要滑鼠滾動一屏甚至幾屏才能看到var變數的具體使用,理解成本大大提高。所以一般情況下var變數保持在一屏內使用就好。

3. 初始化部分有意義時可以使用

var outputStream = new ByteArrayOutputStream();
var reader = Files.newBufferedReader(...);
var stringList = List.of("a", "b", "c");

初始化的部分,例如調用的方法名稱或者構造類型名字簡單易懂,可以直接使用。

4. 用於拆分鏈式調用或者嵌套調用

return "test string".stream()
       .collect(groupingBy(s -> s, counting()))
       .entrySet()
       .stream()
       .max(Map.Entry.comparingByValue())
       .map(Map.Entry::getKey);

上面的鏈式調用不方便理解或者調試,可以改為

Map<String, Long> freqMap = "test string".stream()
                            .collect(groupingBy(s -> s, counting()));
Optional<Map.Entry<String, Long>> maxEntryOpt = freqMap.entrySet().stream()
                                                .max(Map.Entry.comparingByValue());
return maxEntryOpt.map(Map.Entry::getKey);

這種情況下可以進一步優化為

var freqMap = "test string".stream().collect(groupingBy(s -> s, counting()));
var maxEntryOpt = freqMap.entrySet().stream().max(Map.Entry.comparingByValue());
return maxEntryOpt.map(Map.Entry::getKey);

5. 不用顧慮用於面向介面開發

List<String> normalList = new ArrayList<>();
var varList = new ArrayList<String>();  # varList最終推斷類型是ArrayList<String>而不是List<String>

由於var只能用於局部變數,對於面向介面開發的原則基本無影響,問題主要是var初始化部分的類型依賴,如果發生變化,例如上面示例的ArrayList改成LinkedList,varList的類型隨之變化。但是如果遵循規範“2. 局部變數使用範圍儘可能地小”的話,影響面就會比較小。

6. 謹慎用於泛型類型

# 正確示例
PriorityQueue<Item> itemQueue = new PriorityQueue<>();
var itemQueue = new PriorityQueue<Item>();
# 不規範示例
var itemQueue2 = new PriorityQueue<>();  # itemQueue2最終推斷類型是PriorityQueue<Object>

可能導致類型推斷的最終類型不是想要的泛型類型。

7. 謹慎用於字面量

byte flags = 0;
short mask = 0x7fff;
long base = 17;

改成

var flags = 0;
var mask = 0x7fff;
var base = 17;

全部類型都會推導為int。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前後端分離開發非常普遍,後端處理業務,為前端提供介面。服務中總會出現很多運行時異常和業務異常,本文主要講解在 SpringBoot 實戰中如何進行異常統一處理和請求參數的校驗。 ...
  • Python可以實現給QQ郵箱、企業微信、微信等等軟體推送消息,今天咱們實現一下Python直接給微信推送消息。 這裡咱們使用了一個第三方工具pushplus 單人推送 實現步驟: 1、用微信註冊一個此網站的賬號2、將token複製出來,記錄到小本本上。 代碼展示 import requests # ...
  • IaaS之計算 1.1 IaaS概述 IaaS(Infrastructure as a Service )提供托管的 IT 基礎架構,供用戶調配處理能力、存儲、網路和其他基礎計算資源。IaaS 提供商運行並管理此基礎架構,用戶可以在此基礎架構上運行選擇的操作系統和應用程式軟體。 在雲平臺中還會涉及以 ...
  • 1、Jar 包 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.comp ...
  • 1、conftest.py介紹 conftest.py是pytest框架的一種固定寫法,把fixture或者自己定義的插件寫到這個文件里就會自動去調用。我們前面都是將fixture寫到測試用例文件里,在實際工作中更推薦寫到conftest.py文件中,這樣更加靈活,易維護。 2、conftest.p ...
  • 1.樹的基礎知識概述 樹狀圖是一種數據結構,它是由 n(n>=1)個有限結點組成一個具有層次關係的集合。把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:每個結點有零個或多個子結點;沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外 ...
  • “如果一個線程兩次調用start(),會出現什麼問題?” 如果這個問題出自阿裡p6崗位第一面的提問,你能回答出來嗎? 大家好,我是Mic,一個工作了14年的Java程式員。 關於這個問題,涉及到線程的生命周期,我把完整的回答整理到了15W字的面試文檔裡面大家可以私信我領取。 下麵來看看高手的回答。 ...
  • 作者:小牛呼嚕嚕 | https://xiaoniuhululu.com 電腦內功、JAVA底層、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕 」 什麼是Java泛型 Java 泛型(generics)是 Jdk 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制, 該機制允許程式員在編 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...