Java中的反射

反射之中包含了一個「反」字,所以想要解釋反射就必須先從「正」開始解釋。
一般情況下,我們使用某個類時必定知道它是什麼類,是用來做什麼的。於是我們直接對這個類進行實例化,之後使用這個類對象進行操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

類加載器

類的加載:當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化。

獲取Class對象的三種方式:
1.通過類名獲取 類名.class
2.通過對象獲取 對象名.getClass()
3.通過全類名獲取 Class.forName(全類名)

一個類在加載過程中的三部曲:
1.加載
就是指將class文件讀入內存,併爲之創建一個Class對象.
任何類被使用時系統都會建立一個Class對象。
2.連接
驗證 是否有正確的內部結構,並和其他類協調一致
準備 負責爲類的靜態成員分配內存,並設置默認初始化值
解析 將類的二進制數據中的符號引用替換爲直接引用
3.初始化 系統給出的默認值

類的加載時機(重點)
創建類的實例
訪問類的靜態變量,或者爲靜態變量賦值
調用類的靜態方法
使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
初始化某個類的子類
直接使用java.exe命令來運行某個主類

類加載起的作用:負責將.class文件加載到內存中,併爲之生成對應的Class對象。

類加載器的分類
Bootstrap ClassLoader 根類加載器:也被稱爲引導類加載器,負責Java核心類的加載,比如System,String等。在JDK中JRE的lib目錄下rt.jar文件中。

Extension ClassLoader 擴展類加載器:負責JRE的擴展目錄中jar包的加載,在JDK中JRE的lib目錄下ext目錄。

sysetm ClassLoader 系統類加載器:負責在JVM啓動時加載來自java命令的class文件。

反射

反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。相當於照鏡子。

對象爲什麼需要照鏡子呢?
1.有可能這個對象是別人傳過來的
2.有可能沒有對象,只有一個全類名
通過反射,可以得到這個類裏面的信息

Student.java–Student.class(字節碼文件)–看成一個對象,這個對象就叫字節碼文件對象–對應的類Class
什麼是反射?
答:每一個Java文件都會對應生成一個class字節碼文件,每一個class字節碼文件都會對應一個字節碼對象,在每一個字節碼文件對象中,也是會有成員方法,成員變量,和構造方法的,反射就是:利用字節碼文件對象去使用該類的成員變量,成員方法,構造方法。
(1)獲取字節碼文件對象的三種方式:
A:Object類的getClass()方法
B:數據類型的靜態class屬性
C:Class類的靜態方法forName()
注意:
在平常寫案例的時候,我們直接使用第二種最方便。
但是實際開發中,我們一般用的都是第三種。是因爲第三種接收的是一個字符串類型的參數,
我們可以把這個參數作爲配置文件的內容進行配置,這樣就實現了一個變化的內容。
案例:使用上述三種方式獲取類的Class對象

先創建一個標準的Person類
package com.edu_01;
public class Person {
    //創建三個成員變量
    String name;
    public int age;
    private String address;
    //創建幾個構造方法
    public Person(){}
    Person(String name){
        this.name = name;
    }
    protected Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    private Person(String name,int age,String address){
        this.name = name;
        this.age = age;
        this.address = address;
    }
    //創建一個成員方法
    public void method(){
        System.out.println("method");
    }
    void function(int price){
        System.out.println(price);
    }
    private void show(String husband,String wife){
        System.out.println(husband+"--"+wife);
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", address=" + address
                + "]";
    }
}
創建一個測試類調用方法
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //獲取Person類對應的Class字節碼文件對象
        //A:Object類的getClass()方法
        Person p1 = new Person();
        Person p2 = new Person();
        Class c1 = p1.getClass();
        Class c2 = p2.getClass();
        System.out.println(p1==p2);//false
        System.out.println(c1==c2);//true
        //每一個類會對應一個字節碼文件對象,而這個字節碼文件對象就是這個類的原型,每一個類有且僅有一個字節碼文件對象
        System.out.println("-------------");
        //B:數據類型的靜態class屬性
        Class c3 = Person.class;
        System.out.println(c2==c3);
        System.out.println("---------------");
        //C:Class類的靜態方法forName()
        //public static Class<?> forName(String className),在這裏所說的類名是全類名(帶包名的類名)
        //Class c4 = Class.forName("Person");//java.lang.ClassNotFoundException: Person
        Class c4 = Class.forName("com.edu_01.Person");
        System.out.println(c3==c4);
    }
}

反射的使用步驟:
我們將成員變量封裝在了 Field中
將構造方法封裝在了 Constructor中
將成員方法封裝在了 Method中
反射:class字節碼文件對象 – 去得到對應的成員對象 – 通過該成員的對象調用方法使用,通過反射獲取構造方法並使用

案例:1.通過反射獲取構造方法
public Constructor[] getConstructors() 獲取公共的構造方法
public Constructor[] getDeclaredConstructors() 獲取所有的構造方法(包括私有)
public Constructor getConstructor(Class… parameterTypes) 根據構造參數獲取公共的指定構造
public Constructor getDeclaredConstructor(Class)
Class類的getConstructor()方法,無論是否設置setAccessible(),都不可獲取到類的私有構造器.
Class類的getDeclaredConstructor()方法,只有設置setAccessible()爲true時,纔可獲取到類的私有構造器(包括帶有其他修飾符的構造器).
在用反射創建一個私有化構造器類的對象時,務必要用getDeclaredConstructor()方法並設置構造器方可訪問setAccessible(true)

package com.edu_02;
import java.lang.reflect.Constructor;
public class ConstructorDemo {
    public static void main(String[] args) throws Exception {
        //public Constructor[] getConstructors() 獲取公共的構造方法
        //獲取Peroson類對應的字節碼文件對象
        Class c = Class.forName("com.edu_01.Person");
        //獲取Perosn類中對應的構造方法
        Constructor[] cons = c.getConstructors();
        //遍歷所有獲取到的構造方法
        for (Constructor con : cons) {
            System.out.println(con);//public com.edu_01.Person()
        }
        System.out.println("----------------");
        //public Constructor[] getDeclaredConstructors() 獲取所有的構造方法(包括私有)
        Constructor[] cons2 = c.getDeclaredConstructors();
        for (Constructor con : cons2) {
            System.out.println(con);
        }
        System.out.println("---------------");
        //public Constructor getConstructor(Class... parameterTypes) 根據構造參數獲取公共的指定構造
        //獲取Person類中的公共的無參數的構造方法
        Constructor con  = c.getConstructor();
        System.out.println(con);
        //怎麼通過我們剛纔獲取的無參構造創建對象
        //public T newInstance(Object... initargs)
        Object obj = con.newInstance();
        System.out.println(obj);
        System.out.println("-------------");
        //獲取Person類中的非公共的構造方法
        //public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        Constructor con2 = c.getDeclaredConstructor(String.class,int.class,String.class);
        //獲取構造器的時候,傳入的什麼參數,在調用獲取到的這個構造方法對象的時候也就需要傳入什麼類型的參數
        //取消這個構造器對象所對應的訪問權限檢測
        con2.setAccessible(true);
        Object obj2 = con2.newInstance("陳奕迅",45,"香港");
        System.out.println(obj2);
        System.out.println("-----------");
        //獲取被Protected修飾的構造方法
        Constructor con3 = c.getDeclaredConstructor(String.class,int.class);
        //取消訪問權限的檢測
        con3.setAccessible(true);
        Object obj3 = con3.newInstance("張學友",50);
        System.out.println(obj3);
    }
}

通過反射獲取成員變量並使用
public Field[] getFields() 獲取公有的成員變量
public Field[] getDeclaredFields() 獲取全部的成員變量,包括私有
public Field getDeclaredField(String name) 傳入變量名稱返回指定的成員變量對象,包括私有
public Field getField(String name) 傳入變量名稱返回指定的成員變量對象,僅可獲取共有的
public void set(Object obj,Object value) 給一個對象的一個字段設置一個值

package com.edu_03;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class FieldDemo {
    public static void main(String[] args) throws Exception {
        //獲取Perosn類對應的字節碼文件對象
        Class c = Class.forName("com.edu_01.Person");
        //利用反射獲取一個[Perosn對象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        //獲取所有公共的字段對象
        //public Field[] getFields()獲取公有的成員變量
        Field[] fields = c.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("--------------");
        //獲取所有的字段對象,包括私有
        //public Field[] getDeclaredFields()獲取全部的成員變量,包括私有
        Field[] fields2 = c.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
        }
        System.out.println("--------------");
        //public Field getDeclaredField(String name) 傳入變量名稱返回指定的成員變量對象,包括私有
        //獲取String name;字段
        Field f = c.getDeclaredField("name");
        //使用f這個對象給一個Perosn對象設置姓名
        //public void set(Object obj, Object value)
        /* 參數1:需要設置的對象
         * 參數2:需要給這個對象設置什麼值       */
        //取消訪問修飾符的限制
        f.setAccessible(true);
        f.set(obj, "謝娜");
        System.out.println("--------------");
        //public Field getField(String name)傳入變量名稱返回指定的成員變量對象,僅可獲取公有的
        //獲取public int age;
        Field f2 = c.getField("age");
        f2.set(obj, 30);
        System.out.println("---------------");
        Field f3 = c.getDeclaredField("address");
        //取消權限檢測
        f3.setAccessible(true);
        f3.set(obj, "湖南");
        System.out.println(obj);
    }
}

通過反射獲取成員方法並使用
public Method[] getMethods()獲取所有公共成員方法
public Method[] getDeclaredMethods()獲取所有成員方法,包括私有
public Method getMethod(String name, Class)

package com.edu_04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class MethodDemo {
    public static void main(String[] args) throws Exception {
        //1.獲取Person類中所有的公有的成員方法
        //創建Pwrson類對應的字節碼文件對象
        Class c = Class.forName("com.edu_01.Person");
        //利用反射的方式創建一個Person對象
        Constructor con  = c.getConstructor();
        Object obj = con.newInstance();
        //public Method[] getMethods()獲取所有公共成員方法,包括父類的公共的成員方法
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("---------------");
        //獲取Person類中的所有的成員方法
        //public Method[] getDeclaredMethods()獲取所有成員方法,包括私有,只能獲取本類的所有的成員方法,不能獲取父類的
        Method[] methods2 = c.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
        System.out.println("---------------");
        // public Method getMethod(String name, Class<?>... parameterTypes)
        //參數一:方法名 參數二:方法參數類型.class 獲取指定的公共方法
        //獲取method()這個公有的成員方法
        Method m  = c.getMethod("method");
        System.out.println(m);
        System.out.println("---------------");
        //public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
        //參數一:方法名 參數二:方法參數類型.class 獲取指定的方法,包括私有
        //需求獲取method這個個方法
        Method m2 = c.getDeclaredMethod("method");
        System.out.println(m2);
        //使用m2這個成員方法的對象
        //public Object invoke(Object obj,Object... args)
        /* 參數1:執行m2這個方法的對象
         * 參數2:執行m2這個方法的時候,需要傳入的參數         */
        m2.invoke(obj);
        System.out.println("-----------------");
        //獲取Person類中function方法
        Method m3 = c.getDeclaredMethod("function", int.class);
        //取消權限檢測
        m3.setAccessible(true);
        m3.invoke(obj, 10);
        System.out.println("-------------------");
        Method m4 = c.getDeclaredMethod("show", String.class,String.class);
        m4.setAccessible(true);
        m4.invoke(obj, "張傑","謝娜");
    }
}

案例:
需求:通過反射運行配置文件的內容。

創建一個人類
package com.edu_05;
public class Person {
    public void eat(){
        System.out.println("民以食爲天");
    }
}
創建一個老師類
public class Teacher {
    public void teach(){
        System.out.println("老師會講課");
    }
}
創建一個學生類
public class Student {
    public void study(){
        System.out.println("好好學習,天天向上");
        }
}
利用測試類測試
package com.edu_05;
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
    public static void main(String[] args) throws Exception {
        //1.創建學生對象調用學習的方法
//      Student s = new Student();
//      s.study();
        //2.需要創建老師的對象,調用講課的方法
//      Teacher t = new Teacher();
//      t.teach();
        //3.需要調用人類的吃飯方法
//      Person p = new Person();
//      p.eat();
        /* 以上的代碼的寫法,俗稱硬編碼(將代碼寫的太死了)
         * 嘗試去將代碼中需要訪問到的內容,這個內容是有可能需要不斷
         * 去動態修改的一個內容,我們可不可以將這些內容存儲到一個配置文件中,
         * 當我們的程序運行起來之後,直接去配置文件中讀取信息就可以了,
         * 以後如果需要更改的話,我們只需要更改配置文件中的內容即可,
         * 而我們自己的代碼不需要改動。。。。
         * 對上面的代碼進行改動:
         * 使用反射的方式創建對象,調用方法。。
         * 1.創建某一個類對應的字節碼文件對象  -- 全類名(className=com.edu_01.Person)
         * 2.將對象需要調用的方法,依然利用反射的方式獲取該方法的對應的對象 -- 方法名(classMethod=study)     */
        //創建Properties集合
        Properties prop = new Properties();
        prop.load(new FileReader("prop.txt"));
        //從集合中獲取對應的全類名和對應的需要執行的方法名
        String className = prop.getProperty("className");
        String classMethod = prop.getProperty("classMethod");
        //創鍵配置文件中類對應的對象
        Class c = Class.forName(className);
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        //通過反射獲取需要執行的方法對象
        Method m = c.getMethod(classMethod);
        //執行獲取到的方法
        m.invoke(obj);
    }
}

可以創建一個配置文件用來存儲數據,然後創建出一個類的字節碼文件對象,利用字節碼文件對象來調用方法,這樣可以使我們的代碼避免修改,只需要配置文件就好,更加方便。
需求:給ArrayList的一個對象,想在這個集合中添加一個字符串數據,如何實現呢?
分析:先創建一個ArrayList集合,然後獲取他的字節碼文件對象,再利用對象調用方法將字符串類型添加進去。

  1.創建ArrayList類對應的字節碼文件對象
  2.通過字節碼文件對象獲取到add方法對象
  3.調用add方法的對象,給集合中添加字符串
package com.edu_06;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ArrTest {
    public static void main(String[] args) throws Exception {
        //1.創建ArrayList類對應的字節碼文件對象
        ArrayList<Integer> arr = new ArrayList<>();
        Class c = arr.getClass();
        //2.通過字節碼文件對象獲取到add方法對象
        Method addM = c.getDeclaredMethod("add", Object.class);
        //傳入參數的時候必須傳入與方法上的參數一致的參數
        //3.調用add方法的對象,給集合中添加字符串
        addM.invoke(arr, "java");
        addM.invoke(arr, "world");
        System.out.println(arr);
    }
}

爲什麼Integer 類型的能添加String類型的?
其實泛型是給jvm虛擬機看的,當程序一旦執行起來後,泛型就會自動去掉,這也稱之爲泛型擦除!

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