Java反射機制

寫在前面:
什麼是java反射機制?我們又爲什麼要學它?
當程序運行時,允許改變程序結構或變量類型,這種語言稱爲動態語言。我們認爲java並不是動態語言,但是它卻有一個非常突出的動態相關機制,俗稱:反射。
IT行業裏這麼說,沒有反射也就沒有框架,現有的框架都是以反射爲基礎。在實際項目開發中,用的最多的是框架,填的最多的是類,反射這一概念就是將框架和類揉在一起的調和劑。所以,反射纔是接觸項目開發的敲門磚!


一、Class類
什麼是Class類?
在面向對象的世界裏,萬事萬物皆是對象。而在java語言中,static修飾的東西不是對象,但是它屬於類。普通的數據類型不是對象,例如:int a = 5;它不是面向對象,但是它有其包裝類 Integer 或者分裝類來彌補了它。除了以上兩種不是面向對象,其餘的包括類也有它的面向對象,類是java.lang.Class的實例化對象(注意Class是大寫)。也就是說:
Class A{}
當我創建了A類,那麼類A本身就是一個對象,誰的對象?java.lang.Class的實例對象。
那麼這個對象又該怎麼表示呢?
我們先看一下下面這段代碼:

1
2
3
4
public class Demo(){
F f=new F();
}
class F{}

這裏的F的實例化對象就可以用f表達出來。同理F類也是一個實例化對象,Class類的實例化對象。我們可以理解爲任何一個類都是Class類的實例化對象,這種實例化對象有三種表示方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo(){
F f=new F();
//第一種表達方式
Class c1=F.class;//這種表達方式同時也告訴了我們任何一個類都有一個隱含的靜態成員變量class
//第二種表達方式
Class c2=f.getClass();//這種表達方式在已知了該類的對象的情況下通過getClass方法獲取
//第三種表達方式
Class c3 = null;
try {
c3 = Class.forName("com.text.F");//類的全稱
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
class F{}

以上三種表達方式,c1,c2,c3都表示了F類的類類型,也就是官方解釋的Class Type。
那麼問題來了:

1
System.out.println(c1 == c2)?  or  System.out.println(c1 == c3)?

答案是肯定的,返回值爲ture。這表明不論c1 or c2 or c3都代表了F類的類類型,也就是說一個類只可能是Class類的一個實例對象。
理解了Class的概念,我們也可以通過類的類類型創建該類的對象實例,用c1 or c2 or c3的newInstance()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
Public class Demo1{
try {
Foo foo = (Foo)c1.newInstance();//foo就表示F類的實例化對象
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}}
class F{
void print(){
}
}

這裏需要注意的是,c1是F類的類類型,創建出來的就是F類的對象。如果a是A類的類類型,那麼創建出來的對象也應該與之對應,屬於A類的對象。

二、方法的反射
Class類有一個最簡單的方法,getName():

1
2
3
4
5
6
7
8
9
10
11
public class Demo2 {
public static void main(String[] args) {
Class c1 = int.class;//int 的類類型
Class c2 = String.class;//String類的類類型
Class c3 = void.class;
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());
System.out.println(c3.getName());
}
}

本的數據類型以及void關鍵字都是存在類類型的。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ClassUtil {
public static void printClassMethodMessage(Object obj){
//要獲取類的信息》》首先我們要獲取類的類類型
Class c = obj.getClass();
//我們知道Object類是一切類的父類,所以我們傳遞的是哪個子類的對象,c就是該子類的類類型。
//接下來我們要獲取類的名稱
System.out.println("類的名稱是:"+c.getName());
/*
*我們知道,萬事萬物都是對象,方法也是對象,是誰的對象呢?
* 在java裏面,方法是Method類的對象
*一個成員方法就是一個Method的對象,那麼Method就封裝了對這個成員
*方法的操作
*/
//如果我們要獲得所有的方法,可以用getMethods()方法,這個方法獲取的是所有的Public的函數,包括父類繼承而來的。如果我們要獲取所有該類自己聲明的方法,就可以用getDeclaredMethods()方法,這個方法是不問訪問權限的。
Method[] ms = c.getMethods();//c.getDeclaredMethods()
//接下來我們拿到這些方法之後幹什麼?我們就可以獲取這些方法的信息,比如方法的名字。
//首先我們要循環遍歷這些方法
for(int i = 0; i < ms.length;i++){
//然後可以得到方法的返回值類型的類類型
Class returnType = ms[i].getReturnType();
//得到方法的返回值類型的名字
System.out.print(returnType.getName()+" ");
//得到方法的名稱
System.out.print(ms[i].getName()+"(");
//獲取參數類型--->得到的是參數列表的類型的類類型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
}

總結思路:
通過方法的反射得到該類的名稱步驟:
1.獲取該類的類類型
2.通過類類型獲取類的方法(getMethods())
3.循環遍歷所獲取到的方法
4.通過這些方法的getReturnType()得到返回值類型的類類型,又通過該類類型得到返回值類型的名字
5.getName()得到方法的名稱,getParameterTypes()獲取這個方法裏面的參數類型的類類型。

三、成員變量的反射
首先我們需要認識到成員變量也是對象,是java.lang.reflect.Field類的對象,那麼也就是說Field類封裝了關於成員變量的操作。既然它封裝了成員變量,我們又該如何獲取這些成員變量呢?它有這麼一個方法:

1
2
3
4
5
public class ClassUtil {
public static void printFieldMessage(Object obj){
Class c = obj.getClass();
//Field[] fs = c.getFields();
}

這裏的getFields()方法獲取的所有的public的成員變量的信息。和方法的反射那裏public的成員變量,也有一個獲取所有自己聲明的成員變量的信息:
Field[] fs = c.getDeclaredFields();

我們得到它之後,可以進行遍歷(既然封裝了Field的信息,那麼我們就可以得到Field類型)

1
2
3
4
5
6
7
8
for (Field field : fs) {
//得到成員變量的類型的類類型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成員變量的名稱
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}

四、構造函數的反射
不論是方法的反射、成員變量的反射、構造函數的反射,我們只需要知道:要想獲取類的信息,首先得獲取類的類類型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 首先構造函數也是對象,是java.lang.Constructor類的對象
* 也就是java.lang. Constructor中封裝了構造函數的信息
* 和前面說到的一樣,它也有兩個方法:
* getConstructors()方法獲取所有的public的構造函數
* getDeclaredConstructors()方法得到所有的自己聲明的構造函數
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
//我們知道構造方法是沒有返回值類型的,但是我們可以:
System.out.print(constructor.getName()+"(");
//獲取構造函數的參數列表》》得到的是參數列表的類類型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

五、Class類的動態加載類
如何動態加載一個類呢?
首先我們需要區分什麼是動態加載?什麼是靜態加載?我們普遍認爲編譯時刻加載的類是靜態加載類,運行時刻加載的類是動態加載類。我們舉一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
Class A{
Public static void main(String[] args){
if("B".equal(args[0])){
B b=new B();
b.start();
}
if("C".equal(args[0])){
C c=new C();
C.start();
}
}
}

上面這一段代碼,當我們在用eclipse或者myeclipse的時候我們並不關心是否能夠通過編譯,當我們直接在cmd使用javac訪問A.java類的時候,就會拋出問題:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A.java:7:錯誤:找不到符號
B b=new B();
符號:  類B
位置: 類A
A.java:7:錯誤:找不到符號
B b=new B();
符號:  類B
位置: 類A
A.java:12:錯誤:找不到符號
C c=new C();
符號:  類C
位置: 類A
A.java:12:錯誤:找不到符號
C c=new C();
符號:  類C
位置: 類A
4個錯誤

或許我們理所當然的認爲這樣應該是錯,類B根本就不存在。但是如果我們多思考一下,就會發現B一定用嗎?不一定。C一定用嗎?也不一定。那麼好,現在我們就讓B類存在

1
2
3
4
5
Class B{
Public static void start(){
System.out.print("B...satrt");
}
}

現在我們就先 javac B.class,讓B類先開始編譯。然後在運行javac A.class。結果是:

1
2
3
4
5
6
7
8
9
A.java:12:錯誤:找不到符號
C c=new C();
符號:  類C
位置: 類A
A.java:12:錯誤:找不到符號
C c=new C();
符號:  類C
位置: 類A
2個錯誤

我們再想,這個程序有什麼問題。如果你說沒有什麼問題?C類本來就不存在啊!那麼問題來了B類已經存在了,假設我現在就想用B,我們這個程序用得了嗎?答案是肯定的,用不了。那用不了的原因是什麼?因爲我們這個程序是做的類的靜態加載,也就是說new創建對象是靜態加載類,在編譯時刻就需要加載所有的,可能使用到的類。所以不管你用不用這個類。
現在B類是存在的,但是我們這個程序仍然用不了,因爲會一直報C類有問題,所以B類我也用不了。那麼在實際應用當中,我們肯定需要如果B類存在,B類我就能用,當用C類的時候,你再告訴我錯了。如果說將來你有100個類,只要其中一個類出現問題,其它99個類你都用不了。所以這並不是我們想要的。
我們想要的就是我用那個類就加載那個類,也就是常說的運行時刻加載,動態加載類。如何實現動態加載類呢?我們可以建這麼一個類:

1
2
3
4
5
6
7
8
9
10
11
Class All{
Public static void start(){
try{
Class cl= Class.forName(args[0]);
//通過類類型,創建該類的對象
cl.newInstance();
}catch(Exception e){
e.printStackTrace();
}
}
}

前面我們在分析Class實例化對象的方式的時候,Class.forName(“類的全稱”),它不僅僅表示了類的類類型,還表示了動態加載類。當我們javac All.java的時候,它不會報任何錯誤,也就是說在編譯的時候是沒有錯誤的。只有當我們具體用某個類的時候,那個類不存在,它纔會報錯。
如果加載的類是B類,就需要:

1
B bt = (B) cl.newInstance();

萬一加載的是C類呢,可以改成

1
C ct = (C) cl.newInstance();

但是如果我想用很多的類或者加載很多的類,該怎麼辦?我們可以統一一個標準,不論C類還是B類或者其他的類,比如定義一個標準

1
Stand s = (Stand) cl.newInstance();

只要B類和C類都是這個標準的就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class All{
Public static void start(){
try{
Class cl= Class.forName(args[0]);
//通過類類型,創建該類的對象
Stand s = (Stand) cl.newInstance();
s.start();
}catch(Exception e){
e.printStackTrace();
}
}
}
interface Stand {
Public void start();
}

現在如果我想要用B類,我們只需要:

1
2
3
4
5
Class B implements Stand{
Public void start(){
System.out.print("B...satrt");
}
}

加載B類,編譯運行。

1
2
3
javac B.java
javac Stand.java
java Stand B

結果:

1
B...satrt

如果以後想用某一個類,不需要重新編譯,只需要實現這個標準的接口即可。只需要動態的加載新的東西就行了。
這就是動態加載類


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