Java中字符串switch的實現細節
譯文出處: Java譯站 原文出處:javarevisited
自從Java允許在switch及case語句中使用字符串以來,許多開發人員都使用了這一特性,不過如果使用整型或者枚舉的話會更好。這是JDK7中最受歡迎的特性之一,同樣的還有自動資源管理以及多異常捕獲。盡管我個人不太喜歡這個特性,因為使用枚舉的方式其實更好,但我并不是特別反對使用它。一個原因當然是它很方便,如果程序中已經用到了字符串,這樣做的確很順手,不過我建議在生產環境的代碼中使用新特性之前最好了解下它是如何工作的。我第一次聽說這個特性的時候,我認為這肯定是通過equals()和hashCode()方法來實現的,我更關心的是Java 7中的字符串的switch是如何實現的。我對這個感興趣還有一個原因,是我想在面試中問一下這個問題,如果面試中有類似這樣的一個問題的話會非常有趣。驗證它其實非常簡單,你只需用字符串寫一段switch的代碼,然后反編譯一下,看看編譯器是如何翻譯它們的就可以了。那么還等什么,趕快來看下switch中的字符串是如何工作的吧?
原始代碼:
這是一個簡單的測試程序,它有一個main方法,里面有一個switch塊在操作String變量。程序中用到的字符串參數是運行時傳遞進來的,你可以從main方法的字符串數組中獲取到。有三種模式來啟動這個程序,主動模式,被動模式,以及安全模式。對于這些已知的確定值,其實用枚舉來實現要更好,但如果你已經決定使用字符串了,你得確保你寫的是大寫的,不要寫成小成或者駱駝式的,這樣會出現大小寫敏感的問題。你還可以看下這篇教程 ,了解下如何在Java 7的switch表達式中正確地使用字符串。
/** * Java Program to demonstrate how string in switch functionality is implemented in * Java SE 7 release. */ public class StringInSwitchCase { public static void main(String[] args) { String mode = args[0]; switch (mode) { case "ACTIVE": System.out.println("Application is running on Active mode"); break; case "PASSIVE": System.out.println("Application is running on Passive mode"); break; case "SAFE": System.out.println("Application is running on Safe mode"); } } }
編譯運行這段代碼需要安裝JDK7才行。隨便哪個版本的JDK7都可以。
反編譯后的代碼:
下面是上述代碼使用jdk1.7.040編譯后再反編譯的結果。如果你是Java新手,想知道如何反編譯Java類來實現逆向工程,看下<a href=”http://javarevisited.blogspot.sg/2013/01/how-to-decompile-class-file-in-java-Eclipse-javap-example.html” target=”blank”>這篇文章。JDK的每個版本都會加入越來越多的語法糖,因此對于各個水平的Java開發人員來說,知道 如何反編譯Java類是想當重要的。你寫的代碼和實現執行的差距會越來越大。了解Java類的文件格式以及字節碼指令會對你很有幫助 。Java 8最近發布了一個新的特性,叫做lambda表達式,它通過編譯器來實現了內部匿名類,你可以反編譯下你的類文件來看下編譯器都加了些什么。
/** * Reverse Engineered code to show how String in Switch works in Java. */ import java.io.PrintStream; public class StringInSwitchCase{ public StringInSwitchCase() { } public static void main(string args[]) { String mode = args[0]; String s; switch ((s = mode).hashCode()) { default: break; case -74056953: if (s.equals("PASSIVE")) { System.out.println("Application is running on Passive mode"); } break; case 2537357: if (s.equals("SAFE")) { System.out.println("Application is running on Safe mode"); } break; case 1925346054: if (s.equals("ACTIVE")) { System.out.println("Application is running on Active mode"); } break; } } }
看到這個代碼,你知道原來字符串的switch是通過equals和hashCode()方法來實現的。記住,switch中只能使用整型,比如byte。short,char以及int。還好hashCode()方法返回的是int,而不是long。通過這個很容易記住hashCode返回的是int這個事實,事實上我自己都會經常忘了或者弄混。仔細看下可以發現,進行switch的實際是哈希值,然后通過使用equals方法比較進行安全檢查,這個檢查是必要的,因為哈希可能會發生碰撞。因此它的性能是不如使用枚舉進行switch或者使用純整數常量,但這也不是很差。因為Java編譯器只增加了一個equals方法,如果你比較的是字符串字面量的話會非常快,比如”abc” ==”abc”。如果你把hashCode()方法的調用也考慮進來了,那么還會再多一次的調用開銷,因為字符串一旦創建了,它就會把哈希值緩存起來,這個可以看下我自己比較喜歡的一篇文章 為什么Java中的字符串是不可變的。因此如果這個siwtch語句是用在一個循環里的,比如逐項處理某個值,或者游戲引擎循環地渲染屏幕,這里hashCode()方法的調用開銷其實不會很大。不管怎樣,我仍然認為使用字符串的switch來代表幾個固定的值不是一個最佳實踐,Java里的枚舉的存在是有它的原因的,每個Java開發人員都應該使用它。
這就是Java 7如何實現的字符串switch。正如我所料,它使用了hashCode()來進行switch,然后通過equals方法進行驗證。這其實只是一個語法糖,而不是什么內建的本地功能。選擇權在你,我個人來說不是很喜歡在switch語句中使用字符串,因為它使得代碼更脆弱,容易出現大小寫敏感的問題,而且編譯器又沒有做輸入校驗 。事實上對于性能關鍵的代碼,以前的整型常量和枚舉的寫法是我的最愛,在這里可讀性和代碼質量都更重要。事實上,99。99%的情況下,枚舉都比使用字符串的switch或者整型要好,這也是它們存在于Java語言中的實際意義 。這個特性就是為了改變這種不良的編碼實踐而生的,我很難找到什么情況下非要針對一組輸入值在switch分支中使用字符串,如果你有一個令人信服的使用字符串switch的原因,請告訴我,我或者會改變我現在的想法。
譯注:更深入的話可以了解下Java在字節碼層面是如何實現的,可參考這篇文章。