泛型和反射——讀《編寫(xiě)高質(zhì)量代碼:改善Java程序的151個(gè)建議》(七)

讀書(shū)赡若,收獲蓝厌,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度牍汹。
Talk is cheap.Show me the code

建議93:Java的泛型是類(lèi)型擦除的★☆☆☆☆

Java的泛型在編譯期有效唇聘,在運(yùn)行期被刪除介却,也就是說(shuō)所有的泛型參數(shù)類(lèi)型在編譯后都會(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泛型擦除引起的問(wèn)題:在編譯后所有的泛型類(lèi)型都會(huì)做相應(yīng)的轉(zhuǎn)化辫狼。轉(zhuǎn)換規(guī)則如下:

  • List<String>初斑、List<Integer>List<T>擦除后的類(lèi)型為List膨处。
  • List<String>[]擦除后的類(lèi)型為List[]见秤。
  • List<? extends E>List<? super E>擦除后的類(lèi)型為List<E>真椿。
  • List<T extends Serializable&Cloneable>擦除后為List<Serializable>鹃答。

Java之所以如此處理,有兩個(gè)原因:

  • 避免JVM的大換血突硝。C++的泛型生命期延續(xù)到了運(yùn)行期测摔,而Java是在編譯器擦除掉的,如果JVM也把泛型類(lèi)型延續(xù)到運(yùn)行期解恰,那么JVM就需要進(jìn)行大量的重構(gòu)工作了锋八。
  • 版本兼容。在編譯期擦除可以更好地支持原生類(lèi)型(Raw Type)修噪,在Java 1.5或1.6平臺(tái)上查库,即使聲明一個(gè)List這樣的原生類(lèi)型也是可以正常編譯通過(guò)的,只是會(huì)產(chǎn)生警告信息而已黄琼。

我們就可以解釋類(lèi)似如下的問(wèn)題了:

  1. 泛型的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è)類(lèi)都有一個(gè)class屬性樊销,泛型化不會(huì)改變class屬性的返回值

  2. 泛型數(shù)組初始化時(shí)不能聲明泛型類(lèi)型

    //如下代碼編譯時(shí)通不過(guò):
    List<String>[] list = new List<String>[];  
    

    可以聲明一個(gè)帶有泛型參數(shù)的數(shù)組,但是不能初始化該數(shù)組脏款,因?yàn)閳?zhí)行了類(lèi)型擦除操作围苫,List<Object>[]List<String>[]就是同一回事了,編譯器拒絕如此聲明撤师。

  3. instanceof不允許存在泛型參數(shù)

    //以下代碼不能通過(guò)編譯剂府,原因一樣,泛型類(lèi)型被擦除了:
    List<String> list = new ArrayList<String>();  
    System.out.println(list instanceof List<String>)
    

建議94:不能初始化泛型參數(shù)和數(shù)組★☆☆☆☆

示例如下:

//這段代碼是編譯通不過(guò)的剃盾,因?yàn)榫幾g器在編譯時(shí)需要獲得T類(lèi)型腺占,但泛型在編譯期類(lèi)型已經(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();
        }

    }

類(lèi)的成員變量是在類(lèi)初始化前初始化的,所以要求在初始化前它必須具有明確的類(lèi)型意鲸,否則就只能聲明烦周,不能初始化。

建議95:強(qiáng)制聲明泛型的實(shí)際類(lèi)型★☆☆☆☆

示例:

class ArrayUtils{
    //把一個(gè)變長(zhǎng)參數(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)制聲明泛型類(lèi)型
    //asList方法要求的是一個(gè)泛型參數(shù)怎顾,在輸入前定義這是一個(gè)Integer類(lèi)型的參數(shù)读慎,當(dāng)然,輸出也是Integer類(lèi)型的集合了
        List<Integer> list = ArrayUtils.<Integer>asList();
    }
}

注意:無(wú)法從代碼中推斷出泛型類(lèi)型的情況下槐雾,即可強(qiáng)制聲明泛型類(lèi)型夭委。

建議96:不同的場(chǎng)景使用不同的泛型通配符★★☆☆☆

Java泛型支持通配符(Wildcard),可以單獨(dú)使用一個(gè)“蚜退?”表示任意類(lèi)闰靴,也可以使用extends關(guān)鍵字表示某一個(gè)類(lèi)(接口)的子類(lèi)型,還可以使用super關(guān)鍵字表示某一個(gè)類(lèi)(接口)的父類(lèi)型钻注,但問(wèn)題是什么時(shí)候該用extends,什么時(shí)候該用super呢配猫?

  1. 泛型結(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ù)邏輯處理
            }
        }
    
  2. 泛型結(jié)構(gòu)只參與“寫(xiě)”操作則限定下界(使用super關(guān)鍵字)

        public static void write(List<? super Number> list) {
            list.add(123);
            list.add(3.14);
        }
    

對(duì)于是要限定上界還是限定下界幅恋,JDKCollections.copy方法是一個(gè)非常好的例子,它實(shí)現(xiàn)了把源列表中的所有元素拷貝到目標(biāo)列表中對(duì)應(yīng)的索引位置上泵肄,代碼如下:

    //源列表是用來(lái)提供數(shù)據(jù)的捆交,所以src變量需要限定上界,帶有extends關(guān)鍵字腐巢。
    //目標(biāo)列表是用來(lái)寫(xiě)入數(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)即用作“讀”操作又用作“寫(xiě)”操作肉瓦,那該如何進(jìn)行限定呢?不限定胃惜,使用確定的泛型類(lèi)型即可泞莉,如List<E>

建議97:警惕泛型是不能協(xié)變和逆變的★★☆☆☆

什么叫協(xié)變(covariance)和逆變(contravariance)船殉?

在編程語(yǔ)言的類(lèi)型框架中鲫趁,協(xié)變和逆變是指寬類(lèi)型和窄類(lèi)型在某種情況下(如參數(shù)、泛型利虫、返回值)替換或交換的特性挨厚,簡(jiǎn)單地說(shuō)堡僻,協(xié)變是用一個(gè)窄類(lèi)型替換寬類(lèi)型,而逆變則是用寬類(lèi)型覆蓋窄類(lèi)型疫剃。

泛型既不支持協(xié)變钉疫,也不支持逆變:

    public static void main(String[] args) {
        //數(shù)組支持協(xié)變
        Number[] n = new Integer[10];
        //編譯不通過(guò),泛型不支持協(xié)變
        List<Number> ln = new ArrayList<Integer>();
        //報(bào)錯(cuò):Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
    }
  1. 可以使用通配符(Wildcard)模擬協(xié)變慌申,代碼如下所示:

       //Number的子類(lèi)型都可以是泛型參數(shù)類(lèi)型
       List<? extends Number> ln = new ArrayList<Integer>();
    
  2. 可以使用super關(guān)鍵字來(lái)模擬逆變陌选,代碼如下所示:

       //Integer的父類(lèi)型(包括Integer)都可以是泛型參數(shù)類(lèi)型
      List<? super Integer>  li = new ArrayList<Number>();
    
泛型通配符的QA

注意:Java的泛型是不支持協(xié)變和逆變的,只是能夠?qū)崿F(xiàn)協(xié)變和逆變蹄溉。

建議98:建議采用的順序是List<T>咨油、List<?>List<Object>★★☆☆☆

原因如下:

  1. List<T>是確定的某一個(gè)類(lèi)型

    List<T>表示的是List集合中的元素都為T類(lèi)型柒爵,具體類(lèi)型在運(yùn)行期決定役电;

    List<?>表示的是任意類(lèi)型,與List<T>類(lèi)似棉胀,

    List<Object>則表示List集合中的所有元素為Object類(lèi)型法瑟,因?yàn)?code>Object是所有類(lèi)的父類(lèi),所以List<Object>也可以容納所有的類(lèi)類(lèi)型唁奢,

    從這一字面意義上分析霎挟,List<T>更符合習(xí)慣:編碼者知道它是某一個(gè)類(lèi)型,只是在運(yùn)行期才確定而已麻掸。

  2. List<T>可以進(jìn)行讀寫(xiě)操作

    List<T>可以進(jìn)行如add酥夭、remove等操作,因?yàn)樗念?lèi)型是固定的T類(lèi)型脊奋,在編碼期不需要進(jìn)行任何的轉(zhuǎn)型操作熬北。

    List<?>是只讀類(lèi)型的,不能進(jìn)行增加诚隙、修改操作讶隐,因?yàn)榫幾g器不知道List中容納的是什么類(lèi)型的元素,也就無(wú)法校驗(yàn)類(lèi)型是否安全了久又,而且List<?>讀取出的元素都是Object類(lèi)型的巫延,需要主動(dòng)轉(zhuǎn)型,所以它經(jīng)常用于泛型方法的返回值籽孙。注意烈评,List<?>雖然無(wú)法增加、修改元素犯建,但是卻可以刪除元素讲冠,比如執(zhí)行removeclear等方法适瓦,那是因?yàn)樗膭h除動(dòng)作與泛型類(lèi)型無(wú)關(guān)竿开。

    List<Object>也可以讀寫(xiě)操作谱仪,但是它執(zhí)行寫(xiě)入操作時(shí)需要向上轉(zhuǎn)型(Up cast),在讀取數(shù)據(jù)后需要向下轉(zhuǎn)型(Downcast)否彩,而此時(shí)已經(jīng)失去了泛型存在的意義了疯攒。

建議99:嚴(yán)格限定泛型類(lèi)型采用多重界限★★★☆☆

比如在公交車(chē)費(fèi)優(yōu)惠系統(tǒng)中,對(duì)部分人員(如工資低于2500元的上班族并且是站立著的乘客)車(chē)費(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元的上斑族并且站立的乘客車(chē)票打8折
    public static <T extends Staff & Passenger> void discount(T t){
        if(t.getSalary()<2500 && t.isStanding()){
            System.out.println("恭喜你!您的車(chē)票打八折贴浙!");
        }
    }
    public static void main(String[] args) {
        discount(new Me());
    }
}

在Java的泛型中砂吞,可以使用“&”符號(hào)關(guān)聯(lián)多個(gè)上界并實(shí)現(xiàn)多個(gè)邊界限定,而且只有上界才有此限定崎溃,下界沒(méi)有多重限定的情況蜻直。

使用多重邊界可以很方便地解決問(wèn)題,而且非常優(yōu)雅袁串,建議在開(kāi)發(fā)中考慮使用多重限定

建議100:數(shù)組的真實(shí)類(lèi)型必須是泛型類(lèi)型的子類(lèi)型★★★☆☆

期望輸入的是一個(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)榉盒褪穷?lèi)型擦除的赎瑰,toArray方法經(jīng)過(guò)編譯后與如下代碼相同:

    public static Object[] toArray(List list){
        //此處的強(qiáng)制類(lèi)型沒(méi)必要存在,只是為了保持與源代碼對(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í)際類(lèi)型(Actual Type)也是String就可以了,例如:

public class Client<T> {
    public static void main(String[] args) {
        //objArray的實(shí)際類(lèi)型和表面類(lèi)型都是String數(shù)組
        Object[] objArray = {"A","B"};
        //拋出ClassCastException
        String[] strArray = (String[])objArray;

        String[] ss = {"A","B"};
        //objs的真實(shí)類(lèi)型是String數(shù)組啤咽,顯示類(lèi)型為Object數(shù)組
        Object[] objs = ss;
        //順利轉(zhuǎn)換為String數(shù)組
        String[] strs = (String[])objs;
    }
}

如此,那就把泛型數(shù)組聲明為泛型類(lèi)的子類(lèi)型吧渠脉!代碼如下:

public class Client<T> {

    public static <T> T[] toArray(List<T> list, Class<T> tClass) {
        //聲明并初始化一個(gè)T類(lèi)型的數(shù)組
        //通過(guò)反射類(lèi)Array聲明了一個(gè)T類(lèi)型的數(shù)組宇整,
        //由于我們無(wú)法在運(yùn)行期獲得泛型類(lèi)型的參數(shù),因此就需要調(diào)用者主動(dòng)傳入T參數(shù)類(lèi)型
        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è)泛型類(lèi)(特別是泛型集合)轉(zhuǎn)變?yōu)榉盒蛿?shù)組時(shí)芋膘,泛型數(shù)組的真實(shí)類(lèi)型不能是泛型類(lèi)型的父類(lèi)型(比如頂層類(lèi)Object)鳞青,只能是泛型類(lèi)型的子類(lèi)型(當(dāng)然包括自身類(lèi)型),否則就會(huì)出現(xiàn)類(lèi)型轉(zhuǎn)換異常为朋。

建議101:注意Class類(lèi)的特殊性★☆☆☆☆

Java語(yǔ)言是先把Java源文件編譯成后綴為class的字節(jié)碼文件臂拓,然后再通過(guò)ClassLoader機(jī)制把這些類(lèi)文件加載到內(nèi)存中,最后生成實(shí)例執(zhí)行的习寸,這是Java處理的基本機(jī)制胶惰,但是加載到內(nèi)存中的數(shù)據(jù)是如何描述一個(gè)類(lèi)的呢?

Java使用一個(gè)元類(lèi)(MetaClass)來(lái)描述加載到內(nèi)存中的類(lèi)數(shù)據(jù)霞溪,這就是Class類(lèi)孵滞,它是一個(gè)描述類(lèi)的類(lèi)對(duì)象中捆。

Class類(lèi)特殊的地方:

  • 無(wú)構(gòu)造函數(shù)。Java中的類(lèi)一般都有構(gòu)造函數(shù)坊饶,但是Class類(lèi)卻沒(méi)有構(gòu)造函數(shù)泄伪,不能實(shí)例化,Class對(duì)象是在加載類(lèi)時(shí)由 Java 虛擬機(jī)通過(guò)調(diào)用類(lèi)加載器中的defineClass方法自動(dòng)構(gòu)造的匿级。

  • 可以描述基本類(lèi)型蟋滴。雖然8個(gè)基本類(lèi)型在JVM中并不是一個(gè)對(duì)象,它們一般存在于棧內(nèi)存中痘绎,但是Class類(lèi)仍然可以描述它們津函,例如可以使用int.class表示int類(lèi)型的類(lèi)對(duì)象。

  • 其對(duì)象都是單例模式简逮。一個(gè)Class的實(shí)例對(duì)象描述一個(gè)類(lèi)球散,并且只描述一個(gè)類(lèi),反過(guò)來(lái)也成立散庶,一個(gè)類(lèi)只有一個(gè)Class實(shí)例對(duì)象蕉堰,如下代碼返回的結(jié)果都為true

    public class Client {
        public static void main(String[] args) throws Exception {
            //類(lèi)的屬性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類(lèi)提供了很多的getDeclared×××方法和get×××方法,如下:

public static void main(String[] args) throws Exception {
        //方法名稱(chēng)
        String methodName = "doStuff";
        Method m1 = Foo.class.getDeclaredMethod(methodName);
        Method m2 = Foo.class.getMethod(methodName);
}

getMethod方法獲得的是所有public訪問(wèn)級(jí)別的方法悲龟,包括從父類(lèi)繼承的方法屋讶,而getDeclaredMethod獲得是自身類(lèi)的所有方法,包括公用(public)方法须教、私有(private)方法等皿渗,而且不受限于訪問(wèn)權(quán)限。

其他的getDeclaredConstructorsgetConstructors轻腺、getDeclaredFieldsgetFields等與此相似乐疆。

建議103:反射訪問(wèn)屬性或方法時(shí)將Accessible設(shè)置為true★★☆☆☆

Java中通過(guò)反射執(zhí)行一個(gè)方法的過(guò)程如下:獲取一個(gè)方法對(duì)象,然后根據(jù)isAccessible返回值確定是否能夠執(zhí)行贬养,如果返回值為false則需要調(diào)用setAccessible(true)挤土,最后再調(diào)用invoke執(zhí)行方法。如下:

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

那為什么要這么寫(xiě)呢误算?

首先仰美,Accessible的屬性并不是訪問(wèn)權(quán)限,而是指是否要更容易獲得儿礼,是否進(jìn)行安全檢查咖杂。

AccessibleObject類(lèi)的源代碼如下:

//它提供了取消默認(rèn)訪問(wè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屬性只是用來(lái)判斷是否需要進(jìn)行安全檢查的诉字,如果不需要?jiǎng)t直接執(zhí)行,這就可以大幅度的提升系統(tǒng)性能了(注意:取消了安全檢查,也可以運(yùn)行private方法奏窑、訪問(wèn)private屬性的)导披。經(jīng)過(guò)測(cè)試,在大量的反射情況下埃唯,設(shè)置Accessibletrue可以提高性能20倍左右撩匕。

建議104:使用forName動(dòng)態(tài)加載類(lèi)文件★★☆☆☆

動(dòng)態(tài)加載(Dynamic Loading)是指在程序運(yùn)行時(shí)加載需要的類(lèi)庫(kù)文件。

對(duì)Java程序來(lái)說(shuō)墨叛,一般情況下止毕,一個(gè)類(lèi)文件在啟動(dòng)時(shí)或首次初始化時(shí)會(huì)被加載到內(nèi)存中,而反射則可以在運(yùn)行時(shí)再?zèng)Q定是否要加載一個(gè)類(lèi)漠趁。

比如從Web上接收一個(gè)String參數(shù)作為類(lèi)名扁凛,然后在JVM中加載并初始化,這就是動(dòng)態(tài)加載闯传,此動(dòng)態(tài)加載通常是通過(guò)Class.forName(String)實(shí)現(xiàn)的谨朝,只是為什么要使用forName方法動(dòng)態(tài)加載一個(gè)類(lèi)文件呢?

因?yàn)槲覀儾恢缹⒁傻膶?shí)例對(duì)象是什么類(lèi)型(如果知道就不用動(dòng)態(tài)加載)甥绿,而且方法和屬性都不可訪問(wèn)字币。

動(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.....
    }
}

如上共缕,并沒(méi)有對(duì)Utils做任何初始化洗出,只是通過(guò)forName方法加載了Utils類(lèi),但是卻產(chǎn)生了一個(gè)Do Something的輸出图谷,這就是因?yàn)?code>Utils類(lèi)被加載后翩活,JVM會(huì)自動(dòng)初始化其static變量和static代碼塊,這是類(lèi)加載機(jī)制所決定的便贵。

經(jīng)典的應(yīng)用:數(shù)據(jù)庫(kù)驅(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類(lèi)會(huì)被加載到內(nèi)存中辟犀,也就是把自己注冊(cè)到DriverManager中绸硕。

forName只是把一個(gè)類(lèi)加載到內(nèi)存中,并不保證由此產(chǎn)生一個(gè)實(shí)例對(duì)象魂毁,也不會(huì)執(zhí)行任何方法,之所以會(huì)初始化static代碼,那是由類(lèi)加載機(jī)制所決定的,而不是forName方法決定的晶府。也就是說(shuō)桂躏,如果沒(méi)有static屬性或static代碼塊,forName就只是加載類(lèi)川陆,沒(méi)有任何的執(zhí)行行為剂习。

注意:forName只是加載類(lèi),并不執(zhí)行任何代碼较沪。

建議105:動(dòng)態(tài)加載不適合數(shù)組★☆☆☆☆

在Java中鳞绕,數(shù)組是一個(gè)非常特殊的類(lèi),雖然它是一個(gè)類(lèi)购对,但沒(méi)有定義類(lèi)路徑猾昆。

示例:

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ù)組類(lèi)型生成不同的類(lèi)骡苞,具體如下表所示:


數(shù)組編譯對(duì)應(yīng)關(guān)系表

所以動(dòng)態(tài)加載一個(gè)對(duì)象數(shù)組只要加載編譯后的數(shù)組對(duì)象就可以了垂蜗,修改代碼如下:

  //加載一個(gè)String數(shù)組
  Class.forName("[Ljava.lang.String;");
  //加載一個(gè)Long數(shù)組
  Class.forName("[J");

但是這種操作沒(méi)有什么意義,因?yàn)樗荒苌梢粋€(gè)數(shù)組對(duì)象解幽,只是把一個(gè)String類(lèi)型的數(shù)組類(lèi)和long類(lèi)型的數(shù)組類(lèi)加載到了內(nèi)存中贴见,它沒(méi)有定義數(shù)組的長(zhǎng)度,在Java中數(shù)組是定長(zhǎng)的躲株,沒(méi)有長(zhǎng)度的數(shù)組是不允許存在的片部。

因?yàn)閿?shù)組的特殊性,所以Java專(zhuān)門(mén)定義了一個(gè)Array數(shù)組反射工具類(lèi)來(lái)實(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);

注意:通過(guò)反射操作數(shù)組使用Array類(lèi)档悠,不要采用通用的反射處理API

建議106:動(dòng)態(tài)代理可以使代理模式更加靈活★★★☆☆

Java的反射框架提供了動(dòng)態(tài)代理(Dynamic Proxy)機(jī)制望浩,允許在運(yùn)行期對(duì)目標(biāo)類(lèi)生成代理辖所,避免重復(fù)開(kāi)發(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)類(lèi)(要讓哪個(gè)廚師做)
     */
    private Chef chef = null;

    /**
     * 默認(rèn)被代理者(默認(rèn)的廚師老張)
     */
    public HappyRestaurant() {
        chef = new RealChef();
    }

    /**
     * 通過(guò)構(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) {
        //來(lái)到幸福餐廳
        HappyRestaurant happyRestaurant = new HappyRestaurant();
        //點(diǎn)了一份餃子
        String food = happyRestaurant.dumplings();
        System.out.println(food);
        //得到:老張秘制酸湯水餃
    }

代理:"你去餐廳吃飯缘回,并沒(méi)有見(jiàn)給你真正做飯的廚師老張吆视,而是由餐廳的服務(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)外賣(mài))
     */
    @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;
    }

}

注意看啦吧,這里沒(méi)有了餐廳這個(gè)角色,取而代之的是ChefHandler作為主要的邏輯委托處理拙寡,其中invoke方法是接口InvocationHandler定義必須實(shí)現(xiàn)的授滓,它完成了對(duì)真實(shí)方法的調(diào)用。

InvocationHanlder接口:動(dòng)態(tài)代理是根據(jù)被代理的接口生成所有方法的倒庵,也就是說(shuō)給定一個(gè)(或多個(gè))接口褒墨,動(dòng)態(tài)代理會(huì)宣稱(chēng)“我已經(jīng)實(shí)現(xiàn)該接口下的所有方法了”

動(dòng)態(tài)代理的場(chǎng)景類(lèi),代碼如下:

    public static void main(String[] args) {
        //被代理類(lèi)(想吃老張做的飯擎宝,確定目標(biāo))
        Chef chef = new RealChef();
        //代理實(shí)例的處理Handler(打開(kāi)美團(tuán)app搜索老張)
        InvocationHandler handler = new ChefHandler(chef);
        //當(dāng)前加載器(美團(tuán)開(kāi)始搜索并加載老張的信息)
        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)建代理類(lèi)即實(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é)。就增加功能來(lái)說(shuō)极阅,裝飾模式相比于生成子類(lèi)更為靈活胃碾,

使用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)作類(lèi)
 */

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è)裝飾類(lèi)型必然是抽象構(gòu)建(Component)的子類(lèi)型波势,它必須實(shí)現(xiàn)doStuff方法,此處的doStuff方法委托給了動(dòng)態(tài)代理執(zhí)行橄维,
     * 并且在動(dòng)態(tài)代理的控制器Handler中還設(shè)置了決定裝飾方式和行為的條件(即代碼中InvocationHandler匿名類(lèi)中的if判斷語(yǔ)句)尺铣,
     * 當(dāng)然,此處也可以通過(guò)讀取持久化數(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開(kāi)始戲弄貓了
        jerry.doStuff();
}
// 裝飾行為由動(dòng)態(tài)代理實(shí)現(xiàn),實(shí)現(xiàn)了對(duì)裝飾類(lèi)和被裝飾類(lèi)的完全解耦兑障,提供了系統(tǒng)的擴(kuò)展性。

建議108:反射讓模板方法模式更強(qiáng)大★★★☆☆

模板方法模式(Template Method Pattern)的定義是:定義一個(gè)操作中的算法骨架,將一些步驟延遲到子類(lèi)中流译,使子類(lèi)不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟逞怨。簡(jiǎn)單地說(shuō),就是父類(lèi)定義抽象模板作為骨架福澡,其中包括基本方法(是由子類(lèi)實(shí)現(xiàn)的方法叠赦,并且在模板方法被調(diào)用)和模板方法(實(shí)現(xiàn)對(duì)基本方法的調(diào)度,完成固定的邏輯)革砸,它使用了簡(jiǎn)單的繼承和覆寫(xiě)機(jī)制除秀。

普通模板方法,示例如下:

public abstract class AbsPopulator {
    // 模板方法
    public final void dataInitialing() throws Exception {
        // 調(diào)用基本方法
        doInit();
    }

    // 基本方法
    protected abstract void doInit();
}

//子類(lèi)實(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開(kāi)始
                && Modifier.isPublic(m.getModifiers())// 公開(kāi)方法
                && m.getReturnType().equals(Void.TYPE)// 返回值是void
                && !m.isVarArgs()// 輸出參數(shù)為空
                && !Modifier.isAbstract(m.getModifiers());// 不能是抽象方法
    }
}

//子類(lèi)實(shí)現(xiàn)
public class UserPopulator extends AbsPopulator {

    public void initUser() {
        /* 初始化用戶表缎患,如創(chuàng)建慕的、加載數(shù)據(jù)等 */
    }

    public void initPassword() {
        /* 初始化密碼 */
    }

    public void initJobs() {
        /* 初始化工作任務(wù) */
    }
}

在一般的模板方法模式中,抽象模板(這里是AbsPopulator類(lèi))需要定義一系列的基本方法挤渔,一般都是protected訪問(wèn)級(jí)別的肮街,并且是抽象方法,這標(biāo)志著子類(lèi)必須實(shí)現(xiàn)這些基本方法判导,這對(duì)子類(lèi)來(lái)說(shuō)既是一個(gè)約束也是一個(gè)負(fù)擔(dān)嫉父。但是使用了反射后,不需要定義任何抽象方法骡楼,只需定義一個(gè)基本方法鑒別器(例子中isInitDataMethod)即可加載符合規(guī)則的基本方法熔号。鑒別器在此處的作用是鑒別子類(lèi)方法中哪些是基本方法,模板方法(例子中的dataInitialing)則根據(jù)基本方法鑒別器返回的結(jié)果通過(guò)反射執(zhí)行相應(yīng)的方法鸟整。

注意:決定使用模板方法模式時(shí)引镊,請(qǐng)嘗試使用反射方式實(shí)現(xiàn),它會(huì)讓你的程序更靈活篮条、更強(qiáng)大弟头。

建議109:不需要太多關(guān)注反射效率★★☆☆☆

反射的效率相對(duì)于正常的代碼執(zhí)行確實(shí)低很多(經(jīng)過(guò)測(cè)試,相差15倍左右)涉茧,但是它是一個(gè)非常有效的運(yùn)行期工具類(lèi)赴恨,只要代碼結(jié)構(gòu)清晰、可讀性好那就先開(kāi)發(fā)起來(lái)伴栓,等到進(jìn)行性能測(cè)試時(shí)證明此處性能確實(shí)有問(wèn)題時(shí)再修改也不遲(一般情況下反射并不是性能的終極殺手伦连,而代碼結(jié)構(gòu)混亂雨饺、可讀性差則很可能會(huì)埋下性能隱患)。

對(duì)于反射效率問(wèn)題,不要做任何的提前優(yōu)化和預(yù)期,這基本上是杞人憂天蒿涎,很少有項(xiàng)目是因?yàn)榉瓷鋯?wèn)題引起系統(tǒng)效率故障的,而且根據(jù)二八原則移斩,80%的性能消耗在20%的代碼上,這20%的代碼才是我們關(guān)注的重點(diǎn)绢馍,不要單單把反射作為重點(diǎn)關(guān)注對(duì)象向瓷。

注意:反射效率低是個(gè)真命題,但因?yàn)檫@一點(diǎn)而不使用它就是個(gè)假命題舰涌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猖任,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子舵稠,更是在濱河造成了極大的恐慌超升,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哺徊,死亡現(xiàn)場(chǎng)離奇詭異室琢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)落追,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)盈滴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人轿钠,你說(shuō)我怎么就攤上這事巢钓。” “怎么了疗垛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵症汹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我贷腕,道長(zhǎng)背镇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任泽裳,我火速辦了婚禮瞒斩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涮总。我一直安慰自己胸囱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布瀑梗。 她就那樣靜靜地躺著烹笔,像睡著了一般裳扯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谤职,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天嚎朽,我揣著相機(jī)與錄音,去河邊找鬼柬帕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狡门,可吹牛的內(nèi)容都是我干的陷寝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼其馏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼凤跑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起叛复,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仔引,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后褐奥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咖耘,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年撬码,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儿倒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呜笑,死狀恐怖夫否,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叫胁,我是刑警寧澤凰慈,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站驼鹅,受9級(jí)特大地震影響微谓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谤民,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一堰酿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧张足,春花似錦触创、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岩馍。三九已至,卻和暖如春抖韩,著一層夾襖步出監(jiān)牢的瞬間蛀恩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工茂浮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留双谆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓席揽,卻偏偏與公主長(zhǎng)得像顽馋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幌羞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容