Java反射

文章分類:Java編程

Java反射機制是 Java 語言被視爲準動態語言的關鍵性質。 J ava反射機制的核心就是允許在運行時通過 Java Reflection APIs 來取得已知名字的 class 類的相關信息,動態地生成此類,並調用其方法或修改其域(甚至是本身聲明爲 private 的域或方法)。

也許你使用Java 已經很長時間了,可是幾乎不會用到 Java 反射機制。你會嗤之以鼻地告訴我, Java 反射機制沒啥用。或許在 J2EE J2SE 等平臺, Java 反射機制沒啥用(具體我也不瞭解,不多做評論),但是在 Android 應用開發中,該機制會帶給你許多驚喜。

如果熟悉Android ,那麼你應該知道, Google 不知出於什麼原因,在系統源碼中一些類或方法中經常加上“ @hide ”註釋標記。它的作用是使這個方法或類在生成 SDK 時不可見,因此由此註釋的東西,你在編譯期是不可見的。這就出現了一些問題。一些明明可以訪問的東西編譯期卻無法訪問了!這使得你的程序有些本來可以完成的功能無法編譯通過。

當然,有一種辦法是自己去掉Android 源碼中的所有“ @hide ”標記,然後重新編譯一份自己的 SDK 。另一種辦法就是使用 Java 反射機制。當然,你還可以利用反射來訪問存在訪問限制的方法和修改其域。不過這種使用方法比較特殊,我們在文章的最後單獨討論。

從Class類說起

如果你使用Java ,那麼你應該知道 Java 中有一個 Class 類。 Class 類本身表示 Java 對象的類型,我們可以通過一個 Object (子)對象的 getClass 方法取得一個對象的類型,此函數返回的就是一個 Class 類。當然,獲得 Class 對象的方法有許多,但是沒有一種方法是通過 Class 的構造函數來生成 Class 對象的。

也許你從來沒有使用過Class 類,也許你曾以爲這是一個沒什麼用處的東西。不管你以前怎麼認爲, Class 類是整個 Java 反射機制的源頭。一切關於 Java 反射的故事,都從 Class 類開始。

因此,要想使用Java 反射,我們首先得到 Class 類的對象。下表列出了幾種得到 Class 類的方法,以供大家參考。

Class object 誕生管道

示例

運用getClass()

注:每個class 都有此函數

String str = "abc";

Class c1 = str.getClass();

運用

Class.getSuperclass()

Button b = new Button();

Class c1 = b.getClass();

Class c2 = c1.getSuperclass();

運用static method

Class.forName()

(最常被使用)

Class c1 = Class.forName ("java.lang.String");

Class c2 = Class.forName ("java.awt.Button");

Class c3 = Class.forName ("java.util.LinkedList$Entry");

Class c4 = Class.forName ("I");

Class c5 = Class.forName ("[I");

運用

.class 語法

Class c1 = String.class;

Class c2 = java.awt.Button.class;

Class c3 = Main.InnerClass.class;

Class c4 = int.class;

Class c5 = int[].class;

運用

primitive wrapper classes

的TYPE 語法

Class c1 = Boolean.TYPE;

Class c2 = Byte.TYPE;

Class c3 = Character.TYPE;

Class c4 = Short.TYPE;

Class c5 = Integer.TYPE;

Class c6 = Long.TYPE;

Class c7 = Float.TYPE;

Class c8 = Double.TYPE;

Class c9 = Void.TYPE;

獲取一些基本信息

在我們得到一個類的Class 類對象之後, Java 反射機制就可以大施拳腳了。首先讓我們來了解下如何獲取關於某一個類的一些基本信息。

Java class  內部模塊

Java class  內部模塊說明

相應之Reflection API,多半爲Class methods。

返回值類型(return type)

package

class隸屬哪個package

getPackage()

Package

import

class導入哪些classes

無直接對應之API。 可間接獲取。

 

modifier

class(或methods, fields)的屬性

int getModifiers()

Modifier.toString(int)

Modifier.isInterface(int)

int

String

bool

class name or interface name

class/interface

名稱getName()

String

type parameters

參數化類型的名稱

getTypeParameters()

TypeVariable <Class>[]

base class

base class(只可能一個)

getSuperClass()

Class

implemented interfaces

實現有哪些interfaces

getInterfaces()

Class[]

inner classes

內部classes

getDeclaredClasses()

Class[]

outer class

如果我們觀察的class  本身是inner classes,那麼相對它就會有個outer class。

getDeclaringClass()

Class

上表中,列出了一些Java class 內部信息的獲取方式。所採用的方法幾乎都是調用 Class 對象的成員方法(由此你就可以瞭解到 Class 類的用處了吧)。當然,表中所列出的信息並不是全部,有很大一部分沒有列出,你可以通過查閱 Java 文檔得到更全面的瞭解。另外,下面將重點介紹一下類的構造函數、域和成員方法的獲取方式。

類中最重要的三個信息

如果要對一個類的信息重要性進行排名的話,那麼這三個信息理應獲得前三的名次。它們分別是:構造函數、成員函數、成員變量。

也許你不同意我的排名,沒關係。對於Java 反射來說,這三個信息與之前介紹的基本信息相比較而言,有着本質的區別。那就是,之前的信息僅僅是隻讀的,而這三個信息可以在運行時被調用(構造函數和成員函數)或者被修改(成員變量)。所以,我想無可否認,至少站在 Java 反射機制的立場來說,這三者是最重要的信息。

下面,讓我們分別瞭解一下這三個重要信息的獲取方式。另外,我們將在後面的章節,詳細介紹他們的調用方式或者修改方式。

構造函數

如果我們將Java 對象視爲一個二進制的生活在內存中生命體的話,那麼構造函數無疑可以類比爲 Java 對象生命體的誕生過程。我們在構造函數調用時爲對象分配內存空間,初始化一些屬性,於是一個新的生命誕生了。

Java是純面向對象的語言, Java 中幾乎所有的一切都是類的對象,因此可想而知構造函數的重要性。

Java反射機制能夠得到構造函數信息實在應該是一件令人驚喜的事情。正因爲此,反射機制實質上才擁有了孵化生命的能力。換句話言之,我們可以通過反射機制,動態地創建新的對象。

獲取構造函數的方法有以下幾個:

Constructor getConstructor(Class[] params) 

Constructor[] getConstructors()

Constructor getDeclaredConstructor(Class[] params) 

Constructor[] getDeclaredConstructors()

我們有兩種方式對這四個函數分組。

首先可以由構造函數的確定性進行分類。我們知道,一個類實際上可以擁有很多個構造函數。那麼我們獲取的構造函數是哪個呢?我們可以根據構造函數的參數標籤對構造函數進行明確的區分,因此,如果我們在Java 反射時指定構造函數的參數,那麼我們就能確定地返回我們需要的那個“唯一”的構造函數。 getConstructor(Class[] params)  getDeclaredConstructor(Class[] params) 正是這種確定唯一性的方式。但是,如果我們不清楚每個構造函數的參數表,或者我們出於某種目的需要獲取所有的構造函數的信息,那麼我們就不需要明確指定參數表,而這時返回的就應該是構造函數數組,因爲構造函數很可能不止一個。 getConstructors() getDeclaredConstructors() 就是這種方式。

另外,我們還可以通過構造函數的訪問權限進行分類。在設計類的時候,我們往往有一些構造函數需要聲明爲“private ”、“ protect ”或者“ default ”,目的是爲了不讓外部的類調用此構造函數生成對象。於是,基於訪問權限的不同,我們可以將構造函數分爲 public 和非 public 兩種。

getConstructor(Class[] params)  getConstructors() 僅僅可以獲取到public 的構造函數,而 getDeclaredConstructor(Class[] params)  getDeclaredConstructors() 則能獲取所有(包括public 和非 public )的構造函數。

成員函數

如果構造函數類比爲對象的誕生過程的話,成員函數無疑可以類比爲對象的生命行爲過程。成員函數的調用執行纔是絕大多數對象存在的證據和意義。Java 反射機制允許獲取成員函數(或者說成員方法)的信息,也就是說,反射機制能夠幫助對象踐行生命意義。通俗地說, Java 反射能使對象完成其相應的功能。

和獲取構造函數的方法類似,獲取成員函數的方法有以下一些:

Method getMethod(String name, Class[] params)

Method[] getMethods()

Method getDeclaredMethod(String name, Class[] params) 

Method[] getDeclaredMethods() 

其中需要注意,String name 參數,需要寫入方法名。關於訪問權限和確定性的問題,和構造函數基本一致。

成員變量

成員變量,我們經常叫做一個對象的域。從內存的角度來說,構造函數和成員函數都僅僅是Java 對象的行爲或過程,而成員變量則是真正構成對象本身的細胞和血肉。簡單的說,就是成員變量佔用的空間之和幾乎就是對象佔用的所有內存空間。

獲取成員變量的方法與上面兩種方法類似,具體如下:

Field getField(String name)

Field[] getFields()

Field getDeclaredField(String name)

Field[] getDeclaredFields()

其中,String name 參數,需要寫入變量名。關於訪問權限和確定性的問題,與前面兩例基本一致。

讓動態真正動起來

在本文的一開始就說,Java 反射機制是 Java 語言被視爲準動態語言的關鍵性質。如果 Java 反射僅僅能夠得到 Java 類(或對象)運行時的信息,而不能改變其行爲和屬性,那麼它當然算不上“動態”。百度了一把何謂“動態語言”,解釋如下: 動態語言,是指程序在運行時可以改變其結構:新的函數可以被引進,已有的函數可以被刪除等在結構上的變化。 由此看來,Java 確實不能算作“動態語言”。但是和 C C++ 等純靜態語言相比, Java 語言允許使用者在運行時 加載、探知、使用編譯期間完全未知的classes ,所以我們說Java 是“準動態”語言。

細心地讀者 可能已經發現,在“類中最重要的三個信息”一節中,我們獲取的信息其實都是屬於類的,而不是對象。對於類的信息提取,其實並不涉及到對象內存,在程序編譯 完成的那一刻起,一切都已經是確定的了。因此,它並不能算“動態”。而如何對對象內存進行操作和訪問,纔是“動”的真正含義。

說了這麼多,關鍵還在於如何利用反射讓Java 真正動起來。下面我將按照創生、行爲與屬性三個方面來介紹反射機制是如何讓 Java 動的。

創生

不知是否本性使然,人類偏愛于思索起源與終結的話題。如果將程序類比於一個二進制的世界的話,那麼我們程序員則是這個世界的上帝。我們掌控着這個世界的起源和終結,熟悉世界中一草一木的屬性和所有生靈的習性。現在就讓我們開始創世紀吧!

在 “構造函數”那一小節中,我們列出了獲取構造函數的四種方法。這四種方法的返回值不知是否引起了各位的注意,那就是 Constructor 類。Constructor 就類比於女媧吹給泥人的那一口真氣,有了它,一個生命才真正出現。

Constructor 支持泛型,也就是它本身應該是 Constructor <T>。這個類有一個 public 成員函數, newInstance ( Object...  args) ,其中args 爲對應的參數。我們正是通過它來實現創生的過程。

行爲

行爲踐行着生命的意義,而衆多事物的行爲才得以構成整個世界的運轉。儘管道家的老子主張“無爲而治”,宣揚“ 聖人處無爲之事,行不言之教 ”,但那是因爲他本身就是 “無”的信仰者(“道”即“無”)。我們是唯物主義的信徒,所以必然要以“有”爲價值。那麼,在二進制的世界裏,我們如何調用Java 對象的行爲呢?

同樣,我們首先回顧“成員函數”小節中四種方法的返回值。對,那就是 Method 類。此類有一個public 成員函數, Object  invoke ( Object  receiver,  Object...  args) 。我們能很好理解此函數的第二個參數 args ,它代表這個方法所需要接收的參數。也許大家對第一個參數 receiver 還存在疑惑之處。這得從編程語言的發展歷程講起。

如果你關注幾種主流編程語言的起源,那麼你能有這樣的印象:C 從彙編而來, C++ C 而來,而 Java C/C++ 而來。有這樣一種印象就足夠了。從這樣的發展史我們可以看出, C++ Java 這兩種面向對象的編程語言都是從面向過程的 C 語言基礎上發展而來的。 OOP 是一種思想,它本身與編程語言無關。也就是說,我們用 C 也能寫出面向對象的程序,這也是 C++ Java 能夠以 C 爲基礎的根本所在。然而, C 無法實現類似 object.method() 這種表現形式,因爲 C 語言的結構體中並不支持函數定義。那麼我們用 C 實現 OOP 的時候,如何調用對象的方法呢?

本質上說,object.method() 這種調用方式是爲了表明具體 method() 的調用對象。而 invoke ( Object  receiver,  Object...  args) 的第一個參數正是指明調用對象。在C++ 中, object.method() 其實是有隱含參數的,那就是 object 對象的指針, method 原型的第一個參數其實是 this 指針,於是原型爲 method(void* this)

這樣一溯源,也許你更清楚了 Object  receiver 參數的含義,或許更迷糊了?不管怎樣,歷史就是如此,只不過我個人能力有限,說不清楚而已。

另外需要注意的是,如果某個方法是Java 類的靜態方法,那麼 Object  receiver 參數可以傳入null ,因爲靜態方法不從屬於對象。

屬性

同樣是人類,令狐沖和嶽不羣是如何被區分開的?那是因爲他們有着不同的屬性。同樣,同一個類可以生成多個對象,幾個同類型的對象之間如何區分?屬性起着決定性的作用。說到這裏,想起一個科幻故事。人體瞬移機,作用的根本原理就是人進入A 位置,被完全掃描之後,再在 B 位置重新組成它的細胞、血肉等屬性,從而完全創造出另一個一模一樣的人。當然,這是唯物主義的極致,它假設了只要一切物質相同,連記憶和靈魂都不會出現偏差,另外還存在倫理的問題,例如 A 位置的人會被銷燬掉嗎?

儘管這是一個科幻,但是在程序的世界裏,我們早已經用上了這類似幻想的技術。Java 中如何遠程傳遞一個對象?我們已經使用上了 Java 對象序列化的接口。不僅如此,利用序列化接口,我們甚至可以將一個生命保存起來,在需要的時候將它復活,這就是對象的持久化。不得不感慨,在程序的世界裏,我們就是上帝啊!

對象序列化如此強大,那麼它的本質是什麼呢?它的工作原理是怎樣的呢?簡單的說,對象序列化的本質就是屬性的序列化。原理就是我們崇尚的唯物主義,如果同一個類的兩個對象所有屬性值都完全相同,那麼我們可以認爲這是同一個對象。

說了這麼多,只是想說明一件事情,屬性對於對象而言是多麼的重要。那麼如何讀寫對象中屬性的值呢?回顧獲取屬性信息的方法返回值類型,那是 Field Field 類有兩個 public 方法,分別對應讀與寫,它們是:

Object  get(Object object)

v oid  set(Object object, Object value)

object參數需要傳入的對象,原理類似於成員方法需要指明對象一樣。如果是靜態屬性,此值同樣可以爲 null

發佈了62 篇原創文章 · 獲贊 2 · 訪問量 6005
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章