程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java反射入門

Java反射入門

編輯:JAVA綜合教程

Java反射入門


Java反射機制是指在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
用一句話總結就是反射可以實現在運行時可以知道任意一個類的屬性和方法。

1 理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎。

1.1 類是對象嗎?

思考一個問題:

在面向對象的世界裡,萬事萬物皆是對象,而對象是類的一個實例,那麼類是一個對象嗎?

答案是肯定的,類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)

1.2 如何表示Class的對象?

對於普通的對象,我們一般都會這樣創建和表示:

Code code1 = new Code(); 

上面說了,所有的類都是Class的對象,那麼如何表示呢,可不可以通過如下方式呢:

Class c = new Class();

但是我們查看Class的源碼時,是這樣寫的:

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

可以看到構造器是私有的,只有JVM可以創建Class的對象,因此不可以像普通類一樣new一個Class對象,雖然我們不能new一個Class對象,但是卻可以通過已有的類得到一個Class對象,共有三種方式,如下:

Class c1 = Code.class;
這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是通過獲取類的靜態成員變量class得到的 Class c2 = code1.getClass();
code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的 Class c3 = Class.forName("com.trigl.reflect.Code");
這種方法是Class類調用forName方法,通過一個類的全量限定名獲得

這裡,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這裡就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那麼Code和c1、c2、c3不就一樣了嗎?為什麼還叫Code什麼類類型?這裡不要糾結於它們是否相同,只要理解類類型是干什麼的就好了,顧名思義,類類型就是類的類型,也就是描述一個類是什麼,都有哪些東西,所以我們可以通過類類型知道一個類的屬性和方法,並且可以調用一個類的屬性和方法,這就是反射的基礎。

類類型是反射的基礎!
類類型是反射的基礎!
類類型是反射的基礎!

加粗+斜體+說三遍,這次應該記住了吧!

我們創建一個對象是通過new關鍵字的,但是知道了類類型以後,可以通過類類型的newInstance()方法創建某個類的對象實例以及調用方法,如下:

Code code = (Code)c1.newInstance(); // 需要有無參的構造方法
code.print();

那麼問題又來了,通過類類型創建類和通過new創建類有什麼不同呢?事實上類類型創建類是動態加載類,下面講一下什麼是動態加載類。

2 動態加載類

程序執行分為編譯器和運行期,編譯時刻加載一個類就稱為靜態加載類,運行時刻加載類稱為動態加載類,下面通過一個實例來講解:

現在拋開IDE工具,用記事本手寫類,這是為了方便我們利用cmd命令行手動編譯和運行一個類,從而更好理解動態加載類和靜態加載類的區別。

首先寫Office.java

class Office
{
    public static void main(String[] args)
    {
        if ("Word".equals(args[0]))
        {   
            // 靜態加載類,在編譯時加載
            Word w = new Word();
            w.start();
        }
        if ("Excel".equals(args[0]))
        {
            Excel e = new Excel();
            e.start();
        }
    }
}

然後進入cmd編譯Office.java,如圖:

這裡寫圖片描述

由於我們new的兩個類Word和Excel沒有編譯,所以報錯了,這就是靜態加載類的缺點,即必須在編譯時期就加載所有可能用到的類,而我們希望實現的是運行時用到哪個類就加載哪個類,下面通過動態加載類來加以改進。<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrjEvfjS1LrztcTA4KO6T2ZmaWNlQmV0dGVyLmphdmE8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> class OfficeBetter { public static void main(String[] args) { try { // 動態加載類,在運行時加載 Class c = Class.forName(args[0]); // 通過類類型,創建該類對象 OfficeAble oa = (OfficeAble)c.newInstance(); oa.start(); } catch (Exception e) { e.printStackTrace(); } } }

這裡動態加載了名為args[0]的類,而args[0]是在運行期輸入給main方法的第一個參數,如果你輸入Word那麼就會加載Word.java,這時候就需要在與OfficeBetter.java相同路徑下面創建Word.java;同理,如果你輸入Excel就需要加載Excel.java了。
其中OfficeAble是一個接口,上面動態加載的類如Word、Excel就是實現了OfficeAble,體現了多態的思想,這種動態加載和多態的思想可以使具體功能和代碼解耦,也就是隨時想添加某個功能(如Word和Excel都不要了,我要PPT)都能動態添加,而不改動原來的代碼。

其中OfficeAble接口如下:

interface OfficeAble
{
    public void start();
}

Word類:

class Word implements OfficeAble
{
    public void start()
    {
        System.out.println("word...starts...");
    }
}

按順序編譯、運行上面的類:

這裡寫圖片描述

3 獲取類的信息

一個類都有哪些東西呢?答案非常簡單:屬性和方法,這一節我們就學習如何通過類類型得到類的基本信息。

3.1 獲取類的成員方法信息

首先想一想成員方法中都包括什麼:返回值類型+方法名+參數類型
在Java中,類的成員方法也是一個對象,它是java.lang.reflect.Method的一個對象,所以我們通過java.lang.reflect.Method裡面封裝的方法來獲取這些信息.

3.1.1 單獨獲取某一個方法

獲取方法
單獨獲取某一個方法是通過Class類的以下方法獲得的:

public Method getDeclaredMethod(String name, Class
public void print(String a, int b) {
    // code body
}

現在知道A有一個對象a,那麼就可以通過:

Class c = a.getClass();
Method method = c.getDeclaredMethod("print", String.class, int.class);

來獲取這個方法。

如何調用獲取到的方法
那得到方法以後如何調用這個方法呢,也就是像調用普通對象方法那樣實現方法中的代碼呢?通過Method類的以下方法實現:

public Object invoke(Object obj, Object… args)

兩個參數分別是這個方法所屬的對象和這個方法需要的參數,還是用上面的例子來說明,通過:

method.invoke(a, "hello", 10);

和通過普通調用:

a.print("hello", 10);

效果完全一樣,這就是方法的反射,invoke()方法可以反過來將其對象作為參數來調用方法,完全跟正常情況反了過來。

3.1.2 獲取類中所有成員方法的信息

如果想要獲得類中所有而非單獨某個成員方法的信息,可以通過以下幾步來實現:

已知一個對象,獲取其類的類類型

Class c = obj.getClass();

獲取該類的所有方法,放在一個數組中

Method[] methods = c.getDeclaredMethods();

遍歷方法數組,獲得某個方法method

for (Method method : methods)

得到方法返回值類型的類類型

Class returnType = method.getReturnType();

得到方法返回值類型的名稱

String returnTypeName = returnType.getName();

得到方法的名稱

String methodName = method.getName();

得到所有參數類型的類類型數組

Class[] paramTypes = method.getParameterTypes();

遍歷參數類的類類型數組,得到某個參數的類類型class1

for (Class class1 : paramTypes)

得到該參數的類型名

String paramName = class1.getName();

3.2 獲取類的成員變量信息

想一想成員變量中都包括什麼:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field裡面封裝的方法來獲取這些信息。

3.2.1 單獨獲取某個成員變量

通過Class類的以下方法實現:

public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量 public Field getField(String name) // 獲得該類自所有的public成員變量,包括其父類變量

參數是成員變量的名字。
例如一個類A有如下成員變量:

private int n;

如果A有一個對象a,那麼就可以這樣得到其成員變量:

Class c = a.getClass();
Field field = c.getDeclaredField("n");

3.2.2 獲取所有的成員變量

同樣,如果想要獲取所有成員變量的信息,可以通過以下幾步:

已知一個對象,獲取其類的類類型

Class c = obj.getClass();

獲取該類的所有成員變量,放在一個數組中

Field[] fields = c.getDeclaredFields(); 

遍歷變量數組,獲得某個成員變量field

for (Field field : fields)

得到成員變量類型的類類型

Class fieldType = field.getType();

得到成員變量的類型名

String typeName = fieldType.getName();

得到成員變量的名稱

String fieldName = field.getName();

3.3 獲取類的構造函數

最後再想一想構造函數中都包括什麼:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor裡面封裝的方法來獲取這些信息。

3.3.1 單獨獲取某個構造函數

通過Class類的以下方法實現:

public Constructor getDeclaredConstructor(Class
public A(String a, int b) {
    // code body
}

那麼就可以通過:

Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

來獲取這個構造函數。

3.3.2 獲取所有的構造函數

可以通過以下步驟實現:

已知一個對象,獲取其類的類類型

Class c = obj.getClass();

獲取該類的所有構造函數,放在一個數組中

Constructor[] constructors = c.getDeclaredConstructors();

遍歷構造函數數組,獲得某個構造函數constructor

for (Constructor constructor : constructors)

得到構造函數參數類型的類類型數組

Class[] paramTypes = constructor.getParameterTypes();

遍歷參數類的類類型數組,得到某個參數的類類型class1

for (Class class1 : paramTypes)

得到該參數的類型名

String paramName = class1.getName();

4 通過反射了解集合泛型的本質

首先下結論:

Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。

下面通過一個實例來驗證:

package com.trigl.reflect;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 集合泛型的本質
 * @description
 * @author Trigl
 * @date 2016年4月2日上午2:54:11
 */
public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 沒有泛型 
        List list2 = new ArrayList(); // 有泛型


        /*
         * 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
         * 這個時候如果list2添加int類型會報錯
         */
        list2.add("hello");
//      list2.add(20); // 報錯!list2有泛型限制,只能添加String,添加int報錯
        System.out.println("list2的長度是:" + list2.size()); // 此時list2長度為1


        /*
         * 2.然後通過反射添加元素方式,在運行期動態加載類,首先得到list1和list2
         * 的類類型相同,然後再通過方法反射繞過編譯器來調用add方法,看能否插入int
         * 型的元素
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 結果:true,說明類類型完全相同

        // 驗證:我們可以通過方法的反射來給list2添加元素,這樣可以繞過編譯檢查
        try {
            Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
            m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
            System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增加了,並沒有泛型檢查
        } catch (Exception e) {
            e.printStackTrace();
        }

        /*
         * 綜上可以看出,在編譯器的時候,泛型會限制集合內元素類型保持一致,但是編譯器結束進入
         * 運行期以後,泛型就不再起作用了,即使是不同類型的元素也可以插入集合。
         */
    }
}   

輸出結果

list2的長度是:1
true
list2的長度是:2

OVER

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved