每個類都可以提供一個公有的靜態工廠方法(static factory method),這就是一個返回類的實例的靜態方法:
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }上面的方法將boolean基本類型值轉換成一個Boolean對象的引用,即Boolean類型的一個實例。
只公有的靜態方法而不是構造器來提供類的一個實例有如下幾點優勢:
靜態工廠方法可以做到見名知意:調用構造器BigInteger( int, int, Random )可能返回一個素數,但是在沒有注釋的情況下很難得知這個構造器的作用,不過使用BigInteger.PRobablePrime的驚濤方法就顯得更為清楚。
不必每次調用靜態工廠方法時都創建一個新的實例:這可以增加復用的幾率,減少內存的開銷。對于大型對象來說可以大大提高性能。這種不必每次都創建一個新的實例的類叫做實例受控的類(instance-controlled),這種類型確保該類是一個單例類(Singleton)或不可直接實例化的類;另外,單例類還保證了a==b 與 a.equals(b)為true 互為充要條件,這樣的話,可以使用==操作符代替equals方法,可以提升效率。
靜態工廠方法可以返回原來類型的任意子類型,靈活性大大提高。下面這個栗子中,Provider負責提供Service的實現實例,而所有的Provider都被保存于靜態的Map
public interface Service {//Service中的具體方法}public interface Provider { Service newService();}//負責保存Service的實例,獲取、注冊Service實例public class Services { private Services() { } private static final Map<String,Provider> providers = new ConcurrentHashMap<>(); public static final String DEFAULT_PROVIDER_NAME = "<def>"; //注冊一個默認的Provider public static void registerDefaulrProvider(Provider p){ registerProvider(DEFAULT_PROVIDER_NAME, p );} public static void registerProvier(String name, Provider p) { providers.put(name,p);} //客戶端調用的APIpublic static Service newInstance() { return newInstance(DEFAULT_PROVEIDER_NAME);}public static service newInstance(String name) {Provider p = providers.get(name);if(p == null){throw new IllegalArgumentExecption("No provider registerd with name: " + name);}return p.newService();}}靜態工廠方法使得實例化變得更加簡潔:首先,使用常規的構造器形式實例化Map<String, List<String>> m = new HashMap<String,List<String>>;
;而如果HashMap提供了靜態方法 public static <k, v> HashMap<K,V> newInstance() { return new HashMap<K , V>();}
那么在調用端就會變得簡單:
Map<String, List<String>> m = HashMap.newInstance();一句話: 重疊構造器可行。但是當有許多參數的時候??蛻舳舜a會很難編寫,并且仍較難以閱讀。若讀者相紙到那些值是啥意思,必須仔細了解這些參數的意義。這很容易導致錯誤:若不小心導致了錯誤,編譯器也不會報錯,但在運行時會出現錯誤行為。
另一種方式是使用javaBeans的方式。這種方式可讀性強,但JavaBeans存在一個先天的不足,那就是初始化一個對象的過程并不是一步完成,而要分成多次,若漏掉某個參數的初始化,那么查錯會變得困難。
第三中方式兼顧了第一章方式的安全性和第二種方式的可讀性。即Builder模式。不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器,得到一個builder對象。然后客戶端在builder對象上調用類似于setter的方法,來設置每個相關的可選參數。最后,客戶端調用午餐的build方法來生成不可變的對象。這個builder是它構建的類的靜態成員類:
public 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 carbohydrate = 0; private int sodium = 0; //Builder構造方法 public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servidngs = servings; } public Builder calories(int val) { calories = val; } public Builder fat(int val) { fat = val; return this; } public Builder carbohvdrate(int val) { carbohvdrate = 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; }}那么客戶端代碼就可以寫成:
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100).fat(0).sodium(35).build();Singleton通常被用來代表那些本質上唯一的系統組件:
在JDK1.5之前,一般定義一個私有的構造器并導出公有的靜態成員:
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } ...}這種方式有一個問題:享有特權的客戶端可以借助accessibleObject.setAccessible方法,通過反射調用私有的構造器。
在JDK1.5 之前 還有一種方式實現單例類,即靜態工廠方法:
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public static Elvis getInstance { return INSTANCE; } ...}JDK1.5版本以后,可以使用單元素的枚舉類型創建單例類:
public enum Elvis { INSTANCE;}...這種方式目前是實現單例類的最佳方式。
略…
若在一個循環中每次都創建一個String實例,是完全不必要的,實際上只需要這樣:
String s = "stringette";這保證了在同一臺虛擬機內,只要包含相同的字符串字面常量,該對象就會被重用。
自動裝箱和拆箱也暗含著創建不必要的對象:
public static void main(String[] args) { Long sum = 0L; for(int i = 0;i<Integer.MAX_VALUE; ++i) { sum += i; } System.out.println(sum);}該循環每執行一遍,都會實例化一個Long的實例,這樣相當耗費內存,指引自動裝箱的緣故,只需要將Long 改為long。
有些對象的初始化很耗費資源,如Calendar類,對于這種類,根據實際的應用場合,只需要實例化一次,所以可以把該類的實例化放在靜態初始化塊中。
首先舉個反例:
public class Person { private final Date birthDate; //錯誤的寫法 public boolean isBatyBoomer() { Canlendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.set(1946,Calendar.JANUARY,1,0,0,0); Date boomStart = gmtCal.getTime(); gmtCal.set(1965,Calendar.JANUARY,1,0,0,0); Date boomEnd = gmtCal.getTime(); return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0; }}每調用一次方法,都新建一個Calendar,一個TimeZone,兩個Date對象,這是不必要的。
在靜態初始化塊中初始化Calendar,避免其重復創建:
public class Person { private final Date birthDate; private static final Date BOOM_START; private static final Date BOOM_END; static { Calendar gmtCal = Calendar.getInstance(TimeZone.getImeZone("GMT")); gmtCal.set(1946, Calendar.JANUARY,1,0,0,0); BOOM_START = gmtCal.getTime(); gmtCal.set(1965,Calendar.JANUARY,1,0,0,0); BOOM_END = gmtCal.getTime(); } public boolean isBabyBoomer() { return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0; }}上述代碼中存在內存泄漏的風險,當元素從數組中彈棧的時候,我們只是單純地將數組的范圍減少1,但是并沒有把該范圍之外的數組中引用的對象釋放掉,這就造成了過期的引用無法被釋放的問題,從而造成了內存泄漏。
解決辦法是,將那些過期的引用置空:
public Object pop() { if(size == 0) { throw new EmptyStackEception(); } Object result = elements[--size]; elements[size] = null; return result;}這種做法的另一個好處是,當數組中過期的引用被錯誤的引用時,會拋出NullPointerExecption異常。
內存泄漏還有一種常見的來源,就是緩存——一旦把對象引用放到緩存中,它就很容易被遺忘掉,從而使得它不再有用之后很長一段時間仍然留在緩存中。
推薦幾篇有關內存泄漏的文章,值得一讀~: 1、內存泄露從入門到精通三部曲之基礎知識篇 2、內存泄露從入門到精通三部曲之常見原因與用戶實踐 3、內存泄露從入門到精通三部曲之排查方法篇
總結起來就一句:盡量避免使用終結方法。
原因是:終結方法不能保證會被及時地執行——從一個對象變得不可到達(即沒有任何引用再只想這個對象)到它的終結方法被執行(finalize()執行),這段時間是任意長的,不可控的;第二,不使用finialize()作為回收資源的方式,是因為不同的JVM回收算法實現起來大相徑庭,時間不一樣;第三,finialize()方法根本不保證會被執行。
新聞熱點
疑難解答