1. 程式人生 > >編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議101~109)

編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議101~109)

我命由我不由天 --哪吒

建議101:注意Class類的特殊性

建議102:適時選擇getDeclaredXXX和getXXX

建議103:反射訪問屬性或方法時Accessible設定為true

建議104:使用forName動態載入類檔案

建議105:動態載入不適合陣列

建議106:動態代理可以使代理模式更加靈活

建議107:使用反射增加修飾模式的普適性

建議108:反射讓模板方法模式更強大

建議109:不需要太多關注反射效率

建議101:注意Class類的特殊性

Java語言是先把Java原始檔編譯成字尾為class的位元組碼檔案,然後通過classLoader機制把這些類載入到記憶體中。最後生成例項執行。

class類是Java的反射入口,只有在獲得一個類的描述物件後才能動態的載入、呼叫,一般獲得class物件的三種方法:

1、類屬性載入:如String.class

2、物件的getClass方法:如new String.getClass()

3、forName方法載入:如Class.forName("java.lang.String")

獲得class物件之後,就可以通過getAnnotation()獲得註解,通過getMethods()獲得方法,通過getConstructors()獲得建構函式等。

建議102:適時選擇getDeclaredXXX和getXXX

getMethod方法獲得的是所有public訪問級別的方法,包括從父類繼承的方法。

getDeclaredMethod獲得的是自身類的方法,包括公用的(public)方法、私有(private)方法,而且不受限於訪問許可權。

建議103:反射訪問屬性或方法時Accessible設定為true

Java中通過反射執行一個方法:獲取一個方法物件,然後根據isAccessible返回值確定是否能夠執行,如果返回false,則呼叫setAccessible(true),然後再呼叫invoke執行方法:

 

Method method= ...;
//檢查是否可以訪問
if(!method.isAccessible()){
     method.setAccessible(true);
}
//執行方法
method.invoke(obj, args);

通過反射方法執行方法時,必須在invoke之前檢查Accessible屬性。

建議104:使用forName動態載入類檔案

動態載入是指程式執行時載入需要的類庫檔案,對Java程式來說,雷哥類檔案在啟動時或首次初始化時會被載入到記憶體中,而反射則可以在執行時再決定是否需要載入。

動態載入的意義在於:載入一個類表示要初始化該類的static變數,特別是static程式碼塊,在這裡可以做大量的工作,比如註冊,初始化環境等等。

對於動態載入最經典的應用就是資料庫驅動程式的載入片段,程式碼如下:

//載入驅動
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")時,Driver類會被載入到記憶體中,於是static程式碼塊開始執行,也就是把自己註冊到DriverManager中。

forName只是把一個類載入到記憶體中,並不保證由此產生一個例項物件,也不會執行任何方法,之所以會初始化static程式碼,那是由類載入機制所決定的,而不是forName方法決定的。也就是說,如果沒有static屬性或static程式碼塊,forName就是載入類,沒有任何的執行行為。

總而言之,forName只是把一個類載入到記憶體中,然後初始化static程式碼。

建議105:動態載入不適合陣列

建議106:動態代理可以使代理模式更加靈活

Java的反射框架提供了動態代理(Dynamic Proxy)機制,允許在執行期對目標類生成代理,避免重複開發。我們知道一個靜態代理是通過主題角色(Proxy)和具體主題角色(Real Subject)共同實現主題角色(Subkect)的邏輯的,只是代理角色把相關的執行邏輯委託給了具體角色而已,一個簡單的靜態代理如下所示:

interface Subject {
    // 定義一個方法
    public void request();
}

// 具體主題角色
class RealSubject implements Subject {
    // 實現方法
    @Override
    public void request() {
        // 實現具體業務邏輯
    }

}

class Proxy implements Subject {
    // 要代理那個實現類
    private Subject subject = null;

    // 預設被代理者
    public Proxy() {
        subject = new RealSubject();
    }

    // 通過建構函式傳遞被代理者
    public Proxy(Subject _subject) {
        subject = _subject;
    }

    @Override
    public void request() {
        before();
        subject.request();
        after();
    }

    // 預處理
    private void after() {
        // doSomething
    }

    // 善後處理
    private void before() {
        // doSomething
    }
}

這是一個簡單的靜態代理。Java還提供了java.lang.reflect.Proxy用於實現動態代理:只要提供一個抽象主題角色和具體主題角色,就可以動態實現其邏輯的,其例項程式碼如下:

interface Subject {
    // 定義一個方法
    public void request();
}

// 具體主題角色
class RealSubject implements Subject {
    // 實現方法
    @Override
    public void request() {
        // 實現具體業務邏輯
    }

}

class SubjectHandler implements InvocationHandler {
    // 被代理的物件
    private Subject subject;

    public SubjectHandler(Subject _subject) {
        subject = _subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 預處理
        System.out.println("預處理...");
        //直接呼叫被代理的方法
        Object obj = method.invoke(subject, args);
        // 後處理
        System.out.println("後處理...");
        return obj;
    }
}

注意這裡沒有代理主題角色,取而代之的是SubjectHandler作為主要的邏輯委託處理,其中invoke方法是介面InvocationHandler定義必須實現的,它完成了對真實方法的呼叫。

通過InvocationHandler介面的實現類來實現,所有的方法都是有該Handler進行處理的,即所有被代理的方法都是由InvocationHandler接管實際的處理任務。

程式碼如下:

public static void main(String[] args) {
    //具體主題角色,也就是被代理類
    Subject subject = new RealSubject();
    //代理例項的處理Handler
    InvocationHandler handler =new SubjectHandler(subject);
    //當前載入器
    ClassLoader cl = subject.getClass().getClassLoader();
    //動態代理
    Subject proxy = (Subject) Proxy.newProxyInstance(cl,subject.getClass().getInterfaces(),handler);
    //執行具體主題角色方法
    proxy.request();
}

此時實現了不用顯示建立代理類即實現代理的功能,例如可以在被代理的角色執行前進行許可權判斷,或者執行後進行資料校驗。

動態代理很容易實現通用的代理類,只要在InvocationHandler的invoke方法中讀取持久化的資料即可實現,而且還能實現動態切入的效果。

建議107:使用反射增加修飾模式的普適性

裝飾模式(Decorator Pattern)的定義是“動態的給一個物件新增一些額外的職責。就增加功能來說,裝飾模式相比於生成子類更加靈活“,不過,使用Java的動態代理也可以實現修飾模式的效果,而且其靈活性、適應性會更強。

我們以卡通片《貓和老鼠》(Tom and Jerry)為例,看看如何包裝小Jerry讓它更強大。首先定義Jerry的類:老鼠(Rat類),程式碼如下: 

interface Animal{
    public void doStuff();
}

class Rat implements Animal{
    @Override
    public void doStuff() {
        System.out.println("Jerry will play with Tom ......");
    }
}

接下來,我們要給Jerry增加一些能力,比如飛行,鑽地等能力,當然使用繼承也很容易實現,但我們這裡只是臨時的為Rat類增加這些能力,使用裝飾模式更符合此處的場景,首先定義裝飾類,程式碼如下:

//定義某種能力
interface Feature{
    //載入特性
    public void load();
}
//飛行能力
class FlyFeature implements Feature{

    @Override
    public void load() {
        System.out.println("增加一對翅膀...");
    }
}
//鑽地能力
class DigFeature implements Feature{
    @Override
    public void load() {
        System.out.println("增加鑽地能力...");
    }   
}

此處定義了兩種能力:一種是飛行,另一種是鑽地,我們如果把這兩種屬性賦予到Jerry身上,那就需要一個包裝動作類了,程式碼如下: 

class DecorateAnimal implements Animal {
    // 被包裝的動物
    private Animal animal;
    // 使用哪一個包裝器
    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;
            }
        };
        //當前載入器
        ClassLoader cl = getClass().getClassLoader();
        //動態代理,又handler決定如何包裝
        Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
        proxy.load();
    }
}

注意看doStuff方法,一個修飾型別必然是抽象構建(Component)的子型別,它必須實現doStuff方法,此處的doStuff方法委託了動態代理執行,並且在動態代理的控制器Handler中還設定了決定修飾方式和行為的條件(即程式碼中InvocationHandler匿名類中的if判斷語句),當然,此處也可以通過讀取持久化資料的方式進行判斷,這樣就更加靈活了。

編寫客戶端進行呼叫了,程式碼如下: 

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();
}

此類程式碼只是一個比較通用的裝飾模式,只需要定義被裝飾的類及裝飾類即可,裝飾行為由動態代理實現,實現了對裝飾類和被裝飾類的完全解耦,提供了系統的擴充套件性。

建議109:不需要太多關注反射效率

反射的效率相對於正常的程式碼執行確實低很多,但它是一個非常有效的執行期工具類,只要程式碼結構清晰、可讀性好那就先開發出來,等到進行效能測試時有問題再優化。

最基本的編碼規則:"Don't  Repeat Yourself"

 

編寫高質量程式碼:改善Java程式的151個建議@目錄

相關推薦

編寫質量程式碼:改善Java程式151建議(7:反射___建議93~97)

我們最大的弱點在於放棄。成功的必然之路就是不斷的重來一次。 --達爾文 建議93:Java的泛型是可以擦除的 建議94

編寫質量程式碼:改善Java程式151建議(7:反射___建議101~109)

我命由我不由天 --哪吒 建議101:注意Class類的特殊性 建議102:適時選擇getDeclaredXXX和ge

讀讀《編寫質量程式碼:改善Java程式151建議

這本書可以作為平時寫程式碼的一個參考書,這本書以我個人讀的經驗看來,最好是通過平時程式碼驅動的方式來讀,這樣吸收的快,也讀的快。 這本書主要講什麼,我自己用了個思維導圖概述: 根據這種導圖可知,主要講的就是Java語法、JDK API、程式效能、開源工具和框架、程式設計風格和程式設計思

java學習-排序及加密簽名時資料排序方式 十大經典排序演算法(動圖演示) Java Comparator字元排序(數字、字母、中文混合排序) 編寫高質量程式碼:改善Java程式151個建議(5:陣列集合___建議70~74)

排序有兩種 1. 類實現comparable介面呼叫List.sort(null)或Collections.sort(List<T>)方法進行排序 jdk內建的基本型別包裝類等都實現了Comparablel介面,預設是使用自然排序,即升序排序 自定義類實現Comparable介面必須要實現c

編寫質量程式碼改善Java程式151建議

(Lock類(顯式鎖)和synchronized關鍵字(內部鎖)用在程式碼塊的併發性和記憶體上時的語義是一樣的,都是保持程式碼塊同時只有一個執行緒具有執行權。顯式鎖的鎖定和釋放必須在一個try...finally塊中,這是為了確保即使出現執行期異常也能正常釋放鎖,保證其他執行緒能夠順利執行。Lock鎖為什麼不

編寫質量程式碼:改善Java程式151建議(3:類、物件及方法___建議31~40)

書讀的多而不思考,你會覺得自己知道的很多。 書讀的多而思考,你會覺得自己不懂的越來越多。  -----江疏影 在面向

編寫質量程式碼:改善Java程式151建議(5:陣列集合___建議70~74)

本節內容有些吹毛求疵、晦澀難懂! 建議70:子列表只是原列表的一個檢視 List介面提供了subList方法,其作用是返回一個

編寫質量程式碼:改善Java程式151建議(6:列舉註解___建議83~87)

列舉和註解都是在Java1.5中引入的,列舉改變了常量的宣告方式,註解耦合了資料和程式碼。 建議83:推薦使用列舉定義常量 常

編寫質量程式碼:改善Java程式151建議(8:異常___建議110~113)

不管人類的思維有多麼縝密,也存在“智者千慮必有一失”的缺憾。無論計算機技術怎麼發展,也不可能窮盡所有的場景,這個世界是不完美的,是

編寫質量程式碼:改善Java程式151建議(5:陣列集合___建議60~66)

如果你浪費了自己的年齡,那是挺可悲的。因為你的青春只能持續一點兒時間——很短的一點兒時間。 —— 王爾德 建議6

編寫質量程式碼:改善Java程式151建議(4:字串___建議52~59)

生活不只眼前的苟且。還有讀不懂的詩和到不了的遠方。 --閆妮 建議52:推薦使用String直接賦值 建議53:注意方

Github即將破百萬的PDF:編寫質量程式碼改善JAVA程式151建議

在通往"Java技術殿堂"的路上,本書將為你指點迷津!內容全部由Java編碼的最佳 實踐組成,從語法、程式設計和架構、工具和框架、編碼風格和程式設計思想等五大方面,對 Java程式設計師遇到的各種棘手的疑難問題給出了經驗性的解決方案,為Java程式設計師如何編寫 高質量的Java程式碼提出了151條極為

轉載--編寫質量代碼:改善Java程序的151建議(5:數組集合___建議65~69)

ceo next foreach遍歷 通過 當前 prev 支持 變量 信息 閱讀目錄 建議65:避開基本類型數組轉換列表陷阱 建議66:asList方法產生的List的對象不可更改 建議67:不同的列表選擇不同的遍歷算法 建議68:頻繁插入和刪除時使用LinkLis

編寫質量程式碼改善C#程式的157建議——導航開篇

為什麼要來看這本書    寫此書的作者在書中也有明確的記錄。作者一直在思考一個問題:就是到底什麼樣的程式設計書籍能夠幫助入門者快速進階?所謂“入門者”指的是已經可以使用一門語言來編寫程式,但是不太明白如何編寫高質量程式碼的人。作者回憶自己開發生涯的入門階段發現,那時候常常被以下三類問題所困擾。

深拷貝與淺拷貝---《編寫質量程式碼改善C#程式的157建議》筆記

1.定義: 淺拷貝:將物件所有欄位複製到新物件(副本)中,其中,值型別的值被複制到副本中之後,在副本中的修改不會影響源物件的值;而引用型別欄位被複制到副本中的是引用型別的引用,而不是引用的物件,在副本中對引用型別欄位的修改會影響到源物件本身。 深拷貝:將物件中的所有欄位複製到新物件中,不過無

編寫質量程式碼 改善python程式的91建議》讀書筆記

前言: python 一切皆物件,此為前提。 一、關於函式:不要在函式中定義可變物件為預設值,使用異常替換返回錯誤,保證通過單元測試。python函式傳遞的是物件的引用。在類的初始化方法裡,引數的預設值應該為None,因為預設引數在函式被呼叫時候僅僅被評估一次。 二、編碼。 最開始使用ASC

讀書筆記之《編寫質量程式碼:改善C#程式的157建議

最近,在閱讀書籍《編寫高質量程式碼:改善C#程式的157個建議》,感覺寫得很不錯,特將其中的建議整理了一下,待以後隨時檢視。 現只羅列了其中的部分建議,因為書籍還沒有閱讀完,會慢慢的完善補充。 1 正確操作字串 1.1 確保儘量少的裝箱 在使用其他值引用型別到字串的轉換並

編寫質量程式碼 改善Python程式的91建議

建議1:理解Pythonic概念 建議2:編寫Pythonic程式碼 建議3:理解Python與C語言的不同之處 建議4:在程式碼中適當添加註釋 建議5:通過適當新增空行使程式碼佈局更為優雅、合理 建議6:編寫函式的4個原則 建議7:將常量集中到一個檔案 建議8:利用assert語句來發現問題 建議9:資料

編寫質量程式碼改善Python程式的很多建議

基礎語法 有節制地使用 from...import 語句 Python 提供三種方式來引入外部模組:import語句、from...import語句以及__import__函式,其中__import__函式顯式地將模組的名稱作為字串傳遞並賦值給名稱空間的變數。 使用import需要注

C#程式編寫質量程式碼改善的157建議[4-9]

前言   本文首先亦同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要來學習記錄一下內容:   建議4、TryParse比Parse好   建議5、使用int?來確保值型別也可以為null   建議6、區別readonly和const的使用方法   建議