Java 程式面試:原始型別模式如何改變 instanceof 與 switch?
題干與適用場景
團隊評估 Java 25 中用於 instanceof、switch 和 record pattern 的原始型別模式預覽功能。解析器得到 Number 值,需要分類整數與浮點數,同時不能發生隱含縮窄或 null 崩潰。請解釋語言規則,提供小例子,並說明相容性與發布決策。
JEP 507 在 Java 25 仍是預覽功能。強回答必須說明編譯與執行需要預覽開關,正式環境不能把它當成自動穩定的語言契約。
面試官考察點
- 是否區分參照模式、原始型別模式與裝箱轉換。
- 是否能解釋安全擴寬、不安全縮窄與資訊遺失。
- 是否明確處理
null、NaN和無限大。 - 是否理解窮舉式 switch 與預覽相容邊界。
- 是否能設計穩定回退與測試,而不是只因程式碼更短就採用。
面試官要聽到規則和反例,不是 JEP 編號清單。
回答前需要釐清的問題
- CI 和正式環境是否允許預覽功能?若不允許,必須使用 Java 21 時代的模式或明確轉換。
- 「分類數值」要保留原值,還是允許得到有界整數?這會改變縮窄是否成立。
null應拒絕、映射為UNKNOWN,還是由專用分支處理?NaN和無限大是否為合法輸入?型別匹配不會自動把它們變成普通有限值。- 同一份原始碼是否必須在非預覽 JDK 編譯?若必須,隔離預覽程式碼或保留穩定實作。
30 秒回答框架
「Java 25 的 JEP 507 預覽版允許模式在原始型別場景中表達更統一的匹配,過去需要裝箱或複雜守衛。編譯器只允許符合規則的安全轉換,並拒絕可能遺失資訊的縮窄。我會先處理可為 null 的參照,再測試邊界值、NaN 和無限大,並把預覽開關固定在工具鏈中。若部署不能固定 Java 25 與預覽開關,就使用穩定 switch 或明確範圍檢查。」
分步深入解答
1. 先確認值的靜態型別
模式相容性不代表任意數值都能轉成任意原始型別。Number 參照可能實際持有 Integer、Long、Double 或其他實作。先匹配包裝型別再拆箱,與對原始選擇器使用原始模式,是不同契約。討論窮舉前先說清楚選擇器型別與轉換方向。
static String classify(Number value) {
if (value == null) {
return "missing";
}
return switch (value) {
case Integer i -> "int:" + i;
case Long l -> "long:" + l;
case Double d when Double.isNaN(d) -> "nan";
case Double d -> "double:" + d;
default -> "other";
};
}這個穩定範例匹配包裝型別,沒有宣稱所有 Number 都能安全縮窄成 int。
2. 解釋擴寬與縮窄
JEP 507 統一原始模式規則,使模式變數在轉換安全時可以接收原始值。int 擴寬到 long 會保留全部數值;long 縮窄到 int 可能遺失高位,因此不能允許靜默接受。回答應指出被拒絕的轉換,若業務確實需要縮窄,就提出明確範圍檢查。
static String bucket(long value) {
if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
int narrowed = (int) value;
return "small:" + narrowed;
}
return "wide:" + value;
}這裡的強制轉型有可見不變量。模式不能取代這個證明。
3. 把 null 與原始匹配分開
原始值不能是 null,但包裝選擇器可以。參照選擇器的 switch 不會把 null 默認成數值分支。需要完整結果時加入 case null,否則在進入 switch 前拒絕。不要讓 default 掩蓋 null 或未知包裝型別,因為兩者的維運處理不同。
浮點型別匹配也不代表有限值。若下游依賴排序或算術,必須明確檢查 NaN、正無限與負無限。
4. 有意構造完整 switch
原始模式和常數能讓 switch 更短,但覆蓋範圍仍取決於選擇器與守衛。帶守衛的分支不能覆蓋該型別全部值。使用無守衛分支或 default 處理剩餘值,並為可為 null 的選擇器加入 case null。計費、解析或安全策略使用的結果必須可觀測,不能靜默吞掉未知值。
5. 檢查預覽邊界
JEP 507 在 Java 25 是預覽功能,編譯與執行需要匹配的 --enable-preview,原始碼級別也必須和 JDK 一致。後續 JDK 可能改變或刪除預覽語法。CI 應同時編譯穩定回退版本,產物記錄 JDK、預覽開關與語言級別。公共函式庫也不應強迫呼叫者為一個 API 開啟預覽。
6. 比較替代方案
異質 Number 輸入且重視相容性時,使用包裝型別模式。選擇器已是原始型別且部署能固定預覽工具鏈時,才考慮原始 switch。業務規則是「必須落入 32 位」時,明確範圍檢查更能表達意圖,也不依賴語法變化。程式碼更短不代表數值契約更安全。
高品質示範回答
「我會先說明選擇器和 null 策略。Java 25 的 JEP 507 仍是預覽功能,不能說成穩定語法。原始型別模式讓 instanceof、switch 和 record pattern 的表達更一致,但不會讓縮窄自動無損。int 擴寬為 long 保留所有值,long 轉 int 需要範圍證明。對包裝輸入,我處理 null、區分包裝型別,並單獨分類 NaN 與無限大。每個 switch 都有明確剩餘分支,守衛分支不能提供完整覆蓋。若正式環境不能固定 Java 25 和預覽開關,我保留包裝模式或明確檢查,並在 CI 同時驗證兩條路徑。測試包括 null、數值邊界、越界值、NaN、無限大、未知包裝型別、守衛未命中和非預覽編譯。」
常見錯誤
- 把預覽語法說成穩定能力 → 後續 JDK 可能改變或刪除 → 固定 JDK 並保留回退。
- 認為任何數值轉換都安全 → 縮窄可能遺失位元或拒絕數值 → 說明轉換方向並證明範圍。
- 讓
default吸收 null → null 與未知型別需要不同處理 → 加入case null或在匹配前拒絕。 - 把 double 匹配當成有限值 →
NaN與無限大仍是該型別 → 明確測試和分類。 - 把帶守衛分支當成窮舉 → 不符合守衛的數值仍未匹配 → 增加無守衛剩餘分支並測試覆蓋。
追問及應對
long 模式可以安全綁定 int 選擇器嗎?
可以,因為擴寬保留 int 的全部數值。反向轉換通常不安全,long 可能超過 int 範圍。如果業務有範圍保證,就把檢查寫出來並測試兩端邊界。
為什麼不把所有值都裝箱?
裝箱便於檢查異質輸入,但會引入包裝語意和可能的配置,仍不能證明轉換保值。選擇裝箱應基於 API 相容性,而不是逃避數值推理。
如何測試預覽實作?
讓預覽實作和穩定回退執行同一組用例:null、零、最小最大值、剛越界的值、NaN、正負無限、未知包裝型別、守衛未命中和非法輸入。CI 固定精確 release 與預覽開關,再執行非預覽相容任務。
Java 26 改變預覽規則怎麼辦?
把語言級別視為產物契約。閱讀新版本說明,編譯候選分支,拿回退測試對照語意,只有原始碼和執行時矩陣通過才推廣。不要在正式環境靜默開啟新的預覽開關。