-序言: 1.Java的正式發行名稱 工程版本號 JDK 1.1.x / JRE 1.1.x 1.1 Java2 Platform,Standard Edition,v 1.2 1.2 Java2 Platform,Standard Edition,v 1.3 1.2 Java2 Platform,Standard Edition,v 1.4 1.2 Java2 Platform,Standard Edition,v 5.0 1.5 Java Platform,Standard Edition,5 1.6 2.術語: 1.Java語言支持4種類型: 接口(interface) 類(class) 數組(array) 基本類型(primitive),前3中類型通常被稱為引用類型,類實例和數組是對象,而基本類型的值則不是對象。 2.類的成員由其域(field),方法(method),成員類(member class)和成員接口(member interface)組成. 3.方法的簽名(signature)由它的名稱和所有的參數類型組成,簽名不包括它的返回類型。 4.繼承(inheritance),作為子類化(subclassing)的同義詞 5.不再使用接口繼承這種說法,而是簡單的說一個類實現(implement)了另一個接口或者說一個接口擴展(extend)了另一個接口. 6.描述沒有指定訪問級別的情況下所使用的訪問級別,使用了包級私有(package-private),而不是如JLS(Java Language Specifications),6.6.1所使用的技術性術語"缺省訪問(default access)"級別. 7.導出的API(exported API)或者簡單的說API,是指類,接口,構造器,成員,和序列化形式,程序員可以通過他們可以訪問類,接口夠或者包。使用API編寫程序的程序員被稱為該API的用戶(user),在類的實現中使用了API的類被稱為該API的客戶(client). 8.類,接口,構造器,成員以及序列化的形式被統稱為API元素(API element),導出的API由所有可在該API的包之外訪問的API元素組成。任何客戶端都可以使用這些API元素,而API的創建者負責支持這些API元素。Javadoc工具類在默認情況下也正是為這些元素生成文檔,這絕非偶然。不嚴格的講,一個類的導出的API是由每個公有(public)類或者接口中所有公有的或者受保護的(protected)成員和構造器組成. 第一條:考慮用靜態工廠方法代替構造器 1.對于類來說,為了讓客戶端獲取其自身的一個實例,最常用的辦法就是提供一個公有的構造器。還有一種方法,也應該在每個程序員的工具箱中占有一席之地。類可以提供一個公有的靜態工廠方法(static factory method),其時一個返回類實例的靜態方法。如: public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } 注:靜態工廠方法與設計模式的工廠方法模式不同。本條目所指的靜態工廠方法并不直接對應設計模式的工廠方法。 類可以通過靜態工廠方法提供他的客戶端,而不是通過提供構造器 2.優勢: 1.靜態工廠方法與構造器不同的第一大優勢在于:他們有名稱。如果構造器的參數本身沒有確切的描述正被返回的對象,那么具有適當名稱的靜態工廠方法會更容易使用,產生的客戶端代碼也更易讀。 如:BigInteger(int,int,Random),返回的可能是素數,如果用BigInteger.probablePrime的靜態工廠方法來表示,顯示更清楚。1.4的發行版本中最終增加了這個方法。 2.一個類只能有一個帶有指定簽名的構造器,編程人員通常避開這一限制是提供兩個構造器,而它們的參數列表只是在參數列表順序上有所不同。實際上這不好。對于這樣的API,client永遠也記不住該調用哪個構造器,結果通常會調用錯誤的構造器。并且讀到這些構造器的代碼時,如果沒有參考文檔,也不知所云。所以由于靜態工廠方法有名稱,所以他們不受上述限制。當一個類需要多個帶有相同簽名的構造器(只是參數列表順序不同)時,就用靜態工廠方法代替構造器并且慎重選擇名稱已突出他們之間的區別。 3.靜態工廠方法與構造器不同的第二大優勢在于,不必再每次調用它們的時候都創建一個新對象。 這使得不可變類可以使用預先構建好的實例,或者將構建好的實例緩存起來,進行重復利用,從而避免創建不必要的重復對象。Boolean.valueOf(boolean)說明了這項技術,它從來不創建對象。 這類似于Flyweight模式。如果程序經常請求創建相同的對象,并且創建對象的代價很高,則這項技術可以極大的提高性能。 靜態工廠方法能夠為重復的調用返回相同對象,這有助于類總能嚴格控制在某個時刻那些實例應該存在。這種類被稱為實例受控的類。instance-controlled.編寫實例受控的類有幾個原因: 1.使得類可以確保它是一個單例或者是不可實例化的 2.使得不可變的類確保不會存在兩個相等的實例,即當且僅當a == b的時候才有a.equals(b)為true.如果類保證了這一點,其客戶端就可以利用==操作符來代替equals(Object)方法,這樣可以提高性能。枚舉類型保證了這一點。 4.靜態工廠方法與構造器不同的第三大優勢在于,它們可以返回原返回類型的任何子類型對象: 這樣我們在選擇返回對象的類時就有了更大的靈活性. 這種靈活性的一種應用是,API可以返回對象,同時又不會使對象的類變成公有的,以這種方式因此實現類會使API變的非常簡潔.該項技術適用于基于接口的框架,interface-based-framework. 接口為靜態工廠方法提供了自然返回類型。因接口中不能有靜態方法,按照慣例,接口Type的靜態工廠方法被放在一個名為Types的不可實例化的類中。 例子1: Java Collections Framework的集合接口有32個便利實現,分別提供了不可修改的集合,同步集合等。幾乎所有這些實現都通過靜態工廠方法在一個不可實例化的類java.utils.Collections中導出。所有返回對象都是非公有的。 如: public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) { return new UnmodifiableCollection<>(c); } static class UnmodifiableCollection<E> implements Collection<E>, Serializable UnmodifiableCollection定義在Collections中,是非public。 現在Collections Framework Api被導出32個獨立公有類的那種實現方式要小的多,每種便利實現都對應一個類。這不僅僅是指API數量上的減少,也是概念意義上的減少,用戶知道被返回的對象是由相關的接口精確指定的,所以它們不需要閱讀相關的類文檔。使用這種靜態工廠方法時,甚至要求客戶端通過接口來引用被返回的對象,而不是通過它的實現類來引用被返回的對象,這是一種良好的習慣。 公有的靜態工廠方法所返回的對象的類不僅可以是非公有的,而且該類還可以隨著每次調用而發生變化,這取決于靜態工廠方法的參數值。只要是已生命的返回類型的子類型,都是允許的。 為了維護軟件的可維護性和性能,返回的對象的類也可能隨著發行版本的不同而不同。 如:java.util.EnumSet,沒有公用構造器,只有靜態工廠方法。他們返回兩種實現類之一,具體取決于底層枚舉數組的大小;如果它的元素有64個或者更少,就像大多數枚舉類型一樣,靜態工廠方法返回一個RegularEnumSet實例,用單個long支持;如果枚舉類型有65個或者更多,工廠就返回JumboEnumSet實例,用long數組進行支持。 如: public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); } 這兩個實現類的存在對于客戶端來說是不可見的。如果RegularEnumSet不能再給小的枚舉類型提供性能優勢,就可能從未來的發行版本中將它刪除,不會造成不良的影響。同樣的了,如果證明對性能要好處,也可能在未來的發行版本中添加第三甚至第四個Enum實現。客戶端永遠也不知道也不關心他們從工廠方法得到的對象的類,他們只關心他是EnumSet的某個子類即可。 class RegularEnumSet<E extends Enum<E>> extends EnumSet<E>,非public. class JumboEnumSet<E extends Enum<E>> extends EnumSet<E>,非public. 靜態工廠方法返回的對象所屬的類,在編寫包含該靜態工廠方法的類時可以不必存在。這種靈活的靜態工廠方法構成了服務提供者框架,Service Provider Framework的基礎。如JDBC API。服務提供者框架是指這樣一個系統,多個服務提供者實現一個服務,系統為服務提供者的客戶端提供多個實現,把它們從多個實現中解耦出來。 服務器提供者框架中有三個重要的組件:服務接口(Service Interface),這是提供者實現的,提供者注冊API,provider registration API,這是系統用來注冊實現,讓客戶端訪問它們的;服務訪問API,Service Access API,是客戶端用來獲取服務實例的。服務訪問API一般允許但是不要求客戶端指定某種選擇提供者的條件.如果沒有這樣的規定,API就會返回默認實現的 一個實例。服務訪問API是靈活的靜態工廠,它構成了服務提供者框架的基礎. 服務提供者框架的第四個組件是可選的:服務提供者接口,Service Provider Interface,這些提供者負責創建器服務實現的實例。如果沒有服務提供者接口,實現就按照類名來注冊,并通過反射方式進行實例化。對于JDBC來說,Connection就是它的服務接口,DriverManager.registerDriver就是提供者注冊API,DriverManager.getConnection是服務訪問API,Driver就是服務提供者接口。 服務提供者框架模式有著無數種變體.,如服務訪問API可以利用適配器模式,返回被提供者需要的更豐富的服務接口: 5.靜態工廠方法的第四大優勢在于,在創建參數化類型實例的時候,他們使代碼變得更加簡潔。 如在使用參數化的類的構造器時,即時類型參數很明顯,也必須指明。如:通常要求你接連兩次提供類型參數. Map<String,List<String>> m = new HashMap<String,List<String>> 隨著類型參數越來越長,越來越復雜,這一冗長的說明也很快變的痛苦起來。如果有靜態工廠方法,編譯器就可以替你找到類型參數。這被稱為類型推倒,type inference.如假設HashMap提供了這個工廠工廠: public static <K,V> HashMap<K,V> newInstance() { return new HashMap<K,V>(); } 你就可以簡潔的代碼替換上面繁瑣的說明: Map<String,List<String>> m = HashMap.newInstance(); 目前1.6版本的標準集合還沒有提供工廠方法,但是可以把這些方法放在你自己的工具類中。更重要的是,可以把這樣的靜態工廠放在你自己的參數化的類中。 6.靜態工廠方法的主要缺點在于,類如果不含公有或者受保護的構造器,就不能被子類化。 對于公有的靜態工廠所返回的非公有類,也同樣如此。如,你想講Collections Framework中的任何方便的實現類子類化,是不可能的。 但是這樣有時候也有好處,即它鼓勵程序員使用復合compostion,而不是繼承。 7.靜態工廠方法的第二個缺點在于,他們與其他的靜態方法實際上沒有任何區別。在API文檔中,他們沒有像構造器那樣在API文檔中明確標識出來,因此對于提供了靜態工廠方法而不是構造器的類來說,要想查明如何實例化一個類,這是非常困難的。javadoc工具總有一天會注意到靜態工廠方法。同時,你通過在類或者接口注釋中關注靜態工廠,并遵守標準的命名習慣,也可以彌補這一劣勢。下面是靜態工廠的一些慣用名稱: 1.valueOf-不太嚴格講,該方法返回的實例與它的參數具有相同的值。這樣的靜態工廠方法實際上是類型轉換方法。 2.of-valueOf的一種更為簡潔的替代,在EnumSet中使用并流行起來。 3.getInstance,返回的實例是通過方法的參數來描述的。但是不能夠說與參數具有同樣的值。對于單例Singleton來說,該方法沒有參數,并返回唯一的實例。 4.newInstance,向getInstance一樣,但newInstance能夠確保返回的每個實例都與所有其他實例不同。 5.getType,就像getInstance一樣,但是在工廠方法處于不同的類的時候使用(子類)。Type表示工廠方法所返回的對象類型。 6.newType,就像newInstance一樣,但是在工廠方法處于不同的類的時候使用(子類)。Type表示工廠方法所返回的對象類型。 總之,靜態工廠方法與公共構造器都各有好處,我們需要理解他們各自的長處。靜態工廠通常更加合適,因此切記第一反應就是提供公有的構造器,而不先考慮靜態工廠。
部分源碼:
package com.book.chap2.staticFactory;


/** *//**
*
*服務接口
*Service interface
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-12-24
*
*/

public interface Service


{

}

package com.book.chap2.staticFactory;


/** *//**
*
*服務提供者接口
*Service Provider interface
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-12-24
*
*/

public interface Provider


{
Service newService();
}

package com.book.chap2.staticFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/** *//**
*
*非實例化的類,用來注冊和訪問服務
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-12-24
*
*/

public class Services


{
private Services()

{
}
//服務提供者map
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
//默認的服務提供者名字
public static final String DEFAULT_PROVIDER_NAME = "<def>";

/** *//**
*
* 注冊默認的Provider
*
* @param p
*/
public static void registerDefaultProvider(Provider p)

{
registerProvider(DEFAULT_PROVIDER_NAME, p);
}

/** *//**
*
* 注冊Provider
*
* @param name
* @param p
*/
public static void registerProvider(String name,Provider p)

{
providers.put(name, p);
}
//服務訪問api Service access API
public static Service newInstance()

{
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name)

{
Provider p = providers.get(name);
if(p == null)

{
throw new IllegalArgumentException("No Provider registered with name:" + name);
}
return p.newService();
}
}
package com.book.chap2.staticFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


/** *//**
*
*參數化實例,使用靜態工廠,會使代碼簡單很多
*<p>使用類型推斷</p>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2012-12-26
*
*/

public class TypeInference


{

/** *//**
*
* 使用類型推斷的初始化HashMap方式
*
* @return
*/
public static <K,V> HashMap<K, V> newInstance()

{
return new HashMap<K, V>();
}
public static void main(String
args)

{
//普通方式初始化Map
Map<String, List<String>> commonMap = new HashMap<String, List<String>>();
//類型推斷的初始化方式
Map<String, List<String>> inferMap = TypeInference.newInstance();
}
}

posted on 2013-03-15 14:57
landon 閱讀(2342)
評論(2) 編輯 收藏 所屬分類:
Program 、
Book