胖哥說反射 上卷

我來學習反射

1.爲什麼我們要學習反射?

通過反射機制可以獲取到一個類的完整信息,例如:所有(包含private修飾)屬性和方法,包信息等。

換句話說,Class本身表示一個類的本身,通過Class可以完整獲取一個類中的完整結構,包含此類中的方法定義,屬性定義等。

反射就是把Java類中的各種成分映射成一個個的Java對象

例如:一個類有:成員變量、方法、構造方法、包等等信息,利用反射技術可以對一個類進行解剖,把個個組成部分映射成一個個對象。

2.反射的核心是什麼?

我個人認爲:一切的操作都是講使用Object完成,類或者數組的引用是可以用Object進行接收。

也就是我們之前說Java中的我認爲很重要的多態,對象的多態:Object object= 任何引用類型的實例對象

3.類的加載過程

類的正常加載過程:反射的原理在與Class對象

Class對象的由來是將class文件讀入內存,併爲之創建一個Class對象,那麼Class對象在反射中起到什麼作用?

我們用圖片已經說明了很清楚了,我們在來看一下官方的解釋

For every type of object, the Java virtual machine instantiates an immutable instance of java.lang.Class which provides methods to examine the runtime properties of the object including its members and type information. Class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.

對於每一種類,Java虛擬機都會初始化出一個Class類型的實例,每當我們編寫並且編譯一個新創建的類就會產生一個對應Class對象,並且這個Class對象會被保存在同名.class文件裏。當我們new一個新對象或者引用靜態成員變量時,Java虛擬機(JVM)中的類加載器系統會將對應Class對象加載到JVM中,然後JVM再根據這個類型信息相關的Class對象創建我們需要實例對象或者提供靜態變量的引用值。

如上圖所示,比如創建編譯一個Student類,那麼,JVM就會創建一個Student對應Class類的Class實例,該Class實例保存了Student類相關的類型信息,包括屬性,方法,構造方法等等,通過這個Class實例可以在運行時訪問Student對象的屬性和方法等。另外通過Class類還可以創建出一個新的Student對象。這就是反射能夠實現的原因,可以說Class是反射操作的基礎。

需要特別注意的是,每個class(注意class是小寫,代表普通類)類,無論創建多少個實例對象,在JVM中都對應同一個Class對象。

4.Class API簡要說明

跟我們之前學習查看Math、String類一樣的過程

Class 類的實例表示正在運行的 Java 應用程序中的類和接口。也就是JVM中有N多的實例每個類都有該Class對象。(包括基本數據類型)

Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的defineClass 方法自動構造的。也就是這不需要我們自己去處理創建,JVM已經幫我們創建好了。

沒有公共的構造方法,方法共有64個太多了。下面用到哪個就詳解哪個吧

5.反射的使用

Java 提供反射機制,依賴於 Class 類和 java.lang.reflect 類庫。其主要的類如下:

  1. Class:表示類或者接口
  2. Field:表示類中的成員變量
  3. Method:表示類中的方法
  4. Constructor:表示類的構造方法
  5. Array:該類提供了動態創建數組和訪問數組元素的靜態方法

先自己設置一個Student類來完成對應的測試,代碼如下:

package com.pangsir.model;

public class Student {
    public int no;
    public String sex;

    private String name;
    private int age;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    /*
     * 構造方法
     */
    
   Student(String str){  
       System.out.println("(默認)的構造方法 s = " + str);  
   }  
     
   //無參構造方法  
   public Student(){  
       System.out.println("調用了公有、無參構造方法執行了。。。");  
   }  
     
   //有一個參數的構造方法  
   protected Student(char name){  
       System.out.println("姓名:" + name);  
   }  
     
   //有多個參數的構造方法  
   public Student(String name ,int age){  
       System.out.println("姓名:"+name+"年齡:"+ age);//這的執行效率有問題,以後解決。  
   }  
     
   //私有構造方法  
   private Student(int age){  
       System.out.println("私有的構造方法   年齡:"+ age);  
   }
    
}

5.1 獲取Class對象的三種方式

說Class是反射能夠實現的基礎的另一個原因是:Java反射包java.lang.reflect中的所有類都沒有public構造方法,要想獲得這些類實例,只能通過Class類獲取。所以說如果想使用反射,必須得獲得Class對象。

/*
 * Constructor. Only the Java Virtual Machine creates Class
 * objects.
 */
private Class() {}
  • Object.getClass() 方法(對象.getClass())

    如果我們有一個類的對象,那麼我們可以通過 Object.getClass 方法獲得該類的 Class 對象。

    // String 對象的 getClass 方法
    Class clazz1 = "hello".getClass();
    // 數組對象的 getClass 方法
    Class clazz2 = (new byte[1024]).getClass();
    System.out.println(class2) // 會輸出 [B, [ 代表是數組, B 代表是 byte。即 byte 數組的類類型
    

    然而對於基本類型無法使用這種方法:

    boolean b;
    Class c = b.getClass();   // compile-time error
  • class 語法

    任何數據類型(包括基本數據類型)都有一個“靜態”的class屬性,若我們知道要獲取的類類型的名稱時,我們可以使用 class 語法獲取該類類型的對象

    // 類
    Class clazz = Integer.class;
    // 數組
    Class clazz2 = int [][].class;
    • 包裝類的 TYPE 靜態屬性

      對於基本類型和 void 都有對應的包裝類。在包裝類中有一個靜態屬性 TYPE,保存了該來的類類型。以 Integer 類爲例,其源碼中定義瞭如下的靜態屬性:

      /**
       * The {@code Class} instance representing the primitive type
 *
 * @since   JDK1.1
 */
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
```

生成 Class 類實例的方法:

```java
Class clazz1 = Integer.TYPE;
Class clazz2 = Void.TYPE;
```
  • Class.forName() 方法

    通過Class類的靜態方法:forName(String className)(常用)

    可以通過 Class 的 forName 方法獲取 Class 實例,其中類的名稱要寫類的完整路徑。

    該方法只能用於獲取引用類型的類類型對象。

    // 這種方式會使用當前的類的加載器加載,並且會將 Class 類實例初始化
    Class<?> clazz = Class.forName("java.lang.String");
    // 上面的調用方式等價於
    Class<?> clazz = Class.forName("java.lang.String", true, currentLoader);

    對於數組比較特殊:

    Class cDoubleArray = Class.forName("[D");    //相當於double[].class
    
    Class cStringArray = Class.forName("[[Ljava.lang.String;");   //相當於String[][].class

    使用該方法可能會拋出 ClassNotFoundException 異常,這個異常發生在類的加載階段,原因如下:

    • 類加載器在類路徑中沒有找到該類(檢查:查看所在加載的類以及其所依賴的包是否在類路徑下)
    • 該類已經被某個類加載器加載到 JVM 內存中,另外一個類加載器又嘗試從同一個包中加載

5.2 Student類獲取Class

package com.pangsir;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Student student = new Student();
        
        /*
         * JAVA反射--獲取Class對象的三種方式
         */
        
        // 通過對象名.getClass()方法獲取
        Class stuClass = student.getClass();
        System.out.println("stuClass is "+stuClass.getName());        
        
        // 通過類名.class方式獲得
        Class stuClass1 = Student.class;
        System.out.println("stuClass1 is "+stuClass1.getName());
        System.out.println(stuClass == stuClass1);
        
        // 通過Class.forName()方法獲得
        Class stuClass2 = Class.forName("com.pangsir.model.Student");
        System.out.println("stuClass2 is "+stuClass2);
        System.out.println(stuClass1 == stuClass2);
    }
}

Output:
stuClass is com.pangsir.model.Student
stuClass1 is com.pangsir.model.Student
true
stuClass2 is class com.pangsir.model.Student
true
代碼說明:在運行期間,一個類,只有一個Class對象產生。三種方式常用第三種,第一種對象都有了還要反射幹什麼。第二種需要導入類的包,依賴太強,不導包就拋編譯錯誤。一般都選第三種,一個字符串可以傳入也可寫在配置文件中等多種方法。

5.3 Member & AccessibleObject

在講 Field、Method、Constructor 之前,先說說 Member 和 AccessibleObject。Member 是一個接口,表示 Class 的成員,前面的三個類都是其實現類。
AccessibleObject 是 Field、Method、Constructor 三個類共同繼承的父類,它提供了將反射的對象標記爲在使用時取消默認 Java 語言訪問控制檢查的能力。通過 setAccessible 方法可以忽略訪問級別,從而訪問對應的內容。並且 AccessibleObject 實現了 AnnotatedElement 接口,提供了與獲取註解相關的能力。

5.4 獲取構造方法

Constructor 提供了有關類的構造方法的信息,以及對它的動態訪問的能力。

可以通過 Class 提供的方法,獲取 Constructor 對象,具體如下:

方法返回值 方法名稱 方法說明
Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定參數類型、具有public訪問權限的構造函數對象
Constructor<?>[] getConstructors() 返回所有具有public訪問權限的構造函數的Constructor對象數組
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回指定參數類型、所有聲明的(包括private)構造函數對象
Constructor<?>[] getDeclaredConstructor() 返回所有聲明的(包括private)構造函數對象
package com.pangsir.test;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {

        // 通過Class.forName()方法獲得Class對象
        Class stuClass = Class.forName("com.pangsir.model.Student");
        
        
        System.out.println("************返回所有public構造方法************");
        Constructor[] constructors = stuClass.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        /*
         * Output:
         * ************返回所有public構造方法************
         *    public com.pangsir.model.Student()
         *    public com.pangsir.model.Student(java.lang.String,int)
         */
    
        System.out.println("************所有的構造方法(包括:私有、受保護、默認、公有)***************");  
        Constructor[] constructors2 = stuClass.getDeclaredConstructors();
        for (Constructor constructor : constructors2) {
            System.out.println(constructor);
        }
        /*
         * Output:
         * ************所有的構造方法(包括:私有、受保護、默認、公有)***************
         *    private com.pangsir.model.Student(int)
         *    public com.pangsir.model.Student()
         *    protected com.pangsir.model.Student(char)
         *    public com.pangsir.model.Student(java.lang.String,int)
         *    com.pangsir.model.Student(java.lang.String)
         */
        
        
        Constructor constructor;

        System.out.println("************返回指定類型的 public構造器************");
        try {
            constructor = stuClass.getConstructor(String.class, int.class);
            System.out.println(constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        /*
         * Output:
         * ************返回指定類型的 public構造器************
         * public com.pangsir.model.Student(java.lang.String,int)
         * 
         * 如果指定參數的構造器是非public類型的 則拋出java.lang.NoSuchMethodException異常
         */
          
        System.out.println("************返回指定類型的構造器************");
        try { 
            constructor = stuClass.getDeclaredConstructor(int.class);
            System.out.println(constructor);            // char.class
        } catch (NoSuchMethodException e) {             // String.class, int.class
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        /*
         * Output:
         * ************返回指定類型的構造器************
         * public com.pangsir.model.Student(java.lang.String,int)
         * protected com.pangsir.model.Student(char)
         * private com.pangsir.model.Student(int)
         */
        System.out.println("********獲取私有構造方法,並調用**********");  
        con = clazz.getDeclaredConstructor(char.class);  
        System.out.println(con);  
        //調用構造方法  
        con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)  
        obj = con.newInstance('男');  
         /*
         * Output:
         * ************返回指定類型的構造器************
         * public com.pangsir.model.Student(char)  
         * 姓名:男 
         */
    }
}

5.5 獲取變量

Field 提供了有關類或接口的單個屬性的信息,以及對它的動態訪問的能力。

可以通過 Class 提供的方法,獲取 Field 對象,具體如下:

方法返回值 方法名稱 方法說明
Field getDeclaredField(String name) 獲取指定name名稱的(包含private修飾的)字段,不包括繼承的字段
Field[] getDeclaredField() 獲取Class對象所表示的類或接口的所有(包含private修飾的)字段,不包括繼承的字段
Field getField(String name) 獲取指定name名稱、具有public修飾的字段,包含繼承字段
Field[] getField() 獲取修飾符爲public的字段,包含繼承字段
public class Student {  
    public Student(){  
          
    }  
    //**********字段*************//  
    public String name;  
    protected int age;  
    char sex;  
    private String phoneNum;  
      
    @Override  
    public String toString() {  
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex  
                + ", phoneNum=" + phoneNum + "]";  
    }  
      
      
}

測試類

package com.pangsir.field;  
import java.lang.reflect.Field;  
/* 
 * 獲取成員變量並調用: 
 *  
 * 1.批量的 
 *      1).Field[] getFields():獲取所有的"公有字段" 
 *      2).Field[] getDeclaredFields():獲取所有字段,包括:私有、受保護、默認、公有; 
 * 2.獲取單個的: 
 *      1).public Field getField(String fieldName):獲取某個"公有的"字段; 
 *      2).public Field getDeclaredField(String fieldName):獲取某個字段(可以是私有的) 
 *  
 *   設置字段的值: 
 *      Field --> public void set(Object obj,Object value): 
 *                  參數說明: 
 *                  1.obj:要設置的字段所在的對象; 
 *                  2.value:要爲字段設置的值; 
 *  
 */  
public class Fields {  
  
        public static void main(String[] args) throws Exception {  
            //1.獲取Class對象  
            Class stuClass = Class.forName("com.pangsir.model.Student");  
            //2.獲取字段  
            System.out.println("************獲取所有公有的字段********************");  
            Field[] fieldArray = stuClass.getFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }
            /*
             * Output:
             * ***********獲取所有公有的字段********************  
             * public java.lang.String com.pangsir.model.Student.name  
             */
            
            System.out.println("******獲取所有的字段(包括私有、受保護、默認的)***********");  
            fieldArray = stuClass.getDeclaredFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }  
             /*
             * Output:
             ************獲取所有的字段(包括私有、受保護、默認的)********************  
             *  public java.lang.String com.pangsir.model.Student.name  
             *  protected int com.pangsir.model.Student.age  
             *  char com.pangsir.model.Student.sex  
             *  private java.lang.String com.pangsir.model.Student.phoneNum 
             */
            System.out.println("******獲取公有字段並調用*********");  
            Field f = stuClass.getField("name");  
            System.out.println(f);  
            //獲取一個對象  
            Object obj = stuClass.getConstructor().newInstance();//產生Student對象--》Student stu = new Student();  
            //爲字段設置值  
            f.set(obj, "劉德華");//爲Student對象中的name屬性賦值--》stu.name = "劉德華"  
            //驗證  
            Student stu = (Student)obj;  
            System.out.println("驗證姓名:" + stu.name);  
            /*
             * Output:
             *************獲取公有字段**並調用***********************************  
             * public java.lang.String com.pangsir.model.Student.name  
             * 驗證姓名:劉德華 
             */
              
            System.out.println("*****獲取私有字段****並調用***************");  
            f = stuClass.getDeclaredField("phoneNum");  
            System.out.println(f);  
            f.setAccessible(true);//暴力反射,解除私有限定  
            f.set(obj, "12345768901");  
            System.out.println("驗證電話:" + stu);  
            /*
             * Output:
             **************獲取私有字段****並調用********************************  
             * private java.lang.String fanshe.field.Student.phoneNum  
             * 驗證電話:Student [name=劉德華, age=0, sex= ,  phoneNum=12345768901]
             */
              
        }  
    }
代碼分析:調用字段時:需要傳遞兩個參數:
Object obj = stuClass.getConstructor().newInstance();

//產生Student對象--》Student stu = new Student();

//爲字段設置值
f.set(obj, "劉德華");

//爲Student對象中的name屬性賦值--》stu.name = "劉德華"
第一個參數:要傳入設置的對象,第二個參數:要傳入實參

5.6 獲取方法

Method 提供了有關類或接口的單個方法的信息,以及對它的動態訪問的能力。

可以通過 Class 提供的方法,獲取 Field 對象,具體如下:

方法返回值 方法名稱 方法說明
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個指定參數的Method對象,該對象反映此 Class 對象所表示的類或接口的指定已聲明方法。
Method[] getDeclaredMethod() 返回 Method 對象的一個數組,這些對象反映此 Class 對象表示的類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
Method getMethod(String name, Class<?>... parameterTypes) 返回一個 Method 對象,它反映此 Class 對象所表示的類或接口的指定公共成員方法。
Method[] getMethods() 返回一個包含某些 Method 對象的數組,這些對象反映此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的公共 member 方法。

Student類:

public class Student {  
    //**************成員方法***************//  
    public void show1(String s){  
        System.out.println("調用了:公有的,String參數的show1(): s = " + s);  
    }  
    protected void show2(){  
        System.out.println("調用了:受保護的,無參的show2()");  
    }  
    void show3(){  
        System.out.println("調用了:默認的,無參的show3()");  
    }  
    private String show4(int age){  
        System.out.println("調用了,私有的,並且有返回值的,int參數的show4(): age = " + age);  
        return "abcd";  
    }  
} 

測試類:

package com.pangsir.method;  
  
import java.lang.reflect.Method;  
  
/* 
 * 獲取成員方法並調用: 
 *  
 * 1.批量的: 
 *      public Method[] getMethods():獲取所有"公有方法";(包含了父類的方法也包含Object類) 
 *      public Method[] getDeclaredMethods():獲取所有的成員方法,包括私有的(不包括繼承的) 
 * 2.獲取單個的: 
 *      public Method getMethod(String name,Class<?>... parameterTypes): 
 *                  參數: 
 *                      name : 方法名; 
 *                      Class ... : 形參的Class類型對象 
 *      public Method getDeclaredMethod(String name,Class<?>... parameterTypes) 
 *  
 *   調用方法: 
 *      Method --> public Object invoke(Object obj,Object... args): 
 *                  參數說明: 
 *                  obj : 要調用方法的對象; 
 *                  args:調用方式時所傳遞的實參; 
 
): 
 */  
public class MethodClass {  
  
    public static void main(String[] args) throws Exception {  
        //1.獲取Class對象  
        Class stuClass = Class.forName("fanshe.method.Student");  
        //2.獲取所有公有方法  
        System.out.println("***************獲取所有的”公有“方法*******************");   
        Method[] methodArray = stuClass.getMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }
        /*
         * Output:
         ***************獲取所有的”公有“方法*******************  
            public void com.pangsir.model.Student.show1(java.lang.String)  
            public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException  
            public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException  
            public final void java.lang.Object.wait() throws java.lang.InterruptedException  
            public boolean java.lang.Object.equals(java.lang.Object)  
            public java.lang.String java.lang.Object.toString()  
            public native int java.lang.Object.hashCode()  
            public final native java.lang.Class java.lang.Object.getClass()  
            public final native void java.lang.Object.notify()  
            public final native void java.lang.Object.notifyAll() 
         */
        System.out.println("***************獲取所有的方法,包括私有的*******************");  
        methodArray = stuClass.getDeclaredMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }  
        /*
         * Output:
         ***************獲取所有的方法,包括私有的*******************  
        public void om.pangsir.model.Student.show1(java.lang.String)  
        private java.lang.String om.pangsir.model.Student.show4(int)  
        protected void om.pangsir.model.Student.show2()  
        void om.pangsir.model.Student.show3()  
         */
        System.out.println("***************獲取公有的show1()方法*******************");  
        Method m = stuClass.getMethod("show1", String.class);  
        System.out.println(m);  
        //實例化一個Student對象  
        Object obj = stuClass.getConstructor().newInstance();  
        m.invoke(obj, "劉德華");  
         /*
         * Output:
         ***************獲取公有的show1()方法*******************  
        public void om.pangsir.model.Student.show1(java.lang.String)  
        調用了:公有的,String參數的show1(): s = 劉德華 
         */
          
        System.out.println("***************獲取私有的show4()方法******************");  
        m = stuClass.getDeclaredMethod("show4", int.class);  
        System.out.println(m);  
        m.setAccessible(true);//解除私有限定  
        Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(獲取有反射),一個是實參  
        System.out.println("返回值:" + result);
        /*
         * Output:
         ***************獲取私有的show4()方法******************  
        private java.lang.String om.pangsir.model.Student.show4(int)  
        調用了,私有的,並且有返回值的,int參數的show4(): age = 20  
        返回值:abcd 
         */
          
          
    }  
}  
代碼分析:由此可見:
m = stuClass.getDeclaredMethod("show4", int.class);//調用制定方法(所有包括私有的),需要傳入兩個參數,第一個是調用的方法名稱,第二個是方法的形參類型,切記是類型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(獲取有反射),一個是實參
System.out.println("返回值:" + result);

5.7 反射main(主方法)

Student類

package com.pangsir.main;  
  
public class Student {  
  
    public static void main(String[] args) {  
        System.out.println("main方法執行了。。。");  
    }  
}

測試類:

package fanshe.main;  
  
import java.lang.reflect.Method;  
  
/** 
 * 獲取Student類的main方法、不要與當前的main方法搞混了 
 */  
public class Main {  
      
    public static void main(String[] args) {  
        try {  
            //1、獲取Student對象的字節碼  
            Class clazz = Class.forName("fanshe.main.Student");  
              
            //2、獲取main方法  
             Method methodMain = clazz.getMethod("main", String[].class);//第一個參數:方法名稱,第二個參數:方法形參的類型,  
            //3、調用main方法  
            // methodMain.invoke(null, new String[]{"a","b","c"});  
             //第一個參數,對象類型,因爲方法是static靜態的,所以爲null可以,第二個參數是String數組,這裏要注意在jdk1.4時是數組,jdk1.5之後是可變參數  
             //這裏拆的時候將  new String[]{"a","b","c"} 拆成3個對象。。。所以需要將它強轉。  
             methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一  
            // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二  
              
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
          
          
    }  
}

5.8 通過反射越過泛型檢查

泛型用在編譯期,編譯過後泛型擦除(消失掉)。所以是可以通過反射越過泛型檢查的

import java.lang.reflect.Method;  
import java.util.ArrayList;  
 
/* 
* 通過反射越過泛型檢查 
*  
* 例如:有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值? 
*/  
public class Demo {  
   public static void main(String[] args) throws Exception{  
       ArrayList<String> strList = new ArrayList<>();  
       strList.add("aaa");  
       strList.add("bbb");  
         
   //  strList.add(100);  
       //獲取ArrayList的Class對象,反向的調用add()方法,添加數據  
       Class listClass = strList.getClass(); //得到 strList 對象的字節碼 對象  
       //獲取add()方法  
       Method m = listClass.getMethod("add", Object.class);  
       //調用add()方法  
       m.invoke(strList, 100);  
         
       //遍歷集合  
       for(Object obj : strList){  
           System.out.println(obj);
           System.out.println(obj.getClass());
       }  
        /*
         *Output:
         * aaa
         * class java.lang.String
         * bbb
         * class java.lang.String
         * 100
         * class java.lang.Integer
         */
   }  
}

5.9 通過反射運行配置文件內容

Student類

public class Student {  
    public void show(){  
        System.out.println("is show()");  
    }  
} 

配置文件以txt文件爲例子(os.properties):

className = com.pangsir.model.Student  
methodName = show 

測試類:

import java.io.FileNotFoundException;  
import java.io.FileReader;  
import java.io.IOException;  
import java.lang.reflect.Method;  
import java.util.Properties;  
  
/* 
 * 我們利用反射和配置文件,可以使:應用程序更新時,對源碼無需進行任何修改 
 * 我們只需要將新類發送給客戶端,並修改配置文件即可 
 */  
public class Demo {  
    public static void main(String[] args) throws Exception {  
        //通過反射獲取Class對象  
        Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"  
        //2獲取show()方法  
        Method m = stuClass.getMethod(getValue("methodName"));//show  
        //3.調用show()方法  
        m.invoke(stuClass.getConstructor().newInstance());  
          
    }  
      
    //此方法接收一個key,在配置文件中獲取相應的value  
    public static String getValue(String key) throws IOException{  
        Properties pro = new Properties();//獲取配置文件的對象  
        FileReader in = new FileReader("os.properties");//獲取輸入流  
        pro.load(in);//將流加載到配置文件對象中  
        in.close();  
        return pro.getProperty(key);//返回根據key獲取的value值  
    }  
}  
  /*
  *Output:
  * is show()
  */

需求:
當我們升級這個系統時,不要Student類,而需要新寫一個Student2的類時,這時只需要更改pro.txt的文件內容就可以了。代碼就一點不用改動

Student2類:

public class Student2 {  
    public void show2(){  
        System.out.println("is show2()");  
    }  
} 

修改配置文件如下

className = com.pangsir.model.Student2  
methodName = show2  

5.10 利用ParameterizedType獲取java泛型參數類

//超類
package test;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

@SuppressWarnings("unchecked")
public class Person<T> {
    private Class<T> clazz;
    public Person() {
        // 使用反射技術得到T的真實類型
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); // 獲取當前new的對象的 泛型的父類 類型
        this.clazz = (Class<T>) pt.getActualTypeArguments()[0]; // 獲取第一個類型參數的真實類型
        System.out.println("clazz ---> " + clazz);
    }

}
//子類
package test;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Student extends Person<Student> {
}
//測試類
package test;

public class TestGetClass {

    /**
     * @param args
     */
    public static void main(String[] args) {

          Student student = new Student();
    }

}

6.反射的缺點

沒有任何一項技術是十全十美的,Java反射擁有強大功能的同時也帶來了一些副作用。

  • 性能開銷
    反射涉及類型動態解析,所以JVM無法對這些代碼進行優化。因此,反射操作的效率要比那些非反射操作低得多。我們應該避免在經常被執行的代碼或對性能要求很高的程序中使用反射。
  • 安全限制
    使用反射技術要求程序必須在一個沒有安全限制的環境中運行。如果一個程序必須在有安全限制的環境中運行,如Applet,那麼這就是個問題了。
  • 內部曝光
    由於反射允許代碼執行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用--代碼有功能上的錯誤,降低可移植性。反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行爲就有可能也隨着變化。
使用反射的一個原則:如果使用常規方法能夠實現,那麼就不要用反射。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章