1. 程式人生 > >Java反射機制小結和實際操作

Java反射機制小結和實際操作

ring 裝載 工具類 處理 信息 實戰 ssm lB ssi

一、什麽是反射

1、編譯和運行

在了解什麽是Java反射機制前,先聊聊Java的編譯和運行。

還記得第一次使用記事本編寫第一個Java程序的場景嗎?通過命令窗口,使用javac命令編譯一個.java文件,生成一個.class文件,再使用java命令運行.class文件。

在編譯期,jvm會去檢查.java文件中使用的類、類的屬性和方法、類的對象等信息,如果都能找到對應的信息,則編譯通過,否則會編譯報錯或拋出異常信息。

在運行期,會根據.class文件信息,處理其中的代碼邏輯,如果遇到程序運行錯誤會拋出異常信息。

下面我們使用一個小例子來說明編譯和運行。新建一個Animal類,根據傳入的參數不同,創建不同的對象,執行不同的方法。

public class Animal{
    public static void main(String[] args) {
        if("Cat".equals(args[0])){
            Cat cat = new Cat();
            cat.eat();
        }
        if("Dog".equals(args[0])){
            Dog dog = new Dog();
            dog.eat();
        }   
    }
}

使用cmd命令編譯時,會報錯,找不到Cat類和Dog類。

E:\Java項目實戰>javac Animal.java
Animal.java:6: 錯誤: 找不到符號
                        Cat cat = new Cat();
                        ^
  符號:   類 Cat
  位置: 類 Animal
Animal.java:6: 錯誤: 找不到符號
                        Cat cat = new Cat();
                                      ^
  符號:   類 Cat
  位置: 類 Animal
Animal.java:11: 錯誤: 找不到符號
                        Dog dog = new Dog();
                        ^
  符號:   類 Dog
  位置: 類 Animal
Animal.java:11: 錯誤: 找不到符號
                        Dog dog = new Dog();
                                      ^
  符號:   類 Dog
  位置: 類 Animal
4 個錯誤

這時我們新建一個Cat類,編譯Cat類後,再次編譯Animal類,又報了兩個錯誤,找不到Dog類。

Animal.java:11: 錯誤: 找不到符號
                        Dog dog = new Dog();
                        ^
  符號:   類 Dog
  位置: 類 Animal
Animal.java:11: 錯誤: 找不到符號
                        Dog dog = new Dog();
                                      ^
  符號:   類 Dog
  位置: 類 Animal

如果我們想要使用貓類,而不管狗類是否存在,那該怎麽做?那就是繞過編譯期,在運行期動態執行貓類的方法。怎樣繞過編譯期呢?這就需要使用Java的反射機制了。

2、反射的概念

反射是指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法;並且對於任意一個對象,都能夠調用它的任意一個方法。這種動態獲取信息以及動態調用對象方法的功能,我們稱之為Java語言的反射機制

繼續上面遺留的問題,在不新建一個Dog.java文件時,怎樣調到Cat類的eat方法?

首先,我們要根據傳入的參數動態加載類,然後創建類的實例對象,最後執行類的方法。因為你無法確定傳入的參數是貓類還是狗類,就需要定義一個超類或接口,讓貓類繼承超類或實現接口,重載或重寫eat()方法,在創建類實例時,只用創建超類或接口的實例即可。

public class AnimalBetter {
    public static void main(String[] args) {
        try {
            Class c1 = Class.forName(args[0]);
            IAnimal animal = (IAnimal)c1.newInstance();
            animal.eat();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

這裏我們選用的是接口

public interface IAnimal {
    public void eat();  
}

貓類的實現

public class Cat implements IAnimal {
    public void eat() {
        System.out.println("Cat is eatting");
    }
}

二、反射的優缺點

使用反射,可以在代碼改動最小的情況下有效擴展功能。就像上面的小例子,如果哪天我們想使用老虎類進行eat動作,我們還需要改動AnimalBetter類的代碼嗎?答案是不需要,只需要新建一個老虎類,實現IAnimal的接口,重寫eat方法,就可以了,對其他既有代碼無需做任何改動。

使用反射,可以動態的創建類實例和使用類的方法,可以分析類文件,可以訪問類的私有屬性或方法,對於一些需要在運行時處理的事情都可以交給反射實現。

既然反射這麽好,為什麽不在項目中大範圍的使用?其實,反射也是有弊端的。反射包括了一些動態類型,jvm無法對這些代碼進行優化,實際操作效率低於非反射操作,在那些經常被執行的代碼或對性能要求較高的模塊盡量少使用反射。反射在程序有安全限制的環境中是無法使用的。反射可以訪問私有的屬性或方法,可能會破壞封裝性,產生意料之外的結果。

三、如何使用反射

1、使用過程

首先要獲取操作的類的Class對象,然後通過Class類中的方法獲取和查看該類中的方法和屬性,最後使用反射API來操作這些信息。

2、獲取要操作的類的Class對象

有三種方式獲取類的Class對象

(1)已經知道該類的類名

Class c2 = Animal.class;

實際上,任何一個類都有一個隱含的靜態成員變量class,並且任何一個類都是Class類的實例對象,這個對象我們稱為該類的類類型。

(2)已經知道該類的對象,通過getClass方法獲取

Animal animal = new Animal();
Class c3 = animal.getClass();

不管是上面的c2還是這裏的c3,都是Animal類的類類型,一個類只可能是Class類的一個實例對象或一個類類型。

(3)使用Class類中的forName()靜態方法

Class c4 = Class.forName("Animal"); //forName(String str)中參數為類的全路徑(包名+類名)

3、獲取類的屬性和方法

我們將其編寫為一個工具類,如下:

/**
 * 獲取類的屬性和方法
 * @author 小川94
 * @date 2018年6月24日
 */
public class ClassUtil {

    /**
     * 獲取類的成員變量信息
     * @param obj 要操作的類
     */
    public static void getClassFieldInfo(Object obj){
        // 獲取要操作的類的Class對象
        Class c = obj.getClass();
        // 獲取該類的成員變量信息
        // field類是java.lang.reflect包下的類,封裝了關於類成員變量的操作
        Field[] fs = c.getFields(); // 獲取的是所有的public的成員變量的信息
        Field[] fs2 = c.getDeclaredFields(); // 獲取的是該類自己聲明的成員變量的信息
        // 使用foreach遍歷成員變量
        for (Field field : fs2) {
            // 得到成員變量的類型的類類型
            Class fieldType = field.getType();
            // 獲取類類型的名稱
            String typeName = fieldType.getName();
            // 獲取成員變量的名稱
            String fieldName = field.getName();
            System.out.println(typeName + " " + fieldName);
        }
    }
    
    /**
     * 獲取類的方法信息
     * @param obj 要操作的類
     */
    public static void getClassMethodInfo(Object obj){
        // 獲取要操作的類的Class對象
        Class c = obj.getClass();
        // 獲取要操作的類的名稱
        System.out.println("類的名稱是:" + c.getName());
        // 獲取該類的方法信息
        // Method類是java.lang.reflect包下的類,封裝了關於類方法的操作
        Method[] ms = c.getMethods(); //獲取的是所有的public的函數,包括從父類繼承而來的
        Method[] ms2 = c.getDeclaredMethods(); // 獲取的是所有該類自己聲明的方法,不限訪問權限
        // 遍歷方法
        for (Method method : ms2) {
            // 得到方法的返回值類型的類類型
            Class returnType = method.getReturnType();
            System.out.print(returnType.getName() + " ");
            // 獲取方法名
            System.out.print(method.getName() + " (");
            // 獲取參數類型,得到的是參數列表的類型的類類型
            Class[] para = method.getParameterTypes();
            for (Class class1 : para) {
                System.out.print(class1.getName() + ",");
            }
            System.out.println(")");
        }
    }
    
    /**
     * 獲取類的構造方法
     * @param obj 要操作的類
     */
    public static void getClassConstructorMethodInfo(Object obj){
        // 獲取要操作的類的Class對象
        Class c = obj.getClass();
        // 獲取該類的構造方法信息
        // constructor類是java.lang.reflect包下的類,封裝了關於類的構造方法的操作
        Constructor[] cs = c.getConstructors(); // 獲取所有的public的構造函數
        Constructor[] cs2 = c.getDeclaredConstructors(); //得到所有的構造函數
        for (Constructor constructor : cs2) {
            System.out.print(constructor.getName()+"(");
            // 獲取構造函數的參數列表,得到的是參數列表的類類型
            Class[] paramTypes = constructor.getParameterTypes();
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName()+",");
            }
            System.out.println(")");
        }
    }
}

編寫測試類,如下:

public class Test {
    public static void main(String[] args) {
        System.out.println("----- 成員變量信息 -----");
        ClassUtil.getClassFieldInfo(new Integer(2));
        System.out.println("----- 構造方法信息 -----");
        ClassUtil.getClassConstructorMethodInfo(new Integer(2));
        System.out.println("----- 方法信息 -----");
        ClassUtil.getClassMethodInfo(new Integer(2));
    }
}

測試結果如下,大家可以將測試結果與Integer這個類做比較。

----- 成員變量信息 -----
int MIN_VALUE
int MAX_VALUE
java.lang.Class TYPE
[C digits
[C DigitTens
[C DigitOnes
[I sizeTable
int value
int SIZE
int BYTES
long serialVersionUID
----- 構造方法信息 -----
java.lang.Integer(int,)
java.lang.Integer(java.lang.String,)
----- 方法信息 -----
類的名稱是:java.lang.Integer
int numberOfLeadingZeros (int,)
int numberOfTrailingZeros (int,)
int bitCount (int,)
boolean equals (java.lang.Object,)
java.lang.String toString (int,int,)
java.lang.String toString ()
java.lang.String toString (int,)
int hashCode (int,)
int hashCode ()
int min (int,int,)
int max (int,int,)
int reverseBytes (int,)
int compareTo (java.lang.Integer,)
int compareTo (java.lang.Object,)
byte byteValue ()
short shortValue ()
int intValue ()
long longValue ()
float floatValue ()
double doubleValue ()
java.lang.Integer valueOf (java.lang.String,int,)
java.lang.Integer valueOf (int,)
java.lang.Integer valueOf (java.lang.String,)
java.lang.String toHexString (int,)
int compare (int,int,)
java.lang.Integer decode (java.lang.String,)
void getChars (int,int,[C,)
int reverse (int,)
int stringSize (int,)
int sum (int,int,)
int parseInt (java.lang.String,)
int parseInt (java.lang.String,int,)
long toUnsignedLong (int,)
int compareUnsigned (int,int,)
int divideUnsigned (int,int,)
int formatUnsignedInt (int,int,[C,int,int,)
java.lang.Integer getInteger (java.lang.String,java.lang.Integer,)
java.lang.Integer getInteger (java.lang.String,int,)
java.lang.Integer getInteger (java.lang.String,)
int highestOneBit (int,)
int lowestOneBit (int,)
int parseUnsignedInt (java.lang.String,)
int parseUnsignedInt (java.lang.String,int,)
int remainderUnsigned (int,int,)
int rotateLeft (int,int,)
int rotateRight (int,int,)
int signum (int,)
java.lang.String toBinaryString (int,)
java.lang.String toOctalString (int,)
java.lang.String toUnsignedString (int,)
java.lang.String toUnsignedString (int,int,)
java.lang.String toUnsignedString0 (int,int,)

4、使用反射API來操作類

public class MethodDemo {

    public static void main(String[] args) {
        // 第一種寫法
        try {
            // 獲取要操作的類的Class對象
            Class c = Class.forName("sessionone.Foo");
            // 獲取print方法
            Method m = c.getMethod("print", String.class);
            // 第二種寫法,new Class[]{}裏面可以接多個參數,也可以為空
            Method m2 = c.getMethod("print", new Class[]{String.class});
            // 第三種寫法,獲取該類自己聲明的方法
            Method m3 = c.getDeclaredMethod("print", String.class);
            // 執行方法
            m3.invoke(c.newInstance(), "小川94");
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第二種寫法
        Foo foo = new Foo();
        foo.print("小川94");
        // 第三種寫法
        try {
            Class c2 = Class.forName("sessionone.Foo");
            Foo foo2 = (Foo) c2.newInstance();
            foo2.print("小川94");
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 第四種寫法
        try {
            Foo foo3 = new Foo();
            Class c3 = Class.forName("sessionone.Foo");
            Method method = c3.getDeclaredMethod("print", String.class);
            method.invoke(foo3, "小川94");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Foo{
    public void print(String name){
        System.out.println("Hello," + name);
    }
}

四、反射的實際應用

1、在JDBC中的使用

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);

在裝載驅動時,使用了反射,在getConnection的源代碼中,使用了反射,整個JDBC都和反射有關聯。

2、在集合泛型中的使用

ArrayList list = new ArrayList();
ArrayList<String> list2 = new ArrayList<String>();
System.out.println(list.equals(list2)); //true
        
Class c = list.getClass();
System.out.println("list的類類型名稱:"+c.getName());
Class c2 = list2.getClass();
System.out.println("list2的類類型名稱:"+c2.getName());
System.out.println(c == c2); //true
        
// c1==c2結果返回true,說明編譯之後集合的泛型是去泛型化的
// Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯就無效了
// list2.add(20); 這樣寫,是會編譯報錯
try {
    System.out.println("list2中有"+list2.size()+"個元素");
    Method m = c2.getMethod("add", Object.class);
    m.invoke(list2, 20);
    System.out.println("list2中有"+list2.size()+"個元素");
    System.out.println(list2.toString()); //[20],已經將類型是整型的數添加到list2中
} catch (Exception e) {
    e.printStackTrace();
} 

集合中的泛型只是為了防止編譯錯誤,在運行時會將泛型全部清除。

3、在Servlet中的使用

在之前的servlet系列文章中,使用了反射來完成了一個Servlet處理多個請求,具體可以看那篇文章的詳細描述。

4、在框架中的使用

在主流框架中都有反射的影子,通過配置、註解完成一些需要靜態加載類的方法轉為使用反射來動態加載類,具體的會在講解到框架時細講。

Java反射機制小結和實際操作