Effective Java2-學習筆記 第1-10條

本文記錄了,學習Effective Java 2版的心得

狀況:大二,學習JavaSE半年後,沒有設計和項目經驗.

代碼實例:大部分摘自原書或博客中,也可能少摘選.

大優點(主要缺點):摘自原書中.

其餘:首先感謝知了堂-陽哥在學習道路上長達一年的陪伴,也很慶幸在知了堂碰見鑫鑫.html

若有侵權或者錯誤,請務必留言,萌新感謝大佬的指點.java

1.考慮用靜態工廠方法替代構造器

用FruitFactory工廠類建立Apple或者Grape類,引用了Java中的反射機制(在運行期動態加載類)mysql

 1 package staticfactory;
 2 
 3 public class FruitFactory {
 4     public static Fruit getFruit(String type){
 5         Fruit f = null;
 6         try {
 7             // 經過反射機制在運行期建立實例
 8             f = (Fruit)Class.forName("staticfactory." + type).newInstance();
 9         } catch(Exception e){
10             System.out.print("目前沒法生產這種水果");
11         }
12         return f;
13     }
14     
15     public static void main(String[] args) {
16         Fruit f = FruitFactory.getFruit("Apple");
17         f.say();
18     }
19 }
FruitFactory
 1 package staticfactory;
 2 
 3 public class Apple implements Fruit {
 4 
 5     @Override
 6     public void say() {
 7         System.out.println("I am Apple");
 8     }
 9 
10 }
11 
12 
13 package staticfactory;
14 
15 public class Apple implements Fruit {
16 
17     @Override
18     public void say() {
19         System.out.println("I am Apple");
20     }
21 
22 }
Apple and Grape
  • Apple 和 Grape都實現(擴展)了Fruit接口(父類),Java中利用接口實現多繼承
  • 這樣作的緣由是:使得FruitFactory返回的類型統一都返回Fruit類(將Apple或Grape類上轉型爲Fruit父類,不能調用子類新增的屬性和方法)
1 package staticfactory;
2 
3 public interface Fruit {
4     public void say();
5 }
Fruit

 

大優點:程序員

  1. 它們有名稱
  2. 沒必要在每次調用它們的時候都建立一個新對象
  3. 它們能夠返回原返回類型的任何子類型的對象
  4. 在建立參數化類型實例的時候,它們使代碼變得更加簡潔

主要缺點:sql

  1. 類若是不含公有的或者受保護的構造器,就不能被子類化
  2. 它們與其餘的靜態方法實際上沒有任何區別

2.遇到多個構造器參數時要考慮用構建器

在NutritionFacts中聲明一個靜態內部類Builder,利用Builder類的相似setter方法(會返回當前Builder對象)來爲Builder類初始化,所有初始化後利用build()方法返回一個Nutrition對象.數據庫

Nutrition中有一個私有構造方法,它經過深拷貝Builder的屬性來完成初始化.編程

 1 package telescopingConstructor;
 2 
 3 public class NutritionFacts {
 4     // required
 5     private final int servingSize;
 6     private final int servings;
 7     
 8     // optional
 9     public final int calories;
10     public final int fat;
11     public final int sodium;
12     public final int carbohydrate;
13     
14     public static class Builder {
15         // required
16         private final int servingSize;
17         private final int servings;
18         
19         // optional
20         public int calories;
21         public int fat;
22         public int sodium;
23         public int carbohydrate;
24         
25         public Builder(int servingSize, int servings) {
26             super();
27             this.servingSize = servingSize;
28             this.servings = servings;
29         }
30         
31         public Builder calories(int val){
32             this.calories = val;
33             return this;
34         }
35         
36         public Builder fat(int val) {
37             this.fat = val;
38             return this;
39         }
40         
41         public Builder carbohydrate(int val){
42             this.carbohydrate = val;
43             return this;
44         }
45         
46         public Builder sodium(int val) {
47             this.sodium = val;
48             return this;
49         }
50         
51         public NutritionFacts build(){
52             return new NutritionFacts(this);
53         }
54     }
55     
56     private NutritionFacts(Builder builder){
57         servingSize = builder.servingSize;
58         servings = builder.servings;
59         calories = builder.calories;
60         fat = builder.fat;
61         sodium = builder.sodium;
62         carbohydrate = builder.carbohydrate;
63     }
64     
65     @Override
66     public String toString(){
67         return  servingSize + " " + servings + " " + calories + " " + fat + " " + sodium + " " + carbohydrate;
68     }
69     
70     public static void main(String[] args) {
71         NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
72         System.out.println(cocaCola.toString());
73     }
74     
75 }
NutritionFacts and Builder
  • Builder中setter方法要返回當前Builder對象,方便一系列的setter方法調用
  • Builder經過builder方法調用NutritionFacts構造方法(參數爲當前Builder對象),來完成真正的對象建立
  • 爲了方便和佔用少許內存,Builder爲靜態

 優點:segmentfault

  1. 客戶端代碼易於編寫,易於閱讀.bulider模式模擬了具名的可選參數,就像Ada和Python中的同樣
  2. Builder模式十分靈活,能夠利用單個builder構建多個對象(由於其實是調用其餘類的構造方法來拷貝信息並返回這個新對象,因此能夠聲明多個builder方法來構建多個對象).builder的參數能夠在建立對象期間進行調整(能夠在拷貝期間進行修改),也能夠隨着不一樣的對象而改變.builder能夠自動填充某些域,例如每次建立對象時自動增長序列號.
  3. 比JavaBeans更加安全

劣勢:數組

  1. 爲了建立對象,必須先建立它的構造器.雖然構建器的開銷在實踐中可能不那麼明顯.在十分注重性能的狀況下,可能成問題.
  2. Builder模式比重疊構造器模式更加冗長,由於在不少參數的時候才使用,好比4個或者更多.
  3. 與過期的構造器或者靜態工廠顯得十分不協調,一般最好一開始就使用構建器.
以上2018/10/07  23:05更新

3.用私有構造器或者枚舉類型強化Singleton屬性

Singleton指僅僅被實例化一次的類.緩存

 1 public class Sengleton_Lyze { 
 2     //1,構造方法私有化 
 3     private Sengleton_Lyze(){    
 4     } 
 5      
 6     //2,建立類的位惟一實例,private static 修飾 
 7     private static  Sengleton_Lyze instance; 
 8      
 9     //3,提供獲取實例的方法,public static 修飾 
10     public static Sengleton_Lyze getInstance(){ 
11         if (instance==null) { 
12             instance=new Sengleton_Lyze(); 
13         } 
14         return instance; 
15     } 
16 } 
懶漢模式
 1 /** 
 2  * 單例模式Sengleton 
 3  * 試用實際場合:有些對象只須要一個就夠了。 
 4  * 做用:保證整個實際應用程序中某個實例有且只有一個 
 5  * 類別:餓漢模式,懶漢模式 
 6  * 區別:餓漢模式的特色,加載類時比較慢,獲取比較慢。線程安全的。 
 7  *       
 8  *       
 9  */ 
10 public class Sengleton_Hunger { 
11     //1,構造方法私有化,不容許外接直接建立對象 
12     private Sengleton_Hunger(){  
13     } 
14     //2,建立類的惟一實例,使用private static 修飾 
15     private static Sengleton_Hunger instance = new Sengleton_Hunger(); 
16      
17     //3,獲取這個實例的方法 
18     public static Sengleton_Hunger getInstance(){ 
19         return instance; 
20     } 
21      
22 } 
餓漢模式
  • 序列化:爲了讓成爲單例的類實現 序列化的(Serializable),咱們僅僅在聲明上加上 "implements Serializable" 是不夠的。 爲了維護並保證 單例,必需要聲明全部實例域都是 瞬時(transient) 的,並提供一個readResolve方法。不然,每次反序列化一個序列化的實例時,都會建立一個新的實例。
  • 可是若是客戶端擁有 AccessibleObject.setAccessible() 的權限,就能夠經過反射機制調用私有構造器
  • 這裏實現單例主要是依靠構造操做只執行一次,構造器返回保存好的對象
 1 public class Test { 
 2     public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 
 3         //餓漢模式 
 4         Constructor sengleton_Hunger = Sengleton_Hunger.class.getDeclaredConstructor(); 
 5         sengleton_Hunger.setAccessible(true); 
 6         Sengleton_Hunger s1= (Sengleton_Hunger) sengleton_Hunger.newInstance(); 
 7         Sengleton_Hunger s2= (Sengleton_Hunger) sengleton_Hunger.newInstance(); 
 8         if (s1==s2) { 
 9             System.out.println("true"); 
10         }else { 
11             System.out.println("false"); 
12         } 
13          
14         //懶漢模式 
15         Constructor sengleton_Lyze = Sengleton_Lyze.class.getDeclaredConstructor(); 
16         sengleton_Lyze.setAccessible(true); 
17         Sengleton_Lyze s3= (Sengleton_Lyze) sengleton_Lyze.newInstance(); 
18         Sengleton_Lyze s4= (Sengleton_Lyze) sengleton_Lyze.newInstance(); 
19         if (s1==s2) { 
20             System.out.println("同樣的"); 
21         }else { 
22             System.out.println("不一樣的"); 
23         } 
24     } 
25  
26 } 
27 
28 結果
29 false
30 不一樣的
利用反射調用私有構造器
 1 public class Sltn {
 2     private static Sltn s = null;
 3     private static boolean flag = true;
 4     
 5     private Sltn(){
 6         System.out.println("flag:" + flag);
 7         if(flag){  
 8             flag = !flag;  
 9         }else{  
10             try {  
11                 throw new Exception("duplicate instance create error!" + Sltn.class.getName());  
12             } catch (Exception e) {  
13                 e.printStackTrace();  
14             }  
15         }  
16     }
17     
18     public static Sltn getInstance(){
19         if(null == s){  
20             s = new Sltn();  
21         }  
22         return s;  
23     }
24 }
經過異常來防止二次實例對象
  • 經過反射在必定的條件下,是可使用 類私有的構造方法來得到不一樣對象的。
  • 經過在類私有構造方法中加入條件,能夠防止反射得到第二個實例,但寫法有些多餘、複雜。

以上摘自:https://www.cnblogs.com/ybbzbb/p/5524261.html

1 package useenum;
2 
3 public enum Elvis {
4     INsTANCE;
5     
6     public void leaveTheBuilding(){
7         
8     }
9 }
enum
  • 簡潔,無償提供序列化機制,絕對防止屢次實例化,即便是在面對復炸的序列化或者反射攻擊的時候.
  • 單元素的枚舉類型已經成爲實現Singleton的最佳方法

4.經過私有構造器強化不可實例化的能力

在缺乏顯示構造器的狀況下,編譯器會自動提供一個共有的,無參的缺省構造器.對於用戶而言,這個構造器與其餘的構造器沒有任務區別.

企圖經過將類作成抽象類來強制該類不可被實例化,這是行不通的.該類能夠被子類化,而且該子類也能夠被實例化.這樣作甚至會誤導用戶,覺得這種類是專門爲了繼承而設計的.

添加一個私有構造器,來阻止在外部被實例化.

1 public class UtilityClass {
2     // Suppress default constructor for noninstantiability
3     private UtilityClass() {
4         throw new AssertionError();
5     }
6     
7     // Remainder omitted
8 }
添加一個私有構造器
  • AssertionEoor異常???(應該是斷言異常不清楚,弄清楚回來填坑)
  • 反作用:它使一個類不能被子類化.全部的構造器都必須顯式或隱式地調用超類構造器.

以上2018/10/8 23:19更新

 5.避免建立沒必要要的對象

通常來講,最好能重用對象而不是在每次須要的時候就建立一個相同功能的新對象.重用方式既快速,又流行.

 1 import java.sql.Connection;
 2 import java.sql.DriverManager;
 3 import java.sql.SQLException;
 4 
 5 public class DBUtil {
 6     private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
 7     private static final String UNAME = "root";
 8     private static final String PWD = "root";
 9 
10     private static Connection conn = null;
11 
12     static {
13         try {
14             // 1.加載驅動程序
15             Class.forName("com.mysql.jdbc.Driver");
16             // 2.得到數據庫的鏈接
17             conn = DriverManager.getConnection(URL, UNAME, PWD);
18         } catch (ClassNotFoundException e) {
19             e.printStackTrace();
20         } catch (SQLException e) {
21             e.printStackTrace();
22         }
23     }
24 
25     public static Connection getConnection() {
26         return conn;
27     }
28 }
使用static塊
  • 靜態代碼只在類加載時運用一次,關於其餘塊執行順序:https://www.cnblogs.com/ibelieve618/p/6403573.html

一種建立多餘對象的新方法,稱做自動裝箱(autoboxing),它容許程序員將基本類型和裝箱基本類型混用.

1 Integer sum = 0;
2 for(int i=1000; i<5000; i++){
3    sum+=i;
4 }
自動裝箱,下降性能
  • 對象包裝類是不變的,即一旦構造了包裝類,就不一樣意更改包裝在當中的值。同一時候,對象包裝類仍是final,所以不能定義它們的子類。

上面的代碼sum+=i可以當作sum = sum + i。但是+這個操做符不適用於Integer對象,首先sum進行本身主動拆箱操做。進行數值相加操做,最後發生本身主動裝箱操做轉換成Integer對象。其內部變化例如如下:

1 sum = sum.intValue() + i;
2 Integer sum = new Integer(result);
裝箱類
  • 要優先使用基本類型而不是裝箱基本類型,要小心無心識的自動裝箱.
  • 小對象的建立和回收動做是很是廉價的,特別是在現代的JVM實現上更是如此.,經過建立附加的對象,提高程序的清晰性,簡潔性和功能性,這一般是件好事.

6.消除過時的對象引用

即使有垃圾回收功能也須要考慮內存管理的事情.

 1 import java.util.Arrays;
 2 import java.util.EmptyStackException;
 3 
 4 public class Stack {
 5     
 6     private Object[] elements;
 7     private int size = 0;
 8     private static final int DEFAULT_INITIAL_CAPACITY = 16;
 9     
10     public Stack() {
11         elements = new Object[DEFAULT_INITIAL_CAPACITY];
12     }
13     
14     private void ensureCapacity() {
15         if (elements.length == size) {
16             elements = Arrays.copyOf(elements, 2 * size + 1);
17         }
18     }
19     
20     public void push(Object e) {
21         ensureCapacity();
22         elements[size++] = e;
23     }
24     
25     public Object pop() {
26         if (size == 0) {
27         throw new EmptyStackException();
28     }
29         
30         return elements[--size];
31     }
32     
33 }
內存泄漏的棧
  • 僅有程序員知道size以後的元素再也不重要了,可是對於垃圾回收器而言,數組中全部對象引用同等有效,稱這類內存泄漏爲"無心識的對象保存"更爲恰當.
 1 public Object pop() {
 2     if (size == 0) {
 3     throw new EmptyStackException();
 4     }
 5         
 6     Object result = elements[--size];
 7     elements[size] = null; // 清空引用
 8         
 9     return result;
10 }
利用null解決
  • elements數組長度沒有發生改變,後面保存着null

清空對象引用應該是一種例外,而不是一種規範行爲.消除過時引用最好的方法是讓包含該引用的變量結束其生命週期.

通常而言,只要類是本身管理內存,程序員就應該警戒內存泄漏問題.一旦元素被釋放掉,則該元素中包含的任何對象引用都應該被清空.

其餘內存泄漏常見來源:緩存,監聽器和其餘回調.

7.避免使用終結方法

終結方法(finalizer)一般是不可預測的,也是很危險的,通常狀況下是沒必要要的.使用終結方法會致使行爲不穩定,下降性能,以及可移植性問題.

終結方法的缺點在於不能保證會被及時地執行.從一個對象變得不可能到達開始,到它的終結方法被執行,所花費的這段時間是任意長的.

  • 不該該依賴終結方法來更新重要的持久狀態.
  • 使用終結方法有一個很是嚴重的性能損失

說真的,我沒看懂,剩下的部分

8.覆蓋equals時請遵照通用約定

不須要覆蓋:

  • 類的每一個實例本質上都是惟一的
  • 不關心類是否提供了"邏輯相等"的測試功能
  • 超類已經覆蓋了equlas,從超類繼承過來的行爲對於類也是合適的

應該覆蓋:

  • 類是私有的或是包級私有的,能夠肯定它的equlas方法永遠不會被調用.能夠拋出異常來防止被調用
  • 類具備本身特有的"邏輯相等"概念(不一樣於對象等同的概念)

通用約定:

  • 自反性
  • 對稱性
  • 傳遞性
  • 一致性
  • 非控性

高質量equals的訣竅:

  1. 使用==操做符檢查"參數是否爲這個對象的引用"
  2. 使用instanceof操做複檢查"參數是否爲正確的類型"
  3. 把參數轉換成正確的類型
  4. 對於該類的每一個關鍵域,檢查參數中的域是否與該對象中對應的域相匹配
  5. 當你編寫了equals方法以後,應該問本身三個問題;它是不是對稱的,傳遞的,一致的?
  6. 覆蓋equals時總要覆蓋hashCode
  7. 不要企圖讓equlas過於智能
  8. 不要將equals聲明中的Object對象替換爲其餘的類型

9.覆蓋equals時總要覆蓋hashCode

在每一個覆蓋了equals方法的類中,也必須覆蓋hashCode方法.若是不這樣作的話,就會違反Object.hashCode的通用約定,從而致使該類沒法結合全部基於散列的集合一塊兒正常運做,這樣的集合包括HashMap,HashSet和Hanshtable.

下面是約定的內容,摘自Object規範[JavaSE6]

  • 在應用程序的執行期間,只要對象equlas方法的比較操做所用到的信息沒有被修改,那麼對這同一個對象調用屢次,hashCode方法都必須始終如一地返回同一個整數.在同一個應用程序的屢次執行過程當中,每次執行所返回的整數能夠不一致.
  • 若是兩個對象根據equlas(Object)方法比較是相等的,那麼調用這兩個對象中任意一個對象的hashCode方法必須產生一樣的整數結果.
  • 若是兩個對象根據equlas(Object)方法比較是不相等的,那麼調用這兩個對象中任意一個對象的hashCode方法,則不必定要產生不一樣的整數結果.可是程序員應該知道,給不相等的對象產生大相徑庭的整數結果,有可能提升散列列表的性能.

爲不相等的對象產生不相等的散列碼.

簡單解決方法:

  1. 把某個非零的常數值,好比說17,保存在一個名爲result的int類型的變量中
  2. 對於對象中每一個關鍵域f(指equlas方法中涉及的每一個域),完成一下步驟:

a.爲該域計算int類型的散列碼

i.若是該域是boolean類型,則計算(f ? 1 : 0)

ii:若是該域是byte,char,short或者int類型,則計算(int)f

iii:若是該域是long類型,則計算(int)( f ^ (f  >>> 32) )

iv:若是該域是float類型,則計算Float.floatToIntBits(f)

v:若是該域是double類型,則計算Double.doubleToLongBits(f),而後按照步驟2.a.iii,爲獲得的long類型計算散列值

vi.若是該域是一個對象引用,而且該類的equlas方法通用遞歸調用equlas的方法來比較這個域,則一樣爲這個域遞歸地調用hashCode.若是須要更復炸的比較,則爲這個域計算一個範式,而後針對這個範式調用hashCode.若是這個域的值爲null,則返回0(或者其餘某個整數,但一般是0).

vii.若是該域是一個數組,則要把每個元素當作單獨的域來處理.也就是說,遞歸地應用上述規則,對每一個重要的元素計算一個散列碼,而後根據步驟2.b中的作法把這些散列值組合起來.若是數組域中的每一個元素都很重要,能夠利用發行版本1.5中增長的其中一個Arrays.hashCode方法.

b.按照下面的公式,把步驟2.a中計算獲得的散列碼c合併到result中:

result = 31 * result + c;

3.返回result

4.寫完hashCode方法以後,問問本身"相等的實例是否具備相等的散列碼".要編寫單元測試來驗證你的推斷.若是相等的實例有着不相等的散列碼,則要找出緣由,並修改錯誤.

在散列的計算過程當中,能夠把冗餘域排除在外.換句話說,若是一個域的值能夠根據參與計算的其餘域值計算出來,則能夠把這樣的域排除在外.

上述步驟1中用到一個非零的初始值,所以步驟2.a中計算的散列值爲0的那些初始域,會影響到散列值.若是步驟1中的初始值爲0,則整個散列值將不受這些初始域的影響,由於這些初始域會增長衝突的可能性

步驟2.b中的乘法部分使得散列值依賴於域的順序.若是一個類包含多個類似的域,這樣的乘法運算就會產生一個更好的散列函數.例如,若是String散列函數省略了這個乘法部分,那麼只是字母順序不一樣的全部字符串都會有相同的散列碼.

之因此選擇31,是由於它是一個奇數數.若是乘數是偶數,而且乘法溢出的話,信息就會丟失,由於與2相乘等價於移位運算.使用素數的好處並不明顯,但習慣上都是用素數來計算散列的結果.31有個很好的特性,即用移位和減法來代替乘法,能夠獲得更好的性能: 31 - i == ( i << 5 ) - i.現代的VM能夠自動完成這種優化. 

關於31能夠看看這篇文章:https://segmentfault.com/a/1190000010799123

不要試圖從散列碼計算中排除掉一個對象的關鍵部分來提升性能.

10.始終要覆蓋toString

雖然java.lang.Object提供了toString方法的一個實現,但它返回的字符串一般並非類的用戶所指望看到的.

它包含類的名稱,以及一個"@"符號,接着是散列碼的無符號十六進制表示法,例如"PhoneNumber@163b91".

toString的通用約定指出,被返回的字符串應該是一個"簡潔的,但信息豐富,而且易於閱讀的表達形式",

建議全部的子類都覆蓋這個方法.

當對象被傳遞給println,print,字符串聯操做符(+)以及assert或者被控制器打印出來時,toString方法會被自動調用.

toString方法應該返回對象中包含的全部值得關注的信息.

在實現toString的時候,必需要作出一個很重要的決定:是否在文檔中指定返回值的格式.

指定格式的好處是,它能夠被用一種標準的,明確的,適合人閱讀的對象表示法.若是你指定了格式,最好再提供一個相匹配的靜態工廠或者構造器,以便程序員能夠很容易地再對象和它的字符串表示法之間來回轉換.

指定格式的不足之處:若是這個類已經被普遍使用,一旦指定格式,就必須始終如一地堅持這種格式.若是不指定格式,就能夠保留靈活性,便於在未來的發行版本中增長信息,或則改進格式

 

以上多摘自書中,少數添加本身理解,第1-10條到此.

我會將這些應用到之後的編程中,後期回來填坑補充本身的理解.

第11-20條

相關文章
相關標籤/搜索