反射機制

反射

反射機制可以將一個類的實例化操作,方法調用,屬性的調用等操作由編碼期決定改爲在運行期決定,這樣大大的提高了代碼的靈活性;
適度的使用反射機制可以提高代碼複用及靈活性.由於反射會有額外的性能開銷,所以過度的使用會降低系統的性能;
1.反射是Java提供的API,是Java的一部分
2.反射提供了動態解析對象其類型和結構的功能
3.反射API提供了很多動態功能
- 動態加載類
- 動態創建對象
- 動態調用方法
- 動態解析註解
4.利用反射可以解決程序直接的耦合性問題

利用反射檢查對象內部結構

Java 在Object類上定義了 getClass方法, 用於檢查當前對象的具體類型

Class cls = obj.getClass();
System.out.println(cls);

再工作中可以用於檢查對象的具體類型,比如檢查ArrayList迭代器接口的實現類型是啥類型的:

ArrayList list=new ArrayList();
Iterator i = list.iterator();
Class cls = i.getClass();
System.out.println(cls);

反射API提供了檢查類內部的結構信息:

//檢查類的成員變量的方法
        /*
         * getDeclaredFields 返回當前類中聲明的全部成員變量,類可能有多個成員變量,所以返回數組
         */
        Field[] fields = cls.getDeclaredFields();//不含繼承的方法
        cls.getMethods();//獲取包括父類可繼承及本身申明的方法
        for (Field field : fields) {
            System.out.println(field);
            //檢查類中聲明的全部方法信息
            Method[] methods = cls.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
                //類中聲明的構造器
                Constructor[] con = cls.getConstructors();
                for (Constructor constructor : con) {
                    System.out.println(constructor);

反射提供了動態加載類功能

動態加載類型:
Class類:
Class的每個實例稱爲類的類對象
他是反射機制的最重要的一個類
當我們需要使用一個類時,JVM首先會加載該類的class文件,這時JVM就會實例化一個Class的實例,用來加載該類的信息.
由於JVM加載一個類只進行一次,所以每個被JVM加載的類都有且只有一個Class實例用於表示他.
通過Class的實例可以獲取其表示類的一系列信息,比如類的名字,有哪些屬性那些構造方法等.並其提供了一個方法newInstance可以
調用其表示該類的無參構造方法將其實例化(需要有無參構造方法,否則拋出異常)
想獲取一個類的對象通常可以通過下面方法得到:
1.每個類都有一個靜態屬性class,可以直接返回其類的對象,例如想獲取表示String累的對象可以:Class cls = String.class
2.通過Class的靜態方法forName加載該類並得到對象:Class cls = Class.forName(“java.lang.String”)
3.還可以通過加載器ClassLoader加載(更靈活)

Class cls = Class.forName("類名");
Class cls = obj.getClass;

這兩種方法返回的Class 對象有差別嗎? 沒有差別!都類型信息。這個兩種方式的區別在於前提條件不同!
- obj.getClass() 的前提條件是有對象 obj,查找對象的類型。
- Class.forName(類名) 的前提條件是知道“類名”,沒有對象!

舉個栗子:

public static void main(String[] args) {
        Scenner cin = new Scanner(System.in);
        String className = cin.nextLine();
        try {
            //通過這個cls我們可以獲取Person的相關信息,並且可以實例化該類,注:除了Person類之外,還可以加載Java原生類
            Class cls = Class.forName(className);//注意:如果類名錯誤則會拋出ClassNotFoundException
            System.out.println(cls);
            System.out.println(cls.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

Person類:
public class Person {
    public void sayHolle() {
        System.out.println("Holle!");
    }
    public void sayHi() {
        System.out.println("Hi!");
    }
    public void sayName(String name) {
        System.out.println("大家好,我叫"+name);
    }
    public void sayName(String name,int age){
        System.out.println("大家好,我叫"+name+",今年"+age+"歲.");
    }
    private void sayBye() {
        System.out.println("Bye-Bye");
    }
}

反射API提供了動態創建對象功能

反射API可以”動態”創建對象
- 靜態創建,事先知道類名,根據類名創建對象
- 動態創建,事先不知道類名,在用的時候才知道用哪個

        //動態創建對象:類型中必須包含無參構造器
       //newInstance()方法底層調用無參構造器
       /*
         * 如果沒有無參數構造器,則會拋出異常InstantiationException
         * 如果調用私有構造器,則會拋出IllegalAccessException    
         */
         Class cls = Class.forName(類名);
         //通過Class實例化其表示的類
         Object obj = cls.newInstance();

反射API可以調用有參構造器來創建對象

反射API可以動態找到有參構造器,利用反射API動態創建對象
靜態調用有參構造器

Person person = new Person(參數列表);

動態調用有參構造器

Class cls = Class.forName(類名);
//找到一個有參構造器
Constructor c = cls.getConstructor(int.class);
Object o = con.newInstance(n);

看一個小的Dome:

//這裏需要處理很多異常:   ClassNotFoundException, NoSuchMethodException, 
                        //SecurityException, 
                        //InstantiationException, IllegalAccessException, 
                        //IllegalArgumentException, InvocationTargetException
    public static void main(String[] args) throws Exception {
        Scanner cin = new Scanner(System.in);
        System.out.println("請輸入類名:");
        String className = cin.nextLine();
        System.out.println("請輸入要傳遞的參數:");
        int n = cin.nextInt(); 
        Class cls = Class.forName(className);
        //在cls對應的類上查找一個int參數的構造器,找到以後賦值給Constructor的對象
        //如果沒有找到構造器則拋出異常
        Constructor con = cls.getConstructor(int.class);// int.class表示int類型
        //newInstance()執行當前有參構造器創建對象,這裏一定要傳遞對應,類型數據,否則將拋出異常
        Object o = con.newInstance(n);
        //檢查創建對象
        System.out.println(o);//需要對toString()進行重寫
    }

Class.newInstance()與Constructor.newInstance()
- Class.newInstance()只能調用類中無參構造器,如果沒有無參構造器則拋出異常.
- Constructor.newInstance()可以調用任何一個構造器
- 在實際工作中,大部分類都是無參構造器!所以再利用反射創建對象時Class.newInstance()更加方便常用.

利用反射調用方法

步驟:
- 1實例化
* 1.1加載Person類
* 1.2通過Class實例化Person
* 2調用方法
* 2.1通過Class獲取方法
* 2.2通過Method調用其表示的方法

上面講的是調用可見方法
看一個簡單的小Dome:

public static void main(String[] args) {
        Person p = new Person();
        p.sayHolle();
        try {
            //1.實例化      
            //1.1加載Person類
            Class cls = Class.forName("reflet.Person");
            //1.2實例化Person
            Object o = cls.newInstance();
            //2.調用方法
            //2.1通過Class.getDeclaredMethod()獲取方法(Method實例)
            Method method = cls.getDeclaredMethod("sayHolle");
            //通過Method.invoke()調用並啓動方法      
            method.invoke(o, null);
/*
   *通過invoke方法調用其表示的方法.該方法要求傳入兩個參數,第一個爲當前方法所屬的對象,第二個爲調用該方法時要傳入的   *實參(如果沒有直接傳null)
*/
        } catch (Exception e) {
            e.printStackTrace();
        }
}

Method類

Method類的每一個實例,用於表示一個類中的一個方法通常通過Class獲取通過它可以得知其表示的方法更多的詳細信息:訪問修飾符,返回值類型,參數,名字等.並且可以通過invoke方法調用其表示的方法.該方法要求傳入兩個參數,第一個爲當前方法所屬的對象,第二個爲調用該方法時要傳入的實參(如果沒有直接傳null)

利用反射調用私有方法

步驟:
- 1實例化
* 1.1加載Person類
* 1.2通過Class實例化Person
* 2調用方法
* 2.1通過Class獲取方法
* 2.2調用method.setAccessible(true);強制調用私有方法
* 2.3通過Method調用其表示的方法
Dome:

public static void main(String[] args) {
try {
//1.實例化
//1.1加載Person類
Class cls = Class.forName("reflet.Person");
//1.2通過Class實例化Person
Object o = cls.newInstance();
//2.調用方法
//2.1通過Class.getDeclaredMethod()獲取方法(Method實例)
Method method = cls.getDeclaredMethod("sayBye");
//2.2調用method.setAccessible(true);強制調用私有方法
/*
*反射API在調用方法時,Object對象必須包含method方法,參數必須和方法實際參數一致
*否則執行API將拋出異常!
*/
method.setAccessible(true);//設置爲true時可以強制調用sayBye方法(可以理解爲打開訪問權限)
/*注意,一定要在invoke之前調用setAccessible(true);否則會拋出異常*/
//2.3通過Method.invoke()調用並啓動其表示的方法
Object obj = method.invoke(o, null);
//當方法沒有返回值時,則返回null.
System.out.println(val);
} catch (Exception e) {
e.printStackTrace();
}
}

方法的變長參數(JDK1.5+的特性)

變長參數本質上就是一個數組參數!
- 使用類型… 聲明的參數稱爲變長參數,在調用時可以傳遞任意多個參數
- 在方法中按照數組對變長參數進行處理
- 本質上變長參數就是數組參數,可以直接接受同類型數組參數.
- 注意事項:一個方法只能有一個變長參數,且只能作爲最後一個參數
- 技巧:如果祥接收多態參數可以使用Object…
Dome:

    public static void main(String[] args]){
              test();
              test(1);
              test(2,3);
              Test(0);
              Test("你好","double");
    }
  public static void test(int... a){
             //a實際上是一個數組
             int sum = 0;
             for(int i:a){
                   sum+=i;
             }
           System.out.println(sum);
  }
  public static void Test(Object... o){
             for(Object obj:o){
                     System.out.println(obj);
             }
 }

反射的實際用處

經常用於解耦:降低程序的耦合度!!

案例:

需求:

實現一段程序:執行一個類中聲明的全部以test開頭的無參數構造方法.
分析:
被調用的類名—不知道,是未來的某個類
被調用的方法名—-不知道,是某些以test開頭的方法
說明:當前程序將於未來的某個類的某些方法進行耦合,需要使用反射
實現:
1.有用自己輸入未來的類名
2.利用反射將類加載到內存中
3.再利用反射解析類的結果,查找全部方法哪些是以test開頭的方法
4.利用反射創建對象
5.執行對象中全部以test開頭的方法

這裏留給各位看官自己去思考吧 嘻嘻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章