1. 程式人生 > >黑馬程式設計師 java高新技術 反射

黑馬程式設計師 java高新技術 反射

---------- android培訓java培訓、期待與您交流! ----------

一、Class類

    Class是Java程式中各個Java類的總稱;它是反射的基石,通過Class類來使用反射。

   物件的建立和使用:

      建立例項物件:不可用new Class()的方式,因為Class沒有這樣的構造方法。而是將位元組碼物件賦值給Class變數。如Class c1 =Person.class。

      如Person類,它的位元組碼:首先要將Person的java檔案編譯為class檔案放於硬碟上,即為二進位制程式碼,再將這些程式碼載入到記憶體中,接著用它建立一個個物件。就是把類的位元組碼加       載進記憶體中,再用此位元組碼建立一個個物件。當有如Person、Math、Date等等的類,那麼這些位元組碼就是分別的一個Class物件。即Class c2 =Date.class;。

      2、獲得類的位元組碼物件:如Class.forName(”java.lang.String”)即獲得String.class。得到這個位元組碼物件有兩種情況:

      1)此類已經載入進記憶體:若要得到此類位元組碼,不需要再載入。

      2)此類還未載入進記憶體:類載入器載入此類後,將位元組碼快取起來,forName()方法返回載入進來的位元組碼。

      3、得到各位元組碼對應的例項物件(Class型別)的方式:

     類名.class:如System.class,String.class等等

     物件.class:如new Date().getClass()或者d.getClass()。(Date d = new Date())

     Class.forName(“類名”):如Class.forName(”java.lang.String”)

     當獲取類名的時候,是不知道此類的名稱的,forName(字串引數)方法中傳入字串型的變數作為對外訪問的入口,

     即傳入什麼類名就獲得什麼類名,從而得知相應的類名。

 注:forName()是靜態方法,是反射中使用的一種方式獲取位元組碼的例項物件。

    每個類的位元組碼物件只有唯一的一個,如任何字串物件,對應唯一的String.clas位元組碼。

     九個預定義的Class:

   1)包括八種基本型別(byte、short、int、long、float、double、char、boolean)

的位元組碼物件和一種返回值為void型別的void.class。

   2)Integer.TYPE是Integer類的一個常量,它代表此包裝型別包裝的基本型別的位元組碼,所以和int.class是相等的。

   基本資料型別的位元組碼都可以用與之對應的包裝類中的TYPE常量表示

   陣列型別的Class例項物件,可以用Class.isArray()方法判斷是否為陣列型別的。

總結:只要是在源程式中出現的型別都有各自的Class例項物件,如int[].class、void.class等。

     一些方法

   1、static Class forName(String className)返回與給定字串名的類或介面的相關聯的Class物件。

   2、Class getClass() 返回的是Object執行時的類,即返回Class物件即位元組碼物件

   3、Constructor getConstructor() 返回Constructor物件,它反映此Class物件所表示的類的指定公共構造方法。

   4、Field getField(String name) 返回一個Field物件,它表示此Class物件所代表的類或介面的指定公共成員欄位。

   5、Field[] getFields()返回包含某些Field物件的陣列,表示所代表類中的成員欄位。

   6、Method getMethod(String name,Class… parameterTypes) 返回一個Method物件,它表示的是此Class物件所代表的類的指定公共成員方法。

   7、Method[] getMehtods() 返回一個包含某些Method物件的陣列,是所代表的的類中的公共成員方法。

   8、String getName()以String形式返回此Class物件所表示的實體名稱。

   9、String  getSuperclass()返回此Class所表示的類的超類的名稱

   10、boolean isArray() 判定此Class物件是否表示一個數組

   11、boolean isPrimitive()判斷指定的Class物件是否是一個基本型別。

   12、T newInstance()建立此Class物件所表示的類的一個新例項。

public static void fuction()throws Exception{  
        String str1 = "abc";  
        Class cls1 = str1.getClass();  
        Class cls2 = String.class;  
        Class cls3 = Class.forName("java.lang.String");  
        System.out.println(cls1 == cls2);//true  
        System.out.println(cls1 == cls3);//true  
          
        //判斷是否為基本型別:isPrimitive()  
        System.out.println(cls1.isPrimitive());//false  
        System.out.println(int.class == Integer.class);//false  
        //Integer.TYPE代表包裝類對應的基本資料型別的位元組碼  
        System.out.println(int.class == Integer.TYPE);//true  
        System.out.println(int[].class.isPrimitive());//false  
        //判斷是否為陣列型別的  
        System.out.println(int[].class.isArray());//true  
} 


二、反射

    概述:把Java類中的各種成分對映成相應的Java類。

    如Class中的每一個方法返回的都是一種類(型),即Method對所有方法抽取成了這個類Method,它的每一個物件(如變數methodObj1)代表了一個方法。

  一個類中的組成成分:

   成員變數、方法、建構函式、包等資訊,也用一個個java類來表示(如汽車是一個類,其中的發動機,變速箱等也是對應的一個個類)

    表示Java類的Class類顯然要提供一系列的方法   來獲取其中的變數、方法、建構函式、修飾符、包等資訊,這些資訊就是用相應的類的例項物件來表示,

    他們是Field、Method、Contructor、Package等。

    一個類中的每個成員都可用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可得到這些例項物件。

   反射中的各類

   Constructor類

   Constructor代表某個類的構造方法

   獲取構造方法:

  1)如何得到摸個類的所有構造方法:如得到String類的所有構造方法

 Constructor[] cons = Class.forName(“java.lang.String”).getConstructors();

  2)獲取某一個構造方法:

Constructor con =String.class.getConstructor(StringBuffer.class);

   建立例項物件:

  1)通常方式:String str = new String(new StringBuffer (”abc”));

  2)反射方式:String str = (String)con.newInstance(new StringBuffer(“abc”));

  呼叫獲得的方法時要用到上面相同型別的例項物件,即兩個StringBuffer()要對應相等。

   NewInstance():構造出一個例項物件,每呼叫一次就構造一個物件。

注意:上面的兩個地方①②都要用到StringBuffer,這必須是一致的。

  第①個是指定要帶StringBuffer引數型別的構造方法,即所需使用的是含StringBuffer型別的構造方法。

  第②個是用這個構造方法建立物件,要傳入的引數型別是StringBuffer。

   Class.newInstance():建立一個物件,不帶引數的構造方法。

//new String(new StringBuffer("abc"));  
Constructor constructor1 =   
String.class.getConstructor(StringBuffer.class);  
String str2 =  
 (String)constructor1.newInstance(new StringBuffer("abc"));  
System.out.println(str2);  
          
//Class.newInstrance建立不帶引數的構造方法  
String str3 =   
(String)Class.forName("java.lang.String").newInstance();  
System.out.println("str3:"+str3);  

  Field類

   Field類代表成員變數(欄位)的反射。

public class ReflectPoint {  
    private int x;  
    public int y;  
  
    public String toString(){  
        return str1+";" + str2 + ";" + str3;  
    }  
}  
public class FieldTest(){  
ReflectPoint pt1 = new ReflectPoint(3,5);  
    //fieldX和fieldY並不是物件身上的變數,而是類上的  
    //要用它去取某個物件上的對應的值,傳入什麼物件,就取相應物件的值。  
    Field fieldY = pt1.getClass().getField("y");  
    System.out.println(fieldY.get(pt1));  
    //獲取私有的成員變數  
Field fieldX = pt1.getClass().getDeclaredField("x");  
    fieldX.setAccessible(true);  
    System.out.println(fieldX.get(pt1));  
}  

獲取成員變數

  如上例子所示:

  1、獲取公有的成員變數:

     getField(String name)和get(變數)

  2、獲取私有的成員變數:暴力反射

    getDeclared(String name)

    setAccessible(boolean b),將b設為true即可

get(變數)

//替換字元   
private static void changeStringValue(Object obj) throws Exception {  
    Field[] fields = obj.getClass().getFields();  
    for(Field field : fields){  
        //此處需要用==比較,因為是同一份位元組碼物件  
        if(field.getType() == String.class){  
            String oldValue = (String)field.get(obj);  
            String newValue = oldValue.replace('b','a');  
            field.set(obj, newValue);  
        }  
    }  
}  

Method類

1、概述:Method類代表某個類中的一個成員方法。

     呼叫某個物件身上的方法,要先得到方法,再針對某個物件呼叫。

 2、專家模式:誰呼叫這個資料,就是誰在呼叫它的專家。

    如人關門:

   呼叫者:是門呼叫管的動作,物件是門,因為門知道如何執行關的動作,通過門軸之類的細節實現。

   指揮者:是人在指揮門做關的動作,只是給門發出了關的訊號,讓門執行。

總結:變數使用方法,是方法本身知道如何實現執行的過程,也就是“方法物件”呼叫方法,才執行了方法的每個細節的。

 3、獲取某個類中的某個方法:(如String str = ”abc”)

   1)通常方式:str.charAt(1)

   2)反射方式:

   Method charAtMethod = Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

    charAtMethod.invoke(str,1);

  說明:如果傳遞給Method物件的invoke()方法的第一個引數為null,說明Method物件對應的是一個靜態方法

4、用反射方式執行某個main方法:

  首先要明確為何要用反射:在寫源程式時,並不知道使用者傳入的類名是什麼,但是雖然傳入的類名不知道,

  而知道的是這個類中的方法有main這個方法,所以可以通過反射的方式,通過使用者傳入的類名(

  可定義字串型變數作為傳入類名的入口,通過這個變數代表類名),內部通過傳入的類名獲取其main方法,然後執行相應的內容。

//Method類演示  
private static void methodTest(String [] args) throws Exception {  
    String str1 = "abc";  
    //一般方法:  
    System.out.println(str1.charAt(1));  
    //反射方法 :  
    Method methodCharAt =  
        Class.forName("java.lang.String").getMethod("charAt",int.class);  
    System.out.println(methodCharAt.invoke(str1,1));  
      
    //用反射方式執行某個main方法  
    //一般方式:  
    Test.main(new String[]{"111","222","333"});  
    System.out.println("-------");  
      
    //反射方式:  
    String startingClassName = args[0];  
    Method methodMain =  
        Class.forName(startingClassName).getMethod("main",String[].class);  
        //方案一:強制轉換為超類Object,不用拆包  
        methodMain.invoke(null,(Object)new String[]{"111","222","333"});  
        //方案二:將陣列打包,編譯器拆包後就是一個String[]型別的整體  
        methodMain.invoke(null,new Object[]{new String[]{"111","222","333"}});  
    }  
//定義一個測試類  
class Test{  
    public static void main(String [] args){  
        for(String arg : args){  
            System.out.println(arg);  
        }  
    }  
}  

 陣列的反射

   1、陣列位元組碼的名字:有[和陣列對應型別的縮寫,如int[]陣列的名稱為:[I

   2、基本資料型別的一維陣列不能轉換為Object陣列,如:

      int[] a = new int[3];Object[] obj= a;這樣是不成立的。

   3、如何得到某個陣列中的某個元素的型別:

     例:int a = newint[3];Object[] obj= new Object[]{”ABC”,1};

     無法得到某個陣列的具體型別,只能得到其中某個元素的型別,如

      Obj[0].getClass().getName()得到的是java.lang.String

     若通過b.getClass().getName(),結果是:[Ljava.lang.Object;

public static void arrayTest()throws Exception {  
    int[] a1 = new int[]{1,2,3};  
    int[] a2 = new int[4];  
    int[][] a3 = new int[2][3];  
    Integer[] ai = new Integer[3];  
    String[] a4 = new String[]{"a","b","c"};  
      
    System.out.println(a1.getClass() == a2.getClass());  
    System.out.println((Object)a1.getClass() == (Object)a3.getClass());  
          
    System.out.println(a3[0].getClass() == a1.getClass());  
    System.out.println(a3[0].getClass().getSuperclass().getName());  
    System.out.println(a1.getClass().equals(a3.getClass()));  
    System.out.println(a1.getClass().equals(a4.getClass()));  
    System.out.println(a1.getClass().getName());  
    System.out.println("----:" + a1.getClass());  
    System.out.println(a1.getClass().getSuperclass().getName());  
    System.out.println(a2.getClass().getSuperclass().getName());  
          
    Object obj1 = a1;  
    Object obj2 = a2;  
    //int基本資料型別不是Object的  
    //Object[] obj3 = a1;  
    Object[] obj4 = a3;  
    Object[] obj5 = a4;  
          
    System.out.println(a1);  
    System.out.println(a4);  
      
    System.out.println(Arrays.asList(a1));  
    System.out.println(Arrays.asList(a4));  
    System.out.println("-------");  
    int[] a = new int[3];  
    Object[] obj = new Object[]{"abc",new Integer(1)};  
    System.out.println(a.getClass().getName());  
    System.out.println(obj.getClass().getName());  
    System.out.println(obj[0].getClass().getName());  
}  

  HashSet和與hashCode的分析

import java.util.ArrayList;  
import java.util.Collection;  
import java.util.HashSet;  
  
public class ReflectTest2 {  
    public static void main(String [] args){  
        Collection cons = new HashSet();  
        ReflectPoint pt1 = new ReflectPoint(3,3);  
        ReflectPoint pt2 = new ReflectPoint(5,5);  
        ReflectPoint pt3 = new ReflectPoint(3,3);  
  
        cons.add(pt1);  
        cons.add(pt2);  
        cons.add(pt3);  
        cons.add(pt1);  
        cons.remove(pt1);  
        System.out.println(cons.size());  
    }  
}  
  
public class ReflectPoint {  
    private int x;  
    public int y;  
    public String str1 = "ball";  
    public String str2 = "basketball";  
    public String str3 = "itcast";  
      
    public ReflectPoint(int x, int y) {  
        super();  
        this.x = x;  
        this.y = y;  
    }  
      
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        //System.out.println("demo...");//測試  
        return result;  
    }  
  
    public boolean equals(Object obj) {  
        if (this == obj)  
            return true;  
        if (obj == null)  
            return false;  
        if (getClass() != obj.getClass())  
            return false;  
        ReflectPoint other = (ReflectPoint) obj;  
        if (x != other.x)  
            return false;  
        if (y != other.y)  
            return false;  
        return true;  
    }  
  
    public String toString(){  
        return str1+";" + str2 + ";" + str3;  
    }  
}

  覆寫hashCode()方法的意義:只有存入的是具有hashCode演算法的集合的,覆寫hashCode()方法才有價值。

 1、雜湊演算法的由來:

    若在一個集合中查詢是否含有某個物件,通常是一個個的去比較,找到後還要進行equals的比較,物件特別多時,效率很低,

    通過雜湊演算法,將集合分為若干個區域,每個物件算出一個雜湊值,可將雜湊值分組(一般模32為一組),每組對應某個儲存區域,

    依一個物件的雜湊碼即可確定此物件對應區域,從而減少每個物件的比較,只需在指定區域查詢即可,從而提高從集合中查詢元素的效率。

2、如果不存入是hashCode演算法的集合中,那麼則不用複寫此方法。

3、只有類的例項物件要被採用雜湊演算法進行存入和檢索時,這個類才需要按要求複寫hashCode()方法,

      即使程式可能暫時不會用到當前類的hashCode()方法,但是為提供一個hashCode()方法也不會有什麼不好,

       沒準以後什麼時候就會用到這個方法,所以通常要求hashCode()和equals()兩者一併被覆蓋。

4、提示

    1)若同類兩物件用equals()方法比較的結果相同時,他們的雜湊碼也必須是相等的,

       但反過來就不成立了,如”BB”和”Aa”兩字串用equals()比較式不相等的,但是他們的雜湊值是相等的。

    2)當一個物件被儲存進HashSet集合中,就不能再修改參與計算雜湊值的欄位,

       否則物件被修改後的雜湊值與最初被存入的HashSet集合中的雜湊值就不同了。在這種情況下,

      即使contains()方法是用物件的當前引用作為引數去HashSet集合中檢索物件,也將返回找不到物件的結果,

      這也導致無法從HashSet集合中單獨刪除當前物件,從而造成記憶體洩露。

簡單說,之前存入的物件和修改後的物件,是具有不同的雜湊值,被認為是不同的兩個物件

         這樣的物件不再使用,又不移除,而越來越多,就會導致記憶體洩露。

    補充:

     記憶體洩露:某些物件不再使用了,佔用著記憶體空間,並未被釋放,就會導致記憶體洩露;

     也就是說當程式不斷增加物件,修改物件,刪除物件,日積月累,記憶體就會用光了,就導致記憶體溢位。

    3)物件在呼叫方法時,物件會先進行一次自身hashCode()方法的呼叫,再進行操作方法。

   反射作用

   概述 

1、框架:通過反射呼叫位置Java類的一種方式。

        如房地產商造房子使用者住,門窗和空調等等內部都是由使用者自己安裝,房子就是框架,使用者需使用此框架,安好門窗等放入到房地產商提供的框架中。

       框架和工具類的區別:工具類被使用者類呼叫,而框架是呼叫使用者提供的類。

2、框架機器要解決的核心問題:

       我們在寫框架(造房子的過程)的時候,呼叫的類(安裝的門窗等)還未出現,那麼,框架無法知道要被呼叫的類名,

      所以在程式中無法直接new其某個類的例項物件,而要用反射來做。

類載入器:

1、簡述:類載入器是將.class的檔案載入經記憶體,也可將普通檔案中的資訊載入進記憶體。

2、檔案的載入問題:

      a、eclipse會將源程式中的所有.java檔案載入成.class檔案,以確保編譯,然後放到classPath指定的目錄中去。

       並且會將非.java檔案原封不動的複製到.class指定的目錄中去。在真正編譯的時候,使用classPath目錄中的檔案,即放置.class檔案的目錄。

      b、寫完程式是要講配置檔案放到.class檔案目錄中一同打包,這些都是類載入器載入的,資原始檔(配置檔案)也同樣載入了配置檔案。

      c、框架中的配置檔案都要放到classPath指定的資料夾中,原因是它的內部就是用類載入器載入的檔案。

3、資原始檔的載入:是使用類載入器。

       a、由類載入器ClassLoader的一個物件載入經記憶體,即用getClassLoader()方法載入。

         若要載入普通檔案,可用getResourseAsStream(String name)在classPath的檔案中逐一查詢要載入的檔案。

       b、在.class身上也提供了方法來載入資原始檔,其實它內部就是先呼叫了Loader方法,再載入的資原始檔。

        如:Reflect.class.getResourseAsStream(String name)

 4、配置檔案的路徑問題:

      第一、用絕對路徑,通過getRealPath()方法運算出來具體的目錄,而不是內部編碼出來的。

         一般先得到使用者自定義的總目錄,在加上自己內部的路徑。可以通過getRealPath()方法獲取檔案路徑。

         對配置檔案修改是需要要儲存到配置檔案中,那麼就要得到它的絕對路徑才行,因此,配置檔案要放到程式的內部。

      第二、name的路徑問題:

         ①如果配置檔案和classPath目錄沒關係,就必須寫上絕對路徑,

          ②如果配置檔案和classPath目錄有關係,即在classPath目錄中或在其子目錄中(一般是資原始檔夾resource)

           那麼就得寫相對路徑,因為它自己瞭解自己屬於哪個包,是相對於當前包而言的。

       示例:

       配置檔案內容:

        className=java.util.ArrayList

import java.io.FileInputStream;  
import java.io.InputStream;  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.HashSet;  
import java.util.Properties;  
  
public class ReflectTest2 {  
    public static void main(String [] args)throws Exception{  
        //讀取系統檔案到讀取流中  
        //方式一:  
        //InputStream ips = new FileInputStream("config.propert");  
        /*getRealPath()--得到完整的路徑
         * 一定要用完整的路徑,但完整的路徑不是硬編碼出來的,而是運算出來的。*/  
        //方式二:  
        //InputStream ips = 
        //   ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/text1/config.propert");  
        //方式三:  
            //第一種:配置檔案(資原始檔)在當前包中  
        InputStream ips = ReflectTest2.class.getResourceAsStream("resourse/config.propert");  
            //第二種:配置檔案(資原始檔)不在當前包中,和此包沒太大關係  
        //InputStream ips = 
        //   ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/test2/resourse/config.properties");  
          
        //載入檔案中的鍵值對  
        Properties props = new Properties();  
        props.load(ips);  
        //關閉資源,即ips呼叫的那個系統資源  
        //注意:關閉的是ips操作的流,載入進記憶體後,就不再需要流資源了,需要關閉  
        ips.close();  
        //定義變數,將檔案中的類名賦值給變數  
        String className = props.getProperty("className");  
        //通過變數,建立給定類的物件  
        Collection cons =   
                (Collection)Class.forName(className).newInstance();  
          
        //將元素新增到集合中  
        /*Collection cons = new HashSet();*/  
        ReflectPoint pt1 = new ReflectPoint(3,3);  
        ReflectPoint pt2 = new ReflectPoint(5,5);  
        ReflectPoint pt3 = new ReflectPoint(3,3);  
        cons.add(pt1);  
        cons.add(pt2);  
        cons.add(pt3);  
        cons.add(pt1);  
        //移除元素  
        cons.remove(pt1);  
        System.out.println(cons.size());  
    }  
}