a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 學院 > 開發設計 > 正文

Effective Java讀書筆記三:創建和銷毀對象(1-7)

2019-11-14 10:16:25
字體:
來源:轉載
供稿:網友

第1條:考慮用靜態工廠方法代替構造器

對于類而言,為了讓客服端獲得它的一個實例最常用的的一個方法就是提供一個公有的構造器。還有一種方法,類可以提供一個公有的靜態工廠方法(static factory method),它只是一個返回類實例的靜態方法。

通過靜態工廠方法構造對象的優勢:

靜態工廠方法與構造器不同的第一大優勢在于,它們有名稱,使客服端代碼更加容易被閱讀。 不必在每次調用的它們的時候都創建一個新的對象(這個完全取決于具體的實現)。 它們可以返回原返回類型的任何子類型的對象。 這種靈活性的一種應用:API可以返回對象,同時又不會使對象的類變成公有的。公有的靜態方法所返回的對象的類不僅可以是非公有的,而且該類還可以隨著每次調用而發生變化著取決于靜態工廠方法的參數值,只要是已聲明返回類型的子類型,都是允許的。在創建參數化類型(也就是泛型,jdk1.5新特性)實例的時候,它們是的代碼變得更加簡潔。/**普通創建****/ Map<String,List<String>> m=new HashMap<String,List<String>>; /**有了靜態方法過后***/ Map<String,List<String>> m=HashMap.newInstance(); //前提HashMap提供了這個靜態工廠方法 public static <k,v> HashMap<k,v> newInstance(){ return new HashMap<K,V>(); }

靜態工廠方法的主要缺點在于:

類如果不含有他的公有或者受保護的構造器,就不能被子類化(即被繼承)。它們與其他靜態方法實際上沒有任何區別。

常用的靜態工廠名稱:valueOf,of,getInstance,newInstance,getType,newType.

第2條:遇到多個構造參數時要考慮用構建器(Builder模式)

class NutritionFacts { PRivate final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { //對象的必選參數 private final int servingSize; private final int servings; //對象的可選參數的缺省值初始化 private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; //只用少數的必選參數作為構造器的函數參數 public Builder(int servingSize,int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } } //使用方式 public static void main(String[] args) { NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100) .sodium(35).carbohydrate(27).build(); System.out.println(cocaCola); }

對于Builder方式,可選參數的缺省值問題也將不再困擾著所有的使用者。這種方式還帶來了一個間接的好處是,不可變對象的初始化以及參數合法性的驗證等工作在構造函數中原子性的完成了。

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

1、將構造函數私有化,直接通過靜態公有的final域字段獲取單實例對象:

public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elivs() { ... } public void leaveTheBuilding() { ... } }

這樣的方式主要優勢在于簡潔高效,使用者很快就能判定當前類為單實例類,在調用時直接操作Elivs.INSTANCE即可,由于沒有函數的調用,因此效率也非常高效。然而事物是具有一定的雙面性的,這種設計方式在一個方向上走的過于極端了,因此他的缺點也會是非常明顯的。如果今后Elvis的使用代碼被遷移到多線程的應用環境下了,系統希望能夠做到每個線程使用同一個Elvis實例,不同線程之間則使用不同的對象實例。那么這種創建方式將無法實現該需求,因此需要修改接口以及接口的調用者代碼,這樣就帶來了更高的修改成本。

2、 通過公有域成員的方式返回單實例對象:

public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elivs() { ... } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { ... } }

這種方法很好的彌補了第一種方式的缺陷,如果今后需要適應多線程環境的對象創建邏輯,僅需要修改Elvis的getInstance()方法內部即可,對用調用者而言則是不變的,這樣便極大的縮小了影響的范圍。至于效率問題,現今的JVM針對該種函數都做了很好的內聯優化,因此不會產生因函數頻繁調用而帶來的開銷。

3、使用枚舉的方式(java SE5):

public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }

就目前而言,這種方法在功能上和公有域方式相近,但是他更加簡潔更加清晰,擴展性更強也更加安全。

第4條:通過私有構造器強化不可實例化的能力

對于有些工具類如java.lang.Math、java.util.Arrays等,其中只是包含了靜態方法和靜態域字段,因此對這樣的class實例化就顯得沒有任何意義了。然而在實際的使用中,如果不加任何特殊的處理,這樣的classes是可以像其他classes一樣被實例化的。這里介紹了一種方式,既將缺省構造函數設置為private,這樣類的外部將無法實例化該類,與此同時,在這個私有的構造函數的實現中直接拋出異常,從而也避免了類的內部方法調用該構造函數。

public class UtilityClass { //Suppress default constructor for noninstantiability. private UtilityClass() { throw new AssertionError(); } }

這樣定義之后,該類將不會再被外部實例化了,否則會產生編譯錯誤。然而這樣的定義帶來的最直接的負面影響是該類將不能再被子類化。

第5條:避免創建不必要的對象

一般來說,最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象。如果對象是不可變的,它就始終可以被重用。 反例:

String s = new String(“stringette”);

該語句每次被執行的時候都創建一個新的String實例,但是這些創建對象的動作全都是不必要的,傳遞給String構造器的參數(“stringette”)本身就是一個String實例,功能方面等同于構造器創建的對象。如果這種用法是在一個循環中,或者是在一個被頻繁調用的方法中,就會創建出很多不必要的String實例。 改進:

String s = “stringette”;

改進后,只用一個String實例,而不是每次執行的時候都創建一個新的實例,而且,它可以保證,對于所有在同一虛擬機中運行的代碼,只要它們包含相同的字符串字面常量,該對象就會被重用。

對于同時提供了靜態工廠方法和構造器的不可變類,通常可以使用靜態工廠方法而不是構造器,以避免創建不必要的對象。 例如,靜態工廠方法Boolean.valueOf(String)幾乎總是比構造器Boolean(String)好,構造器在每次被調用的時候都會創建一個新的對象,而靜態工廠方法則從來不要求這樣做,實際上也不會這樣做。

要優先使用基本類型而不是裝箱基本類型,要當心無意識的自動裝箱

public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum);}

這段程序算出的答案是正確的,但是比實際情況要更慢一些,只因為打錯了一個字符。變量sum被聲明成Long而不是long,這就意味著程序構造了大約2^31個多的Long實例。

Java共有9中基本類型,同別的語言有重要區別的是這9中類型所占存儲空間大小與機器硬件架構無關,這使得Java程序有很強的可移植性,如下圖:

這里寫圖片描述

不要錯誤地認為“創建對象的代價非常昂貴,我們應該要盡可能地避免創建對象,而不是不創建對象”,相反,由于小對象的構造器只做很少量的工作,所以,小對象的創建和回收動作是非常廉價的,特別是在現代的JVM實現上更是如此。通過創建附加的對象,提升程序的清晰性、簡潔性和功能性,這通常也是件好事。

反之,通過維護自己的對象池來創建對象并不是一種好的做法,除非池中的對象是非常重量級的。真正正確使用對象池的典型對象示例就是數據庫連接池。建立數據庫連接的代價是非常昂貴的,因此重用這些對象非常有意義。

另外,在1.5版本里,對基本類型的整形包裝類型使用時,要使用形如 Byte.valueOf來創建包裝類型,因為-128~127的數會緩存起來,所以我們要從緩沖池中取,Short、Integer、Long也是這樣。

第6條:消除過期的對象引用

盡管Java不像C/C++那樣需要手工管理內存資源,而是通過更為方便、更為智能的垃圾回收機制來幫助開發者清理過期的資源。即便如此,內存泄露問題仍然會發生在你的程序中,只是和C/C++相比,Java中內存泄露更加隱匿,更加難以發現,見如下代碼:

public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if (elements.length == size) elements = Arrays.copys(elements,2*size+1); } }

這段程序有一個“內存泄漏”問題,如果一個棧先是增長,然后再收縮,那么,從棧中彈出來的對象不會被當做垃圾回收,即使使用棧的程序不再引用這些對象,它們也不會被回收。這是因為,棧內部維護這對這些對象的過期使用(obsolete reference),過期引用指永遠也不會被解除的引用。 修復的方法很簡單:一旦對象引用已經過期,只需要清空這些引用即可。對于上述例子中的Stack類而言,只要一個單元彈出棧,指向它的引用就過期了,就可以將它清空。

修改方式如下:

public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; //手工將數組中的該對象置空 return result; }

Stock為什么會有內存泄漏問題呢? 問題在于,Stock類自己管理內存。存儲池中包含了elements數組(對象引用單元,而不是對象本身)的元素。數組活動區域的元素是已分配的,而數組其余部分的元素是自由的。但是垃圾回收器并不知道這一點,就需要手動清空這些數組元素。 一般而言,只要類是自己管理內存,就應該警惕內存泄漏問題。一旦元素被釋放掉,則該元素中包含的任何對象引用都應該被清空。

由于現有的Java垃圾收集器已經足夠只能和強大,因此沒有必要對所有不在需要的對象執行obj = null的顯示置空操作,這樣反而會給程序代碼的閱讀帶來不必要的麻煩,該條目只是推薦在以下3中情形下需要考慮資源手工處理問題:

類是自己管理內存,如例子中的Stack類。使用對象緩存機制時,需要考慮被從緩存中換出的對象,或是長期不會被訪問到的對象。事件監聽器和相關回調。用戶經常會在需要時顯示的注冊,然而卻經常會忘記在不用的時候注銷這些回調接口實現類。

第7條:避免使用終結方法

Java的語言規范中并沒有保證終結方法會被及時的執行,甚至都沒有保證一定會被執行。即便開發者在code中手工調用了System.gc和System.runFinalization這兩個方法,這僅僅是提高了finalizer被執行的幾率而已。還有一點需要注意的是,被重載的finalize()方法中如果拋出異常,其棧幀軌跡是不會被打印出來的。

《Effective Java中文版 第2版》PDF版下載: http://download.csdn.net/detail/xunzaosiyecao/9745699

作者:jiankunking 出處:http://blog.csdn.net/jiankunking


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 欧美一级艳片视频免费观看 | 日韩精品www| 亚洲精品无 | 国产亚洲精品久久久久动 | 国产精品99久久久久久久久 | 久久一本 | 亚洲精品视频在线观看免费视频 | 国产午夜精品一区二区三区嫩草 | 久久国产亚洲精品 | 日本久久网 | 岛国免费av | 国产一区二区三区视频在线观看 | 福利二区视频 | 精品久久久久久久久久久久久久 | 综合五月激情 | 99re在线视频 | 能看的黄色网址 | 欧美午夜影院 | 欧美一区视频 | 精品黑人一区二区三区久久 | 欧美一级片在线 | 国产精品久久免费视频在线 | 高清av一区 | 欧美日韩91 | 日韩精品视频在线观看网站 | 久久韩剧| 亚洲一级毛片 | 欧美国产日韩在线观看 | 日韩国产精品一区二区三区 | 久久久久久久爱 | 国产在线一二 | 午夜在线视频 | 欧美在线观看黄 | 日韩超碰| 一区二区三区免费看 | 久久99国产一区二区三区 | 色婷婷综合久久久久中文一区二 | 成人免费视频网站在线观看 | 久久久久久久久成人 | 中文字幕久久精品 | 天天操狠狠操 |