Java反射

Java反射

GitHub\color{red}{GitHub鏈接}

1.Class 類

  • 類是對象,類是java.lang.Class類的實例對象

    public class MyClass {
        public static void main(String[] args) {
            // People的實例對象
            People people = new People();
            // 任何一個類,都是java.lang.Class的實例對象
        }
    }
    // People本身就是一個對象,是java.lang.Class的對象
    class People { }
    
  • Class類的三種表達方式

    Class c1 = People.class;
    // 方法2:通過已知類對象的getClass方法
    Class c2 = people.getClass();
    // 方法3:通過Class類靜態方法forName()
    Class c3 = Class.forName("com.nj.reflect.People");
    
    /*
     * c1、c2表示了People類的類類型(class type)
     * 類也是對象,是Class類的實例對象,這個對象我們稱爲該類的類類型
     */
    // 不管是c1、c2還是c3都代表People的類類型,一個類只可能是Class類的一個實例對象。
    System.out.print(c1 == c2); // true
    System.out.print(c1 == c3); // true
    
  • 獲取對象實例

    // 通過People類的類類型c1、c2或c3創建該類的實例對象。
    People people1 = (People) c1.newInstance(); // 需要有無參構造方法
    

2.動態加載類

  • Class.forName(“類的全稱”)

    • 不僅表示了類的類類型,還代表了動態加載類
    • 編譯時刻加載類是靜態加載類,運行時刻加載類是動態加載類。
  • 靜態加載類

    public class Animal {
        public static void main(String[] args) {
            if (args[0].equals("Dog")) {
                Dog dog = new Dog();
                dog.eat();
            }
            if (args[0].equals("Cat")) {
                Cat cat = new Cat();
                cat.eat();
            }
        }
    }
    class Dog {
        public void eat() { }
    }
    class Cat {
        public void eat() { }
    }
    
    • 靜態加載類的缺點

      1.如果Dog或者Cat不存在,會報錯。但是Dog和Cat不一定會使用。

      2.通過new創建對象是靜態加載類,在編譯時刻,就需要加載所有可能使用到的類。

  • 動態加載類

    通過Class.forName()動態加載類,就不會在編譯時報錯了。

    public class AnimalManager {
        public static void main(String[] args){
            try {
                // 動態加載類,在運行時刻加載。編譯不會再報錯。
                Class c = Class.forName(args[0]);
                // 創建對象,通過類類型創建該類的對象
                Object o = c.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    但是我們通過c.newInstance()創建類對象的時候,問題來了,是該轉換成Dog,還是該轉換成Cat。所以,需要統一標準,創建Animals接口,使Dog和Cat都實現Animals接口。

    public class AnimalManager {
        public static void main(String[] args){
            try {
                // 動態加載類,在運行時刻加載。編譯不會再報錯。
                Class c = Class.forName(args[0]);
                // 創建對象,通過類類型創建該類的對象
                Animals animals = (Animals) c.newInstance();
                animals.eat();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    interface Animals{
        void eat();
    }
    class Dog implements Animals{
        @Override
        public void eat() {}
    }
    class Cat implements Animals{
        @Override
        public void eat() {}
    }
    
    • 優點

      1.動態加載,更加靈活

      2.方便拓展,如果想要加個Pig類,直接創建Pig類,實現Animals接口和方法即可,無需再修改AnimalManager類。

3.反射的使用

  • 獲取class對象的相關信息

    1.獲取class對象的成員變量

    // 獲取class1對象的所有成員變量,不包括父類的成員變量
    Field[] allFields = class1.getDeclaredFields();
    // 獲取class1對象的public成員變量,包括父類裏面的成員變量
    Field[] publicFields = class1.getFields();
    // 獲取class1對象指定的成員變量,父類的無法獲取
    Field name1 = class1.getDeclaredField("name");
    // 獲取class1對象指定的public成員變量,包括查找父類
    Field name2 = class1.getField("name");
    
    // 獲取變量的名稱
    String name = field.getName();
    // 獲取變量的類型
    Class type = field.getType();
    

    2.獲取class對象的方法

    // 獲取class1對象的所有方法,不包括父類的方法
    Method[] allMethods = class1.getDeclaredMethods();
    // 獲取class1對象的public方法,包括父類的public方法
    Method[] publicMethods = class1.getMethods();
    // 獲取class1對象指定的方法,父類的無法獲取
    Method method1 = class1.getDeclaredMethod("getName");
    // 獲取class1對象指定的方法,並且有一個int類型參數的方法
    Method method2 = class1.getDeclaredMethod("getName", int.class);
    // 獲取class1對象有兩個參數的指定方法
    Method method3 = class1.getDeclaredMethod("getName", int.class, String.class);
    // 獲取class1對象指定的public方法,包括父類的public方法。
    Method method4 = class1.getMethod("getName");
    

    3.獲取class對象的構造函數

    // 獲取class1對象所有的構造函數
    Constructor[] allConstructors = class1.getDeclaredConstructors();
    // 獲取class1對象public的構造函數
    Constructor[] publicConstructors = class1.getConstructors();
    // 獲取class1對象指定參數的構造函數
    Constructor constructor1 = class1.getDeclaredConstructor(int.class);
    // 獲取class1對象指定參數並且爲public的構造函數
    Constructor constructor2 = class1.getConstructor(int.class, String.class);
    

    4.class對象的其他方法

    Annotation[] annotations = class1.getAnnotations();// 獲取class對象的所有註解
    Annotation annotation = class1.getAnnotation(Override.class);// 獲取class對象指定註解
    Class superclass = class1.getSuperclass(); // 獲取class對象直接父類的類對象
    Type genericSuperclass = class1.getGenericSuperclass(); // 獲取class對象的直接父類的Type
    Type[] genericInterfaces = class1.getGenericInterfaces(); // 獲取class對象所有接口的Type集合
    String name = class1.getName(); // 獲取class對象名稱,包括報名路徑
    String simpleName = class1.getSimpleName(); // 獲取class類名
    Package aPackage = class1.getPackage(); // 獲取class包信息
    boolean annotation = class1.isAnnotation();// 判斷是否爲註解類
    boolean anEnum = class1.isEnum();// 判斷是否爲枚舉
    boolean anInterface = class1.isInterface();// 判斷是否爲接口類
    boolean array = class1.isArray();//判斷是否爲集合類型
    boolean primitive = class1.isPrimitive(); // 判斷是否爲原始類型(8個基礎類型)
    boolean anonymousClass = class1.isAnonymousClass(); // 判斷是否是匿名內部類
    boolean annotation1 = class1.isAnnotationPresent(Override.class);//判斷是否被某個註解類修飾
    

    5.Method的相關方法

    Method method = class1.getMethod("getName", String.class);
    String name = method.getName(); // 獲取方法名稱
    // 獲取方法的返回值類型的類類型
    Class returnType = method.getReturnType();
    // 獲取方法的參數列表類型的類類型的數組
    Class[] parameterTypes = method.getParameterTypes();
    
  • Java反射的使用

    1.生成來的實例對象

    // 方法一:調用newInstance()方法,但是這個方法要求class1的類必須有無參構造函數。
    Dog dog = (Dog) class1.newInstance();
    // 方法二:通過getConstructor獲取有參的Constructor構造函數,再調用newInstance()創建實例
    Constructor constructor = class1.getConstructor(String.class);
    Dog dog1 = (Dog) constructor.newInstance("dog");
    

    2.調用類的方法

    // 第一步:生成類對象
    Dog dog = (Dog) class1.newInstance();
    // 第二步:通過class對象的getMethod()等方法,獲得具體需要執行的Method對象。
    Method method = class1.getDeclaredMethod("getName", String.class);
    // 如果方法是private修飾,直接調用invoke,會報錯,Java要求程序必須有調用該方法的權限。
    // 調用setAccessible,並將參數設置爲true,就會取消Java語言的訪問權限檢查。
    method.setAccessible(true);
    // 調用Method對象的invoke方法,第一個參數是調用該方法的實例對象,第二個參數是對應需要傳入的參數。
    // invoke的返回值:如果方法的返回值爲void,則invoke返回null,如果不是void,則返回具體的返回值
    // 相當於 dog.getName("dog");
    Object o = method.invoke(dog, "dog");
    

    3.訪問成員變量的值

    // 第一步:生成類對象
    Dog dog = (Dog) class1.newInstance();
    // 第二步:通過getDeclaredField等方法,獲取相應的Field對象
    Field field = class1.getDeclaredField("age");
    // 取消Java權限檢查
    field.setAccessible(true);
    // 獲取dog對象中age成員變量的值
    int age = field.getInt(dog);
    // 修改dog對象中age成員變量的值
    field.setInt(dog, 25);
    // 獲取dog對象中String類型的成員變量
    Field field1 = class1.getDeclaredField("name");
    // 取消Java權限檢查
    field1.setAccessible(true);
    // 獲取name變量的值
    Object o = field1.get(dog);
    // 修改name變量的值
    field1.set(dog, "sam");
    

參考資料

1.Java反射

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