《Effective Java》讀後感java
靜態工廠方法優勢:程序員
靜態工廠方法與構造器(構造方法)不一樣的第一大優點在於,它們有名稱。見名知意,突出區別。編程
靜態工廠方法與構造器不一樣的第二大優點在於,沒必要在每次調用它們的時候都建立一個新對象。數組
靜態工廠方法與構造器不一樣的第三大優點在於,它們能夠返回原返回類型的任何子類型的對象。緩存
靜態工廠方法與構造器不一樣的第四大優點在於,在建立參數化類型實例的時候,它們使代碼變得更加簡潔。安全
例如:Map<String,List<String>> map=newHashMap<String,List<String>>();併發
假設HashMap提供了靜態工廠方法:(目前是沒有該靜態方法)框架
public static <K,V> HashMap<K,V> newInstance(){dom
return newHashMap<K,V>();ide
}
上面的代碼就能夠簡寫爲:Map<String,List<String>> map= HashMap.newInstance();
靜態工廠方法缺點:
主要缺點在於,類若是不含公有的或者受保護的構造器,就不能被子類化;
第二個缺點在於,它們與其餘的靜態方法實際上沒有任何區別。
假如咱們的一個實體類有不少的屬性值,可是這些屬性值又是可選的。若是咱們遇到這樣的是類,如何設計出方便的實體類呢?
一般解決辦法一:重疊構造器
複製代碼
public class User {
private String id; // id(必填)
private String name; // 用戶名(必填)
private String email; // 郵箱(可選)
private int age; // 年齡(可選)
private String phoneNumber; // 電話(可選)
private String address; // 地址(可選)
public User(String id, String name) {
this(id, name, "qq.com", 0, "120", "廣州");
}
public User(String id, String name, String email) {
this(id, name, email, 0, "120", "廣州");
}
public User(String id, String name, String email, int age) {
this(id, name, email, age, "120", "廣州");
}
public User(String id, String name, String email, int age, StringphoneNumber) {
this(id, name, email, age, phoneNumber, "廣州");
}
public User(String id, String name, String email, int age, StringphoneNumber, String address) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
this.phoneNumber = phoneNumber;
this.address = address;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
}
注:許多你不想設置的參數,可是還不得不爲他們傳遞值
一般解決辦法二: JavaBean模式
調用一個無參構造器來創造對象,而後調用setter方法來設置每一個必須的參數,以及每一個相關的可選參數
複製代碼
public class User {
private String id; // id(必填)
private String name; // 用戶名(必填)
private String email; // 郵箱(可選)
private int age; // 年齡(可選)
private String phoneNumber; // 電話(可選)
private String address; // 地址(可選)publicUser() {
super();
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
public void setAge(int age) {
this.age = age;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void setAddress(String address) {
this.address = address;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="
+ phoneNumber + ",address=" + address + "]";
}
}
注:JavaBeans模式自身有着很嚴重的缺點。由於構造過程被分到幾個調用中,在構造過程當中JavaBean可能處於非一致的狀態。JavaBeans模式阻止了把類作成不可變的可能,這就須要確保他的線程安全。
解決辦法三:構建器
複製代碼
public class User {
private String id; // id(必填)
private String name; // 用戶名(必填)
private String email; // 郵箱(可選)
private int age; // 年齡(可選)
private String phoneNumber; //電話(可選)
private String address; //地址(可選)
public static class Builder{
private String id; // id(必填)
private String name; // 用戶名(必填)
private String email; // 郵箱(可選)
private int age; // 年齡(可選)
private String phoneNumber; //電話(可選)
private String address; //地址(可選)
public Builder(String id, String name) {
super();
this.id = id;
this.name = name;
}
public Builder email(String email){
this.email = email;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Builder phoneNumber(String phoneNumber){
this.phoneNumber = phoneNumber;
return this;
}
public Builder address(String address){
this.address = address;
return this;
}
public User builder(){
return new User(this);
}
}
private User(Builder builder){
this.id = builder.id;
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phoneNumber = builder.phoneNumber;
this.address = builder.address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="
+ phoneNumber + ",address=" + address + "]";
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
}
注:不直接生成想要的對象,而是讓客戶端利用全部必要的參數調用構造器(或者靜態工廠),獲得一個builder對象。而後客戶端在builder對象上調用相似於setter的方法,來設置每一個相關的可選參數。最後,客戶端調用無參的build方法來生成不可變的對象。
測試代碼
public static void main( String[] args )
{
User user = new User.Builder(UUID.randomUUID().toString(),"parry").address("廣州").builder();
System.out.println(user.toString());
}
例如:Strings=new String(「abc」); // Don’t do this
該語句每次被執行的時候都建立一個新的String實例,可是這些建立對象的動做全都是沒必要要的。傳遞給String構造器的參數(」abc」),自己就是一個String實例,功能方面等同於構造器建立的全部對象。若是這個方法在一個循環中,就會建立成千上萬的沒必要要的String實例。
正確的寫法:String s=」abc」;
在Java1.5版本的新特性中,有一種建立多與對象的新方法:自動裝箱!
例如:
Long sum=0L;
for(long i=0; i<Integer.MAX_VALUE; i++){
sum+= i;
}
system.out.println(sum);
執行以上代碼,耗時大約43秒;
這段程序算出的答案是正確的,可是比實際狀況要更慢一些,只由於變量sum使用的Long類型,而不是long,這意味着程序構造了大約2的31次方個多餘的Long實例。若是將Long改成基本類型long。程序運行時長爲6.8秒附近。
結論很明顯:優先使用基本類型而不是裝箱基本類型,要小心無心識的自動裝箱。
內存泄漏:若是一個棧先是增加,而後再收縮,那麼從棧中彈出來的對象將不會被當作垃圾回收,即便使用棧的程序再也不引用這些對象,它們也不會被回收。由於,棧內部維護着對這些對象的過時引用(obsolete reference)。所謂的過時引用,是指永遠也不會再被解除引用。
出現內存泄漏的三種須要注意的狀況:
一:類是本身管理內存;
二:緩存; 把對象放入緩存中,很容易被遺忘。
三:監聽器和其餘回調。
儘管Object是一個具體類,可是設計它主要是爲了擴展。它的全部非final方法(equals、hashCode、toString、clone和finalize)都用明確的通用約定,由於它們被設計成要被覆蓋(override)的。
equals方法實現了等價關係:
自反性(reflexive):對於任何非null的引用值x,x.equals(x)必須返回true
對稱性(symmetric):對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true
傳遞性(transitive):對於任何非null的引用值x、y和z,若是x.equals(y)返回true,且y.equals(z)返回true,那麼x.equals(z)必須返回true
一致性(consistent):對於任何非null的引用值x和y,只要equals的比較操做再對象中全部的信息沒有被修改,屢次調用x.equals(y)就會一致地返回true,或者一致的返回false
非空性(Non-nullity):對於任何非null的引用值x,x.equals(null)必須返回false
引用java API,關於Object中hashCode方法的一段話:
返回該對象的哈希碼值。支持此方法是爲了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常規協定是:
在 Java 應用程序執行期間,在對同一對象屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。
若是根據equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每一個對象調用 hashCode 方法都必須生成相同的整數結果。
若是根據equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求必定生成不一樣的整數結果。可是,程序員應該意識到,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。
對於成員(域、方法、嵌套類和嵌套接口)有四種可訪問級別:下面根據可訪問性遞增順序說明:
私有的(private):只有在聲明該成員的頂層類內部才能夠訪問這個成員;
包級私有(package-private):聲明該成員的包內部的任何類均可以訪問這個成員。從技術上講,它被稱爲「缺省訪問級別」;
受保護的(protected):聲明該成員的類的子類能夠訪問這個成員(可是有一些限制[JLS,6.6.2])。
公有的(public):在任何地方均可以訪問。
爲了使類稱爲不可變,要遵循下面五條規則:
1, 不要提供任何會修改對象狀態的方法;
2, 保證類不會被擴展;
3, 使全部的域都是final的;
4, 使全部的域都稱爲私有的;
5, 確保對於任何可變組件的互斥訪問
現有的類能夠很容易被更新,以實現新的接口;
接口是定義mixin(混合類型)的理想選擇;
接口容許咱們構造非層次結構的類型框架。
例如:比較器函數表明一種爲元素排序的策略。
謹慎地選擇方法的名稱;見名知意
不要過於追求提供便利的方法;
避免過長的參數列表;
沒法使用for-each循環的三種狀況:
1,過濾:若是須要遍歷集合,並刪除選定的元素,就須要使用顯式迭代器,以即可以調用它的remove方法;
2,轉換:若是須要遍歷列表或者數組,並取代它部分或者所有的元素值,就須要列表迭代器或者數組索引,以便設定元素的值
3,平行迭代:若是須要並行的遍歷多個集合,須要顯式的控制迭代器或者索引變量,以便全部迭代器或者索引變量均可以獲得同步前移
以上狀況,須要使用普通for循環。
精確計算,推薦使用BigDecimal(性能會相對慢一些)
字符串不適合代替其餘的值類型;
字符串不適合代替枚舉類型;
字符串不適合代替彙集類型;
字符串不適合代替能力表;
爲了得到能夠接收的性能,請使用StringBuilder或StringBuffer代替String進行字符串的拼接。
使用反射機制的缺點:
1,喪失了編譯時類型檢查的好處,包括異常檢查;
2,執行反射訪問所須要的代碼很是笨拙和冗長。
2,性能損失。反射方法調用比普通方法調用慢了不少。
不要去計較效率上的一些小小的得失,在97%的狀況下,不成熟的優化纔是一切問題的根源。
在優化方面,咱們應該遵照兩條規則:
1, 不要進行優化;
2, 仍是不要進行優化;也就是說在你沒有絕對清晰的優化方案以前,請不要進行優化。
總結:不要費力編寫快速的程序,應該努力編寫好的程序,速度天然隨之而來。
缺點是:
1, 類實現該接口以後,一旦一個類被髮布,就大大下降了「改變該類的實現」的靈活性。
2, 它增長了出現bug和安全漏銅的可能性;
3,隨着類發行新的版本,相關的測試負擔也增長了。
Mutableperiod***。
編寫更加健壯的readObject方法:
對於對象引用域必須保持爲私有的類,要保護性的拷貝這些域中的每一個對象。不可變類的可變組件就屬於這一類別;
對於任何約束條件,若是檢查失敗,則拋出一個InvalidObjectException異常。這些檢查動做應該跟在全部的保護性拷貝以後。
若是整個對象圖在被反序列化以後必須進行驗證,就應該使用ObjectInputValidation接口
不管是直接仍是間接方式,都不用調用類中任何可被覆蓋的方法
若是依賴readResolve進行實例控制,帶有對象引用類型的全部實例域則都必須聲明爲tansient的。不然,會被Mutableperiod***。