Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java
字符串被設計用來表示文本,它們在這方面作得很好。由於字符串是如此常見,而且受到開發語言的良好支持,因此很天然地傾向會將字符串用於其餘目的,而不是它們設計的本來目的。這一條目討論了一些不該該使用字符串作的事情。git
字符串是其餘值類型的不良替代品。當一段數據從文件、網絡或鍵盤輸入進入程序時,它一般是字符串形式的。有一種天然的傾向是這樣的,可是這種傾向只有在數據本質上是文本的狀況下才合理。若是是數值類型,則應將其轉換爲適當的數值類型,如int、float或BigInteger。若是是「是」或「否」問題的答案,則應將其轉換爲適當的枚舉類型或boolean值。更一般地說,若是有合適的值類型,不管是基本值仍是對象引用,都應該使用它;若是沒有,你應該編寫一個。雖然這條建議彷佛很明顯,但常常被違反。程序員
字符串是枚舉類型的不良替代品。 正如條目 34中所討論的,枚舉使得枚舉類型常量比字符串好得多。github
字符串是聚合類型的不良替代品。 若是實體具備多個組件,則將其表示爲單個字符串一般是個壞主意。 例如,這裏是來自真實系統的一行代碼——標識符名稱已被更改:安全
// Inappropriate use of string as aggregate type String compoundKey = className + "#" + i.next();
這種方法有許多缺點。 若是用於分隔屬性的字符出如今某個屬性中,結果可能會產生混亂。 要訪問單個屬性,必須解析字符串,這很慢,很乏味且容易出錯。 不能提供equals,toString或compareTo方法,但必須接受String類提供的行爲。 更好的方法是編寫一個類來表示聚合,一般是私有靜態成員類(條目 24)。網絡
字符串是功能的不良替代品。 有時,字符串用於授予對某些功能的訪問權限。 例如,考慮ThreadLocal
的設計。 這樣的工具提供了每一個線程都有本身值的變量。 從版本1.2開始,Javal類庫就有了一個ThreadLocal工具,但在此以前,程序員必須本身動手來實現。 當多年前遇到設計這樣一個工具的任務時,幾我的獨立地想出了相同的設計,其中客戶提供的字符串鍵用於識別每一個線程局部變量:app
// Broken - inappropriate use of string as capability! public class ThreadLocal { private ThreadLocal() { } // Noninstantiable // Sets the current thread's value for the named variable. public static void set(String key, Object value); // Returns the current thread's value for the named variable. public static Object get(String key); }
這種方法的問題是,字符串鍵表示線程本地變量的共享全局命名空間。爲了使這種方法有效,客戶端提供的字符串鍵必須是唯一的;若是兩個客戶端各自決定爲它們的線程本地變量使用相同的名稱,它們無心中共享一個變量,這一般會致使兩個客戶端都失敗。並且,安全性不好。惡意客戶端能夠故意使用與另外一個客戶端相同的字符串密鑰來非法訪問另外一個客戶機端數據。工具
能夠經過用一個不可僞造的鍵(有時稱爲功能)替換字符串來修復這個API:線程
public class ThreadLocal { private ThreadLocal() { } // Noninstantiable public static class Key { // (Capability) Key() { } } // Generates a unique, unforgeable key public static Key getKey() { return new Key(); } public static void set(Key key, Object value); public static Object get(Key key); }
雖然這解決了基於字符串的API的這兩個問題,可是能夠作得更好。再也不真正須要靜態方法。它們能夠變成鍵上的實例方法,此時再也不是線程局部變量的鍵:而是線程局部變量。此時,頂層類再也不作任何事情,能夠刪除它,並將嵌套類重命名爲ThreadLocal:設計
public final class ThreadLocal { public ThreadLocal(); public void set(Object value); public Object get(); }
此API不是類型安全的,由於當從線程局部變量中檢索它時,必須將值從Object轉換爲其實際類型。原始的基於字符串的API類型安全是不可能實現的,基於鍵的API類型安全也是很難實現的,但經過使ThreadLocal成爲參數化類(第29項)來使這種API類型安全是一件簡單的事情:
public final class ThreadLocal<T> { public ThreadLocal(); public void set(T value); public T get(); }
粗略地說,這是java.lang.ThreadLocal
提供的API。 除了解決基於字符串的API的問題以外,它還比任何基於鍵的API更快,更優雅。
總而言之,當存在或能夠編寫更好的數據類型時,避免將對象表示爲字符串的天然傾向。 使用不當,字符串比其餘類型更麻煩,更靈活更差,速度更慢,更容易出錯。 字符串一般被濫用的類型包括基本類型,枚舉類型和聚合類型。