談談Java反射機制

原文出處: localityjava

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


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

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。
那麼問題來了:spa

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()方法:code

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類的對象。對象

2、方法的反射
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關鍵字都是存在類類型的。接口

案例:ip

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()獲取這個方法裏面的參數類型的類類型。

3、成員變量的反射
首先咱們須要認識到成員變量也是對象,是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);
}

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

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( ")" );
}
}

5、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

若是之後想用某一個類,不須要從新編譯,只須要實現這個標準的接口便可。只須要動態的加載新的東西就好了。
這就是動態加載類

相關文章
相關標籤/搜索