讀書,收獲,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度。
Talk is cheap.Show me the code
建議93:Java的泛型是類型擦除的★☆☆☆☆
Java的泛型在編譯期有效,在運(yùn)行期被刪除,也就是說所有的泛型參數(shù)類型在編譯后都會(huì)被清除掉。
//下面這種方法的重載,編輯器會(huì)報(bào)錯(cuò),提示方法沖突....
//'listMethod(List<String>)' clashes with 'listMethod(List<Integer>)'; both methods have same erasure
public void listMethod(List<String> stringList){
}
public void listMethod(List<Integer> intList) {
}
這就是Java泛型擦除引起的問題:在編譯后所有的泛型類型都會(huì)做相應(yīng)的轉(zhuǎn)化。轉(zhuǎn)換規(guī)則如下:
-
List<String>、List<Integer>、List<T>擦除后的類型為List。 -
List<String>[]擦除后的類型為List[]。 -
List<? extends E>、List<? super E>擦除后的類型為List<E>。 -
List<T extends Serializable&Cloneable>擦除后為List<Serializable>。
Java之所以如此處理,有兩個(gè)原因:
- 避免
JVM的大換血。C++的泛型生命期延續(xù)到了運(yùn)行期,而Java是在編譯器擦除掉的,如果JVM也把泛型類型延續(xù)到運(yùn)行期,那么JVM就需要進(jìn)行大量的重構(gòu)工作了。 - 版本兼容。在編譯期擦除可以更好地支持原生類型(Raw Type),在Java 1.5或1.6平臺(tái)上,即使聲明一個(gè)
List這樣的原生類型也是可以正常編譯通過的,只是會(huì)產(chǎn)生警告信息而已。
我們就可以解釋類似如下的問題了:
-
泛型的
class對(duì)象是相同的public static void main(String[] args) { List<String> ls = new ArrayList<String>(); List<Integer> li = new ArrayList<Integer>(); System.out.println(ls.getClass() == li.getClass()); //運(yùn)行結(jié)果:true }每個(gè)類都有一個(gè)class屬性,泛型化不會(huì)改變
class屬性的返回值 -
泛型數(shù)組初始化時(shí)不能聲明泛型類型
//如下代碼編譯時(shí)通不過: List<String>[] list = new List<String>[];可以聲明一個(gè)帶有泛型參數(shù)的數(shù)組,但是不能初始化該數(shù)組,因?yàn)閳?zhí)行了類型擦除操作,
List<Object>[]與List<String>[]就是同一回事了,編譯器拒絕如此聲明。 -
instanceof不允許存在泛型參數(shù)//以下代碼不能通過編譯,原因一樣,泛型類型被擦除了: List<String> list = new ArrayList<String>(); System.out.println(list instanceof List<String>)
建議94:不能初始化泛型參數(shù)和數(shù)組★☆☆☆☆
示例如下:
//這段代碼是編譯通不過的,因?yàn)榫幾g器在編譯時(shí)需要獲得T類型,但泛型在編譯期類型已經(jīng)被擦除了
//所以new T()和new T[5]都會(huì)報(bào)錯(cuò)
public class Client {
private T t = new T();
private T[] tArray = new T[5];
private List<T> list = new ArrayList<T>();
}
在某些情況下,我們確實(shí)需要泛型數(shù)組,那該如何處理呢?代碼如下:
public class Client<T> {
//不在初始化,由構(gòu)造函數(shù)初始化
private T t ;
private T[] tArray;
private List<T> list = new ArrayList<T>();
public Client() {
try {
Class<?> tType = Class.forName("");
t = (T) tType.newInstance();
tArray = (T[]) Array.newInstance(tType,5);
}catch (Exception e){
e.printStackTrace();
}
}
類的成員變量是在類初始化前初始化的,所以要求在初始化前它必須具有明確的類型,否則就只能聲明,不能初始化。
建議95:強(qiáng)制聲明泛型的實(shí)際類型★☆☆☆☆
示例:
class ArrayUtils{
//把一個(gè)變長參數(shù)轉(zhuǎn)變?yōu)榱斜? public static <T> List<T> asList(T...t){
List<T> list = new ArrayList<T>();
Collections.addAll(list, t);
return list;
}
}
public class Client {
public static void main(String[] args) {
//強(qiáng)制聲明泛型類型
//asList方法要求的是一個(gè)泛型參數(shù),在輸入前定義這是一個(gè)Integer類型的參數(shù),當(dāng)然,輸出也是Integer類型的集合了
List<Integer> list = ArrayUtils.<Integer>asList();
}
}
注意:無法從代碼中推斷出泛型類型的情況下,即可強(qiáng)制聲明泛型類型。
建議96:不同的場(chǎng)景使用不同的泛型通配符★★☆☆☆
Java泛型支持通配符(Wildcard),可以單獨(dú)使用一個(gè)“?”表示任意類,也可以使用extends關(guān)鍵字表示某一個(gè)類(接口)的子類型,還可以使用super關(guān)鍵字表示某一個(gè)類(接口)的父類型,但問題是什么時(shí)候該用extends,什么時(shí)候該用super呢?
-
泛型結(jié)構(gòu)只參與“讀”操作則限定上界(使用
extends關(guān)鍵字)public static <E> void read(List<? extends E> list){ for(E e:list){ System.out.println(e.getClass()); //業(yè)務(wù)邏輯處理 } } -
泛型結(jié)構(gòu)只參與“寫”操作則限定下界(使用
super關(guān)鍵字)public static void write(List<? super Number> list) { list.add(123); list.add(3.14); }
對(duì)于是要限定上界還是限定下界,JDK的Collections.copy方法是一個(gè)非常好的例子,它實(shí)現(xiàn)了把源列表中的所有元素拷貝到目標(biāo)列表中對(duì)應(yīng)的索引位置上,代碼如下:
//源列表是用來提供數(shù)據(jù)的,所以src變量需要限定上界,帶有extends關(guān)鍵字。
//目標(biāo)列表是用來寫入數(shù)據(jù)的,所以dest變量需要界定上界,帶有super關(guān)鍵字。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
如果一個(gè)泛型結(jié)構(gòu)即用作“讀”操作又用作“寫”操作,那該如何進(jìn)行限定呢?不限定,使用確定的泛型類型即可,如
List<E>。
建議97:警惕泛型是不能協(xié)變和逆變的★★☆☆☆
什么叫協(xié)變(covariance)和逆變(contravariance)?
在編程語言的類型框架中,協(xié)變和逆變是指寬類型和窄類型在某種情況下(如參數(shù)、泛型、返回值)替換或交換的特性,簡(jiǎn)單地說,協(xié)變是用一個(gè)窄類型替換寬類型,而逆變則是用寬類型覆蓋窄類型。
泛型既不支持協(xié)變,也不支持逆變:
public static void main(String[] args) {
//數(shù)組支持協(xié)變
Number[] n = new Integer[10];
//編譯不通過,泛型不支持協(xié)變
List<Number> ln = new ArrayList<Integer>();
//報(bào)錯(cuò):Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
}
-
可以使用通配符(Wildcard)模擬協(xié)變,代碼如下所示:
//Number的子類型都可以是泛型參數(shù)類型 List<? extends Number> ln = new ArrayList<Integer>(); -
可以使用super關(guān)鍵字來模擬逆變,代碼如下所示:
//Integer的父類型(包括Integer)都可以是泛型參數(shù)類型 List<? super Integer> li = new ArrayList<Number>();

注意:Java的泛型是不支持協(xié)變和逆變的,只是能夠?qū)崿F(xiàn)協(xié)變和逆變。
建議98:建議采用的順序是List<T>、List<?>、List<Object>★★☆☆☆
原因如下:
-
List<T>是確定的某一個(gè)類型List<T>表示的是List集合中的元素都為T類型,具體類型在運(yùn)行期決定;List<?>表示的是任意類型,與List<T>類似,而
List<Object>則表示List集合中的所有元素為Object類型,因?yàn)?code>Object是所有類的父類,所以List<Object>也可以容納所有的類類型,從這一字面意義上分析,
List<T>更符合習(xí)慣:編碼者知道它是某一個(gè)類型,只是在運(yùn)行期才確定而已。 -
List<T>可以進(jìn)行讀寫操作List<T>可以進(jìn)行如add、remove等操作,因?yàn)樗念愋褪枪潭ǖ?code>T類型,在編碼期不需要進(jìn)行任何的轉(zhuǎn)型操作。List<?>是只讀類型的,不能進(jìn)行增加、修改操作,因?yàn)榫幾g器不知道List中容納的是什么類型的元素,也就無法校驗(yàn)類型是否安全了,而且List<?>讀取出的元素都是Object類型的,需要主動(dòng)轉(zhuǎn)型,所以它經(jīng)常用于泛型方法的返回值。注意,List<?>雖然無法增加、修改元素,但是卻可以刪除元素,比如執(zhí)行remove、clear等方法,那是因?yàn)樗膭h除動(dòng)作與泛型類型無關(guān)。List<Object>也可以讀寫操作,但是它執(zhí)行寫入操作時(shí)需要向上轉(zhuǎn)型(Up cast),在讀取數(shù)據(jù)后需要向下轉(zhuǎn)型(Downcast),而此時(shí)已經(jīng)失去了泛型存在的意義了。
建議99:嚴(yán)格限定泛型類型采用多重界限★★★☆☆
比如在公交車費(fèi)優(yōu)惠系統(tǒng)中,對(duì)部分人員(如工資低于2500元的上班族并且是站立著的乘客)車費(fèi)打8折,該如何實(shí)現(xiàn)呢?
//職員
interface Staff{
//工資
public int getSalary();
}
//乘客
interface Passenger{
//是否是站立狀態(tài)
public boolean isStanding();
}
class Me implements Staff,Passenger{
public boolean isStanding(){
return true;
}
public int getSalary() {
return 2000;
}
}
//使用多重限定
public class Client {
//工資低于2500元的上斑族并且站立的乘客車票打8折
public static <T extends Staff & Passenger> void discount(T t){
if(t.getSalary()<2500 && t.isStanding()){
System.out.println("恭喜你!您的車票打八折!");
}
}
public static void main(String[] args) {
discount(new Me());
}
}
在Java的泛型中,可以使用“&”符號(hào)關(guān)聯(lián)多個(gè)上界并實(shí)現(xiàn)多個(gè)邊界限定,而且只有上界才有此限定,下界沒有多重限定的情況。
使用多重邊界可以很方便地解決問題,而且非常優(yōu)雅,建議在開發(fā)中考慮使用多重限定
建議100:數(shù)組的真實(shí)類型必須是泛型類型的子類型★★★☆☆
期望輸入的是一個(gè)泛型化的List,轉(zhuǎn)化為泛型數(shù)組,代碼如下:
public class Client<T> {
public static <T> T[] toArray(List<T> list) {
T[] t = (T[]) new Object[list.size()];
for (int i = 0, n = list.size(); i < n; i++) {
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
for (String str : toArray(list)) {//這一句報(bào)錯(cuò),Object數(shù)組不能向下轉(zhuǎn)型為String數(shù)組
System.out.println(str);
}
}
}
運(yùn)行異常如下:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at com.jyswm.demo.Client.main(Client.java:17)
因?yàn)榉盒褪穷愋筒脸模?code>toArray方法經(jīng)過編譯后與如下代碼相同:
public static Object[] toArray(List list){
//此處的強(qiáng)制類型沒必要存在,只是為了保持與源代碼對(duì)比
Object[] t = (Object[])new Object[list.size()];
for(int i=0,n=list.size();i<n;i++){
t[i] = list.get(i);
}
return t;
}
那該如何解決呢?
其實(shí)要想把一個(gè)Obejct數(shù)組轉(zhuǎn)換為String數(shù)組,只要Object數(shù)組的實(shí)際類型(Actual Type)也是String就可以了,例如:
public class Client<T> {
public static void main(String[] args) {
//objArray的實(shí)際類型和表面類型都是String數(shù)組
Object[] objArray = {"A","B"};
//拋出ClassCastException
String[] strArray = (String[])objArray;
String[] ss = {"A","B"};
//objs的真實(shí)類型是String數(shù)組,顯示類型為Object數(shù)組
Object[] objs = ss;
//順利轉(zhuǎn)換為String數(shù)組
String[] strs = (String[])objs;
}
}
如此,那就把泛型數(shù)組聲明為泛型類的子類型吧!代碼如下:
public class Client<T> {
public static <T> T[] toArray(List<T> list, Class<T> tClass) {
//聲明并初始化一個(gè)T類型的數(shù)組
//通過反射類Array聲明了一個(gè)T類型的數(shù)組,
//由于我們無法在運(yùn)行期獲得泛型類型的參數(shù),因此就需要調(diào)用者主動(dòng)傳入T參數(shù)類型
T[] t = (T[]) Array.newInstance(tClass, list.size());
for(int i=0,n=list.size();i<n;i++){
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
for (String str : toArray(list,String.class)) {
System.out.println(str);
}
}
}
注意:當(dāng)一個(gè)泛型類(特別是泛型集合)轉(zhuǎn)變?yōu)榉盒蛿?shù)組時(shí),泛型數(shù)組的真實(shí)類型不能是泛型類型的父類型(比如頂層類Object),只能是泛型類型的子類型(當(dāng)然包括自身類型),否則就會(huì)出現(xiàn)類型轉(zhuǎn)換異常。
建議101:注意Class類的特殊性★☆☆☆☆
Java語言是先把Java源文件編譯成后綴為class的字節(jié)碼文件,然后再通過ClassLoader機(jī)制把這些類文件加載到內(nèi)存中,最后生成實(shí)例執(zhí)行的,這是Java處理的基本機(jī)制,但是加載到內(nèi)存中的數(shù)據(jù)是如何描述一個(gè)類的呢?
Java使用一個(gè)元類(MetaClass)來描述加載到內(nèi)存中的類數(shù)據(jù),這就是Class類,它是一個(gè)描述類的類對(duì)象。
Class類特殊的地方:
無構(gòu)造函數(shù)。Java中的類一般都有構(gòu)造函數(shù),但是
Class類卻沒有構(gòu)造函數(shù),不能實(shí)例化,Class對(duì)象是在加載類時(shí)由 Java 虛擬機(jī)通過調(diào)用類加載器中的defineClass方法自動(dòng)構(gòu)造的。可以描述基本類型。雖然8個(gè)基本類型在
JVM中并不是一個(gè)對(duì)象,它們一般存在于棧內(nèi)存中,但是Class類仍然可以描述它們,例如可以使用int.class表示int類型的類對(duì)象。-
其對(duì)象都是單例模式。一個(gè)
Class的實(shí)例對(duì)象描述一個(gè)類,并且只描述一個(gè)類,反過來也成立,一個(gè)類只有一個(gè)Class實(shí)例對(duì)象,如下代碼返回的結(jié)果都為true:public class Client { public static void main(String[] args) throws Exception { //類的屬性class所引用的對(duì)象與實(shí)例對(duì)象的getClass返回值相同 String.class.equals(new String().getClass()); "ABC".getClass().equals(String.class); //class實(shí)例對(duì)象不區(qū)分泛型 ArrayList.class.equals(new ArrayList<String>().getClass()); } }
建議102:適時(shí)選擇getDeclared×××和get×××★☆☆☆☆
Java的Class類提供了很多的getDeclared×××方法和get×××方法,如下:
public static void main(String[] args) throws Exception {
//方法名稱
String methodName = "doStuff";
Method m1 = Foo.class.getDeclaredMethod(methodName);
Method m2 = Foo.class.getMethod(methodName);
}
getMethod方法獲得的是所有public訪問級(jí)別的方法,包括從父類繼承的方法,而getDeclaredMethod獲得是自身類的所有方法,包括公用(public)方法、私有(private)方法等,而且不受限于訪問權(quán)限。
其他的getDeclaredConstructors和getConstructors、getDeclaredFields和getFields等與此相似。
建議103:反射訪問屬性或方法時(shí)將Accessible設(shè)置為true★★☆☆☆
Java中通過反射執(zhí)行一個(gè)方法的過程如下:獲取一個(gè)方法對(duì)象,然后根據(jù)isAccessible返回值確定是否能夠執(zhí)行,如果返回值為false則需要調(diào)用setAccessible(true),最后再調(diào)用invoke執(zhí)行方法。如下:
Method method= ...;
//檢查是否可以訪問
if(!method.isAccessible()){
method.setAccessible(true);
}
//執(zhí)行方法
method.invoke(obj, args);
那為什么要這么寫呢?
首先,Accessible的屬性并不是訪問權(quán)限,而是指是否要更容易獲得,是否進(jìn)行安全檢查。
AccessibleObject類的源代碼如下:
//它提供了取消默認(rèn)訪問控制檢查的功能
public class AccessibleObject implements AnnotatedElement {
//定義反射的默認(rèn)操作權(quán)限suppressAccessChecks
static final private java.security.Permission ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
//是否重置了安全檢查,默認(rèn)為false
boolean override;
//構(gòu)造函數(shù)
protected AccessibleObject() {}
//是否可以快速獲取,默認(rèn)是不能
public boolean isAccessible() {
return override;
}
}
Accessible屬性只是用來判斷是否需要進(jìn)行安全檢查的,如果不需要?jiǎng)t直接執(zhí)行,這就可以大幅度的提升系統(tǒng)性能了(注意:取消了安全檢查,也可以運(yùn)行private方法、訪問private屬性的)。經(jīng)過測(cè)試,在大量的反射情況下,設(shè)置Accessible為true可以提高性能20倍左右。
建議104:使用forName動(dòng)態(tài)加載類文件★★☆☆☆
動(dòng)態(tài)加載(Dynamic Loading)是指在程序運(yùn)行時(shí)加載需要的類庫文件。
對(duì)Java程序來說,一般情況下,一個(gè)類文件在啟動(dòng)時(shí)或首次初始化時(shí)會(huì)被加載到內(nèi)存中,而反射則可以在運(yùn)行時(shí)再?zèng)Q定是否要加載一個(gè)類。
比如從Web上接收一個(gè)String參數(shù)作為類名,然后在JVM中加載并初始化,這就是動(dòng)態(tài)加載,此動(dòng)態(tài)加載通常是通過Class.forName(String)實(shí)現(xiàn)的,只是為什么要使用forName方法動(dòng)態(tài)加載一個(gè)類文件呢?
因?yàn)槲覀儾恢缹⒁傻膶?shí)例對(duì)象是什么類型(如果知道就不用動(dòng)態(tài)加載),而且方法和屬性都不可訪問。
動(dòng)態(tài)加載的意義在什么地方呢?示例如下:
class Utils{
//靜態(tài)代碼塊
static{
System.out.println("Do Something.....");
}
}
public class Client {
public static void main(String[] args) throws ClassNotFoundException {
//動(dòng)態(tài)加載
Class.forName("Utils");
//此時(shí)輸出了:Do Something.....
}
}
如上,并沒有對(duì)Utils做任何初始化,只是通過forName方法加載了Utils類,但是卻產(chǎn)生了一個(gè)Do Something的輸出,這就是因?yàn)?code>Utils類被加載后,JVM會(huì)自動(dòng)初始化其static變量和static代碼塊,這是類加載機(jī)制所決定的。
經(jīng)典的應(yīng)用:數(shù)據(jù)庫驅(qū)動(dòng)程序的加載片段
//加載驅(qū)動(dòng)
Class.forName("com.mysql..jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connection conn =DriverManager.getConnection(url);
Statement stmt =conn.createStatement();
Class.forName("com.mysql..jdbc.Driver");這一句的意義,示例如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//靜態(tài)代碼塊
static {
try {
//把自己注冊(cè)到DriverManager中
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
//異常處理
throw new RuntimeException("Can't register driver!");
}
}
//構(gòu)造函數(shù)
public Driver() throws SQLException {
}
}
程序邏輯如下:當(dāng)程序動(dòng)態(tài)加載該驅(qū)動(dòng)時(shí),也就是執(zhí)行到Class.forName("com.mysql.jdbc.Driver")時(shí),Driver類會(huì)被加載到內(nèi)存中,也就是把自己注冊(cè)到DriverManager中。
forName只是把一個(gè)類加載到內(nèi)存中,并不保證由此產(chǎn)生一個(gè)實(shí)例對(duì)象,也不會(huì)執(zhí)行任何方法,之所以會(huì)初始化static代碼,那是由類加載機(jī)制所決定的,而不是forName方法決定的。也就是說,如果沒有static屬性或static代碼塊,forName就只是加載類,沒有任何的執(zhí)行行為。
注意:
forName只是加載類,并不執(zhí)行任何代碼。
建議105:動(dòng)態(tài)加載不適合數(shù)組★☆☆☆☆
在Java中,數(shù)組是一個(gè)非常特殊的類,雖然它是一個(gè)類,但沒有定義類路徑。
示例:
public static void main(String[] args) throws ClassNotFoundException {
String [] strs = new String[10];
Class.forName("java.lang.String[]");
}
運(yùn)行異常,如下:
Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[]
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
因?yàn)榫幾g器編譯后為不同的數(shù)組類型生成不同的類,具體如下表所示:

所以動(dòng)態(tài)加載一個(gè)對(duì)象數(shù)組只要加載編譯后的數(shù)組對(duì)象就可以了,修改代碼如下:
//加載一個(gè)String數(shù)組
Class.forName("[Ljava.lang.String;");
//加載一個(gè)Long數(shù)組
Class.forName("[J");
但是這種操作沒有什么意義,因?yàn)樗荒苌梢粋€(gè)數(shù)組對(duì)象,只是把一個(gè)String類型的數(shù)組類和long類型的數(shù)組類加載到了內(nèi)存中,它沒有定義數(shù)組的長度,在Java中數(shù)組是定長的,沒有長度的數(shù)組是不允許存在的。
因?yàn)閿?shù)組的特殊性,所以Java專門定義了一個(gè)Array數(shù)組反射工具類來實(shí)現(xiàn)動(dòng)態(tài)探知數(shù)組的功能,如下:
// 動(dòng)態(tài)創(chuàng)建數(shù)組
String[] strs = (String[]) Array.newInstance(String.class, 8);
// 創(chuàng)建一個(gè)多維數(shù)組
int[][] ints = (int[][]) Array.newInstance(int.class, 2, 3);
注意:通過反射操作數(shù)組使用
Array類,不要采用通用的反射處理API。
建議106:動(dòng)態(tài)代理可以使代理模式更加靈活★★★☆☆
Java的反射框架提供了動(dòng)態(tài)代理(Dynamic Proxy)機(jī)制,允許在運(yùn)行期對(duì)目標(biāo)類生成代理,避免重復(fù)開發(fā)。
首先,簡(jiǎn)單的靜態(tài)代理實(shí)現(xiàn)示例如下:
/**
* 抽象角色-廚師
*/
interface Chef {
/**
* 提供餃子
*/
String dumplings();
/**
* 提供面條
*/
String noodles();
}
/**
* 具體角色-廚師老張
*/
class RealChef implements Chef {
@Override
public String dumplings() {
return "老張秘制酸湯水餃";
}
@Override
public String noodles() {
return "老張秘制蘭州牛肉面";
}
}
/**
* 代理角色(proxy)-幸福餐廳
*/
public class HappyRestaurant implements Chef {
/**
* 要代理哪個(gè)實(shí)現(xiàn)類(要讓哪個(gè)廚師做)
*/
private Chef chef = null;
/**
* 默認(rèn)被代理者(默認(rèn)的廚師老張)
*/
public HappyRestaurant() {
chef = new RealChef();
}
/**
* 通過構(gòu)造函數(shù)傳遞被代理者(客戶點(diǎn)名哪個(gè)廚師做)
*/
public HappyRestaurant(Chef _chef) {
chef = _chef;
}
@Override
public String dumplings() {
before();
return chef.dumplings();
}
@Override
public String noodles() {
before();
return chef.noodles();
}
/**
* 預(yù)處理
*/
private void before() {
// 先收銀
}
}
//調(diào)用
public static void main(String[] args) {
//來到幸福餐廳
HappyRestaurant happyRestaurant = new HappyRestaurant();
//點(diǎn)了一份餃子
String food = happyRestaurant.dumplings();
System.out.println(food);
//得到:老張秘制酸湯水餃
}
代理:"你去餐廳吃飯,并沒有見給你真正做飯的廚師老張,而是由餐廳的服務(wù)人員端到你面前的。"
改為動(dòng)態(tài)代理示例如下:
/**
* 抽象角色-廚師
*/
interface Chef {
/**
* 提供餃子
*/
String dumplings();
/**
* 提供面條
*/
String noodles();
}
/**
* 具體角色-廚師老張
*/
class RealChef implements Chef {
@Override
public String dumplings() {
return "老張秘制酸湯水餃";
}
@Override
public String noodles() {
return "老張秘制蘭州牛肉面";
}
}
/**
* 委托處理(不是具體的哪一家餐廳,而是美團(tuán)了)
*/
public class ChefHandler implements InvocationHandler {
/**
* 被代理的對(duì)象(廚師)
*/
private Chef chef;
public ChefHandler(Chef _chef) {
chef = _chef;
}
/**
* 委托處理方法(點(diǎn)外賣)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 預(yù)處理
System.out.println("預(yù)處理...");
//直接調(diào)用被代理的方法
Object obj = method.invoke(chef, args);
// 后處理
System.out.println("后處理...");
return obj;
}
}
注意看,這里沒有了餐廳這個(gè)角色,取而代之的是ChefHandler作為主要的邏輯委托處理,其中invoke方法是接口InvocationHandler定義必須實(shí)現(xiàn)的,它完成了對(duì)真實(shí)方法的調(diào)用。
InvocationHanlder接口:動(dòng)態(tài)代理是根據(jù)被代理的接口生成所有方法的,也就是說給定一個(gè)(或多個(gè))接口,動(dòng)態(tài)代理會(huì)宣稱“我已經(jīng)實(shí)現(xiàn)該接口下的所有方法了”
動(dòng)態(tài)代理的場(chǎng)景類,代碼如下:
public static void main(String[] args) {
//被代理類(想吃老張做的飯,確定目標(biāo))
Chef chef = new RealChef();
//代理實(shí)例的處理Handler(打開美團(tuán)app搜索老張)
InvocationHandler handler = new ChefHandler(chef);
//當(dāng)前加載器(美團(tuán)開始搜索并加載老張的信息)
ClassLoader classLoader = chef.getClass().getClassLoader();
//動(dòng)態(tài)代理(美團(tuán)已經(jīng)擁有了老張的所有能力,比如提供一份水餃等)
Chef proxy = (Chef) Proxy.newProxyInstance(classLoader, chef.getClass().getInterfaces(), handler);
//調(diào)用具體方法(點(diǎn)一份酸湯水餃)
String food = proxy.dumplings();
System.out.println(food);
//得到: 老張秘制酸湯水餃
}
此時(shí)就實(shí)現(xiàn)了不用顯式創(chuàng)建代理類即實(shí)現(xiàn)代理的功能。例如可以在被代理角色執(zhí)行前進(jìn)行權(quán)限判斷,或者執(zhí)行后進(jìn)行數(shù)據(jù)校驗(yàn)。
建議107:使用反射增加裝飾模式的普適性★★★☆☆
裝飾模式(Decorator Pattern)的定義是 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,裝飾模式相比于生成子類更為靈活,
使用Java的動(dòng)態(tài)代理也可以實(shí)現(xiàn)裝飾模式的效果,而且其靈活性、適應(yīng)性都會(huì)更強(qiáng)。
裝飾一只小老鼠,讓它更強(qiáng)大,示例如下:
interface Animal{
public void doStuff();
}
class Rat implements Animal{
@Override
public void doStuff() {
System.out.println("Jerry will play with Tom ......");
}
}
/**
* 使用裝飾模式,給老鼠增加一些能力,比如飛行,鉆地等能力
*/
//定義某種能力
interface Feature{
//加載特性
public void load();
}
//飛行能力
class FlyFeature implements Feature{
@Override
public void load() {
System.out.println("增加一對(duì)翅膀...");
}
}
//鉆地能力
class DigFeature implements Feature{
@Override
public void load() {
System.out.println("增加鉆地能力...");
}
}
/**
* 要把這兩種屬性賦予到老鼠身上,那需要一個(gè)包裝動(dòng)作類
*/
class DecorateAnimal implements Animal {
// 被包裝的動(dòng)物
private Animal animal;
// 使用哪一個(gè)包裝器
private Class<? extends Feature> clz;
public DecorateAnimal(Animal _animal, Class<? extends Feature> _clz) {
animal = _animal;
clz = _clz;
}
@Override
public void doStuff() {
InvocationHandler handler = new InvocationHandler() {
// 具體包裝行為
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
if (Modifier.isPublic(method.getModifiers())) {
obj = method.invoke(clz.newInstance(), args);
}
animal.doStuff();
return obj;
}
};
//當(dāng)前加載器
ClassLoader cl = getClass().getClassLoader();
//動(dòng)態(tài)代理,又handler決定如何包裝
Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
proxy.load();
}
}
/**
* 注意看doStuff方法,
* 一個(gè)裝飾類型必然是抽象構(gòu)建(Component)的子類型,它必須實(shí)現(xiàn)doStuff方法,此處的doStuff方法委托給了動(dòng)態(tài)代理執(zhí)行,
* 并且在動(dòng)態(tài)代理的控制器Handler中還設(shè)置了決定裝飾方式和行為的條件(即代碼中InvocationHandler匿名類中的if判斷語句),
* 當(dāng)然,此處也可以通過讀取持久化數(shù)據(jù)的方式進(jìn)行判斷,這樣就更加靈活了。
*/
/**
* 客戶端進(jìn)行調(diào)
*/
public static void main(String[] args) {
//定義Jerry這只老鼠
Animal jerry = new Rat();
//為Jerry增加飛行能力
jerry = new DecorateAnimal(jerry, FlyFeature.class);
//jerry增加挖掘能力
jerry = new DecorateAnimal(jerry, DigFeature.class);
//Jerry開始戲弄貓了
jerry.doStuff();
}
// 裝飾行為由動(dòng)態(tài)代理實(shí)現(xiàn),實(shí)現(xiàn)了對(duì)裝飾類和被裝飾類的完全解耦,提供了系統(tǒng)的擴(kuò)展性。
建議108:反射讓模板方法模式更強(qiáng)大★★★☆☆
模板方法模式(Template Method Pattern)的定義是:定義一個(gè)操作中的算法骨架,將一些步驟延遲到子類中,使子類不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。簡(jiǎn)單地說,就是父類定義抽象模板作為骨架,其中包括基本方法(是由子類實(shí)現(xiàn)的方法,并且在模板方法被調(diào)用)和模板方法(實(shí)現(xiàn)對(duì)基本方法的調(diào)度,完成固定的邏輯),它使用了簡(jiǎn)單的繼承和覆寫機(jī)制。
普通模板方法,示例如下:
public abstract class AbsPopulator {
// 模板方法
public final void dataInitialing() throws Exception {
// 調(diào)用基本方法
doInit();
}
// 基本方法
protected abstract void doInit();
}
//子類實(shí)現(xiàn)
public class UserPopulator extends AbsPopulator{
@Override
protected void doInit() {
//初始化用戶表,如創(chuàng)建、加載數(shù)據(jù)等
}
}
改造,使用反射增強(qiáng)模板方法模式,使模板方法實(shí)現(xiàn)對(duì)一批固定的規(guī)則的基本方法的調(diào)用。如下:
public abstract class AbsPopulator {
// 模板方法
public final void dataInitialing() throws Exception {
// 獲得所有的public方法
Method[] methods = getClass().getMethods();
for (Method m : methods) {
// 判斷是否是數(shù)據(jù)初始化方法
if (isInitDataMethod(m)) {
m.invoke(this);
}
}
}
// 判斷是否是數(shù)據(jù)初始化方法,基本方法鑒定器
private boolean isInitDataMethod(Method m) {
return m.getName().startsWith("init")// init開始
&& Modifier.isPublic(m.getModifiers())// 公開方法
&& m.getReturnType().equals(Void.TYPE)// 返回值是void
&& !m.isVarArgs()// 輸出參數(shù)為空
&& !Modifier.isAbstract(m.getModifiers());// 不能是抽象方法
}
}
//子類實(shí)現(xiàn)
public class UserPopulator extends AbsPopulator {
public void initUser() {
/* 初始化用戶表,如創(chuàng)建、加載數(shù)據(jù)等 */
}
public void initPassword() {
/* 初始化密碼 */
}
public void initJobs() {
/* 初始化工作任務(wù) */
}
}
在一般的模板方法模式中,抽象模板(這里是AbsPopulator類)需要定義一系列的基本方法,一般都是protected訪問級(jí)別的,并且是抽象方法,這標(biāo)志著子類必須實(shí)現(xiàn)這些基本方法,這對(duì)子類來說既是一個(gè)約束也是一個(gè)負(fù)擔(dān)。但是使用了反射后,不需要定義任何抽象方法,只需定義一個(gè)基本方法鑒別器(例子中isInitDataMethod)即可加載符合規(guī)則的基本方法。鑒別器在此處的作用是鑒別子類方法中哪些是基本方法,模板方法(例子中的dataInitialing)則根據(jù)基本方法鑒別器返回的結(jié)果通過反射執(zhí)行相應(yīng)的方法。
注意:決定使用模板方法模式時(shí),請(qǐng)嘗試使用反射方式實(shí)現(xiàn),它會(huì)讓你的程序更靈活、更強(qiáng)大。
建議109:不需要太多關(guān)注反射效率★★☆☆☆
反射的效率相對(duì)于正常的代碼執(zhí)行確實(shí)低很多(經(jīng)過測(cè)試,相差15倍左右),但是它是一個(gè)非常有效的運(yùn)行期工具類,只要代碼結(jié)構(gòu)清晰、可讀性好那就先開發(fā)起來,等到進(jìn)行性能測(cè)試時(shí)證明此處性能確實(shí)有問題時(shí)再修改也不遲(一般情況下反射并不是性能的終極殺手,而代碼結(jié)構(gòu)混亂、可讀性差則很可能會(huì)埋下性能隱患)。
對(duì)于反射效率問題,不要做任何的提前優(yōu)化和預(yù)期,這基本上是杞人憂天,很少有項(xiàng)目是因?yàn)榉瓷鋯栴}引起系統(tǒng)效率故障的,而且根據(jù)二八原則,80%的性能消耗在20%的代碼上,這20%的代碼才是我們關(guān)注的重點(diǎn),不要單單把反射作為重點(diǎn)關(guān)注對(duì)象。
注意:反射效率低是個(gè)真命題,但因?yàn)檫@一點(diǎn)而不使用它就是個(gè)假命題。