Java反射機制詳解

前言

Java反射機制很早的時候就有耳聞,期間也會去看看相關資料份汗,但是又很快會忘記绊起,所以精拟,寫一篇Blog來加深記憶與理解!虱歪!

Java反射的定義

Java反射機制是指在運行狀態(tài)(非編譯)中蜂绎,對于任意一個類,都能夠知道這個類的所有屬性和方法笋鄙;對于任意一個對象师枣,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制萧落。 用一句話總結(jié)就是反射可以實現(xiàn)在運行時可以知道任意一個類的屬性和方法践美。

Java反射機制的優(yōu)點與缺點

在說明反射的優(yōu)缺點前,我們需要知道一個概念:靜態(tài)編譯和動態(tài)編譯
靜態(tài)編譯:在編譯時確定類型找岖,綁定對象陨倡。
動態(tài)編譯:運行時確定類型,綁定對象许布。動態(tài)編譯最大限度發(fā)揮了java的靈活性兴革,體現(xiàn)了多態(tài)的應用,可以降低類之間的藕合性爹脾。

優(yōu)點

可以實現(xiàn)動態(tài)創(chuàng)建對象和編譯帖旨,體現(xiàn)出很大的靈活性,特別是在J2EE的開發(fā)中它的靈活性就表現(xiàn)的十分明顯灵妨。比如,一個大型的軟件落竹,不可能一次就把把它設計的很完美泌霍,當這個程序編譯后,發(fā)布了,當發(fā)現(xiàn)需要更新某些功能時朱转,我們不可能要用戶把以前的卸載蟹地,再重新安裝新的版本,假如這樣的話藤为,這個軟件肯定是沒有多少人用的怪与。采用靜態(tài)的話,需要把整個程序重新編譯一次才可以實現(xiàn)功能的更新缅疟,而采用反射機制的話分别,它就可以不用卸載,只需要在運行時才動態(tài)的創(chuàng)建和編譯存淫,就可以實現(xiàn)該功能耘斩。

缺點

對性能有影響。使用反射基本上是一種解釋操作桅咆,我們可以告訴JVM括授,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執(zhí)行相同的操作岩饼。

Class類和類類型

Java反射機制中最基礎的類就是Class類了荚虚。(There is a class named Class)
Class 類是什么類呢?
我們平時所說的類其實也是一個對象籍茧,都是Class這個類的對象版述。換句話說:(平時所說的)類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)硕糊。

如何創(chuàng)建Class對象

對于普通的對象院水,我們一般都會這樣創(chuàng)建和表示:

Code code1 = new Code(); 

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

Class c = new Class();

我們?nèi)タ纯丛创a中的Class類

  /*
     * Constructor. Only the Java Virtual Machine creates Class
     * objects.
     */
    private Class() {}

可以看到他的構(gòu)造函數(shù)是私有的而且只能由JVM來構(gòu)建對象,我們知道螟蝙,為了防止在其他地方構(gòu)建類恢恼,我們就會把構(gòu)造函數(shù)設為私有的。所以胰默,這里不能用常規(guī)的方法構(gòu)建Class的對象场斑,那總是有辦法的。下面我們就來看看有什么方法可以創(chuàng)建Class的對象呢牵署?Java給我們提供了三種方法:

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

這里青责,c1、c2、c3都是Class的對象脖隶,他們是完全一樣的扁耐,而且有個學名,叫做Code的類類型(class type)产阱。
這里就讓人奇怪了婉称,前面不是說Code是Class的對象嗎,而c1构蹬、c2王暗、c3也是Class的對象,那么Code和c1怎燥、c2瘫筐、c3不就一樣了嗎?為什么還叫Code什么類類型铐姚?這里不要糾結(jié)于它們是否相同策肝,只要理解類類型是干什么的就好了,顧名思義隐绵,類類型就是類的類型之众,也就是描述一個類是什么,都有哪些東西依许,所以我們可以通過類類型知道一個類的屬性和方法棺禾,并且可以調(diào)用一個類的屬性和方法,這就是反射的基礎峭跳。

來個demo吧膘婶!

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一種:Class c1 = Code.class;
        Class class1=ReflectDemo.class;
        System.out.println(class1.getName());

        //第二種:Class c2 = code1.getClass();
        ReflectDemo demo2= new ReflectDemo();
        Class c2 = demo2.getClass();
        System.out.println(c2.getName());

        //第三種:Class c3 = Class.forName("com.trigl.reflect.Code");
        Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
        System.out.println(class3.getName());
    }
}

執(zhí)行結(jié)果:

com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

反射相關操作

前面我們知道了怎么獲取Class,那么我們可以通過這個Class干什么呢蛀醉?
總結(jié)如下:

1.獲取與調(diào)用成員方法Method
2.獲取操作成員變量Field
3.獲取構(gòu)造函數(shù)Constructor

下面來分別介紹著幾種技能:

1.獲取與調(diào)用成員方法:

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

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法悬襟,不包括父類的
public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的

兩個參數(shù)分別是方法名和方法參數(shù)類的類類型列表拯刁。
例如類A有如下一個方法:

public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"歲");
    }

現(xiàn)在知道A有一個對象a脊岳,那么就可以通過:

Class c = Class.forName("com.tengj.reflect.Person");  //先生成class
Object o = c.newInstance();                           //newInstance可以初始化一個實例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10);                              //通過invoke調(diào)用該方法,參數(shù)第一個為實例對象垛玻,后面為具體參數(shù)值

完整代碼如下:

public class Person {
    private String name="Simon";
    private int age;
    private String msg="hello wrold";
 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;
  }

    public Person() {
    }

    private Person(String name) {
        this.name = name;
  System.out.println(name);
  }

    public void fun() {
        System.out.println("fun");
  }

    public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"歲");
  }
}

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Object o = c.newInstance();
            Method method = c.getMethod("fun", String.class, int.class);
            method.invoke(o, "tengj", 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

我叫tengj,今年10歲

通過Class類獲取Person類類型(上面提到過的)割捅,在通過newInstance()方法獲取Person的實例。這里需要借助Method的invoke()方法調(diào)用fun()方法帚桩。

2.獲取所有方法的數(shù)組:

Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到該類所有的方法亿驾,不包括父類的
或者:
Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的

然后循環(huán)這個數(shù)組就得到每個方法了:

for (Method method : methods)

完整代碼如下:
person類跟上面一樣账嚎,這里以及后面就不貼出來了颊乘,只貼關鍵代碼

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods){
                String  methodName= m.getName();
                System.out.println(methodName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

getName
setName
setAge
fun
fun
getAge

這里如果把c.getDeclaredMethods();改成c.getMethods();執(zhí)行結(jié)果如下参淹,多了很多方法醉锄,以為把Object里面的方法也打印出來了乏悄,因為Object是所有類的父類:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

2.獲取與調(diào)用成員變量:

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

單獨獲取某個成員變量,通過Class類的以下方法實現(xiàn):

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

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

private int n;

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

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

這樣我們就可以獲取他的成員變量了阻肿,當然,通過Field類我們還可以對變量進行操作沮尿。

    public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取成員變量
            Field field = c.getDeclaredField("msg"); //因為msg變量是private的丛塌,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//設置是否允許訪問,因為該變量是private的畜疾,所以要手動設置允許訪問赴邻,如果msg是public的就不需要這行了。
            Object msg = field.get(o);
            System.out.println(msg);
            field.set(o,"hello Android");
            Object msg1 = field.get(o);
            System.out.println(msg1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

執(zhí)行結(jié)果:

hello wrold
hello Android

同樣啡捶,如果想要獲取所有成員變量的信息姥敛,可以通過以下幾步
1.獲取所有成員變量的數(shù)組:

Field[] fields = c.getDeclaredFields();

2.遍歷變量數(shù)組,獲得某個成員變量field

for (Field field : fields)

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Field[] fields = c.getDeclaredFields();
            for(Field field :fields){
                System.out.println(field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

name
age
msg

獲取構(gòu)造函數(shù)

最后再想一想構(gòu)造函數(shù)中都包括什么:構(gòu)造函數(shù)參數(shù)
同上瞎暑,類的成構(gòu)造函數(shù)也是一個對象彤敛,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor里面封裝的方法來獲取這些信息了赌。

單獨獲取某個構(gòu)造函數(shù),通過Class類的以下方法實現(xiàn):

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  獲得該類所有的構(gòu)造器墨榄,不包括其父類的構(gòu)造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構(gòu)造器,包括父類

這個參數(shù)為構(gòu)造函數(shù)參數(shù)類的類類型列表揍拆。
例如類A有如下一個構(gòu)造函數(shù):

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

那么就可以通過:

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

來獲取這個構(gòu)造函數(shù)渠概。

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取構(gòu)造函數(shù)
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//設置是否允許訪問,因為該構(gòu)造器是private的嫂拴,所以要手動設置允許訪問播揪,如果構(gòu)造器是public的就不需要這行了。
            constructor.newInstance("tengj");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

tengj

注意:Class的newInstance方法筒狠,只能創(chuàng)建只包含無參數(shù)的構(gòu)造函數(shù)的類猪狈,如果某類只有帶參數(shù)的構(gòu)造函數(shù),那么就要使用另外一種方式:fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

獲取所有的構(gòu)造函數(shù)辩恼,可以通過以下步驟實現(xiàn):

1.獲取該類的所有構(gòu)造函數(shù)雇庙,放在一個數(shù)組中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構(gòu)造函數(shù)數(shù)組谓形,獲得某個構(gòu)造函數(shù)constructor:

for (Constructor constructor : constructors)

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
            Constructor[] constructors = c.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                System.out.println(constructor);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)

通過反射了解集合泛型的本質(zhì)

首先下結(jié)論:

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

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

public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 沒有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型


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


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

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

        /*
         * 綜上可以看出,在編譯器的時候傲隶,泛型會限制集合內(nèi)元素類型保持一致饺律,但是編譯器結(jié)束進入
         * 運行期以后,泛型就不再起作用了跺株,即使是不同類型的元素也可以插入集合复濒。
         */
    }
}

執(zhí)行結(jié)果:

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

思維導圖

最后給出一個別人整理的思維導圖:

反射思維導圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乒省,隨后出現(xiàn)的幾起案子巧颈,更是在濱河造成了極大的恐慌,老刑警劉巖袖扛,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砸泛,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛆封,警方通過查閱死者的電腦和手機唇礁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惨篱,“玉大人盏筐,你說我怎么就攤上這事≡一洌” “怎么了琢融?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵界牡,是天一觀的道長。 經(jīng)常有香客問我漾抬,道長宿亡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任奋蔚,我火速辦了婚禮她混,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泊碑。我一直安慰自己,他們只是感情好毯欣,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布馒过。 她就那樣靜靜地躺著,像睡著了一般酗钞。 火紅的嫁衣襯著肌膚如雪娇澎。 梳的紋絲不亂的頭發(fā)上重绷,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼厅篓。 笑死,一個胖子當著我的面吹牛剧董,可吹牛的內(nèi)容都是我干的赶熟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼米同,長吁一口氣:“原來是場噩夢啊……” “哼骇扇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起面粮,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤少孝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熬苍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稍走,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年柴底,在試婚紗的時候發(fā)現(xiàn)自己被綠了婿脸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡似枕,死狀恐怖盖淡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凿歼,我是刑警寧澤褪迟,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布冗恨,位于F島的核電站,受9級特大地震影響味赃,放射性物質(zhì)發(fā)生泄漏掀抹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一心俗、第九天 我趴在偏房一處隱蔽的房頂上張望傲武。 院中可真熱鬧,春花似錦城榛、人聲如沸揪利。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疟位。三九已至,卻和暖如春喘垂,著一層夾襖步出監(jiān)牢的瞬間甜刻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工正勒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留得院,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓章贞,卻偏偏與公主長得像祥绞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阱驾,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容