1. 程式人生 > 程式設計 >全面瞭解Java反射機制

全面瞭解Java反射機制

什麼是反射

反射 (Reflection) 是Java的特徵之一,它允許執行中的Java程式獲取自身的資訊,並且可以操作類或物件的內部屬性。

通俗的來講就是:通過反射機制,可以在執行時獲得程式或程式集中每一個型別的成員和成員的資訊。

注意這裡的重點是:執行時,而不是編譯時。我們常規情況下寫的物件型別都是在編譯期就確定下來的。而Java反射機制可以動態地建立物件並呼叫其屬性,這樣建立物件的方式便異常靈活了。

雖然通過反射可以動態的建立物件,增加了靈活性,但也不是什麼地方都可用,還要考慮效能、編碼量、安全、面向物件性等。

我們知道Java是面向物件的,如果通過反射機制去操作物件裡面的屬性或方法,一定程度上破壞了面向物件的特性。同時,通過反射機制還可以修改私有變數,也存在一定的安全性問題。

但這並不影響反射在實踐中的應用,幾乎各大框架多多少少都在使用Java反射機制。特別是主流的Spring框架。

功能及用途

Java反射主要提供以下功能:

在執行時判斷任意一個物件所屬的類;
在執行時構造任意一個類的物件;
在執行時判斷任意一個類所具有的成員變數和方法(通過反射甚至可以呼叫private方法);
在執行時呼叫任意一個物件的方法

反射最重要的用途之一就是開發各類通用框架。以Spring為例,當基於XML進行配置Bean時,我們通常寫如下程式碼:

<bean class="com.choupangxia.UserServiceImpl">
</bean>

Spring在啟動的時候便會利用反射機制去載入對應的UserServiceImpl類,然後進行例項化。如果不存在該類則會丟擲異常,通常異常中還會出現invoke方法呼叫的堆疊資訊。

當Spring基於註解去例項化物件時,同樣利用的是反射機制。下面通過一個簡單demo示例,演示一下如何通過反射獲得註解資訊:

static void initUser(User user) throws IllegalAccessException {

// 獲取User類中所有的屬性(getFields無法獲得private屬性)
Field[] fields = User.class.getDeclaredFields();

// 遍歷所有屬性
for (Field field : fields) {
// 如果屬性上有此註解,則進行賦值操作
if (field.isAnnotationPresent(InitSex.class)) {
InitSex init = field.getAnnotation(InitSex.class);
field.setAccessible(true);
// 設定屬性的性別值
field.set(user,init.sex().toString());
System.out.println("完成屬性值的修改,修改值為:" + init.sex().toString());
}
}
}

更多關於Java反射的例子我們就不多說了。上面的示例現在看不懂也沒關係,下面我們就來詳細介紹一下Java反射機制的具體使用。

簡單示例

我們通過一個簡單的示例對比,來了解一下Java反射機制。首先來看正常情況下建立物件並使用物件的示例:

User user = new User();
user.setUsername("公眾號:程式新視界");
user.setAge(3);

那麼,當基於反射機制來達到統一效果該怎麼做呢?看下面的具體實現:

@Test
public void testCreateReflect() throws ClassNotFoundException,NoSuchMethodException,IllegalAccessException,
InvocationTargetException,InstantiationException {
// 獲取User所對應的Class物件
Class clz = Class.forName("com.choupangxia.reflect.User");
// 獲取User類無引數的構造器
Constructor constructor = clz.getConstructor();
// 通過構造器建立User物件
User user = (User) constructor.newInstance();
user.setUsername("公眾號:程式新視界");
user.setAge(3);
System.out.println("username=" + user.getUsername());
System.out.println("age=" + user.getAge());
}

在上述過程中通過Class.forName獲得User所對應的Class物件,獲得構造器Constructor,通過構造器創建出來一個User物件,然後呼叫對應的方法。

當然,後面的步驟中也可以完全不出現User類,直接通過Class物件獲得對應的Method進行呼叫。示例如下:

Method setUsernameMethod = clz.getMethod("setUsername",String.class);
Method setAgeMethod = clz.getMethod("setAge",int.class);

setUsernameMethod.invoke(obj,"公眾號:程式新視界");
setAgeMethod.invoke(obj,3);

關於get方法也是如此操作,就不再贅述。

經過上面的例項我們已經能夠正常建立物件,並使用物件了。下面就看看反射常用的API,通過這些API我們可以實現更多的更復雜的功能。

反射常用API

獲取Class物件的三種方法

第一種方法:當你知道類的全路徑名時,可使用Class.forName靜態方法來獲得Class物件。上面的示例就是通過這種方法獲得:

Class clz = Class.forName("com.choupangxia.reflect.User");

第二種方法:通過“.class”獲得。前提條件是在編譯前就能夠拿到對應的類。

Class clz = User.class;

第三種:使用類物件的getClass()方法。

User user = new User();
Class clz = user.getClass();

建立物件的兩種方法

可以通過Class物件的newInstance()方法和通過Constructor 物件的newInstance()方法建立類的例項物件。

第一種:通過Class物件的newInstance()方法。

Class clz = User.class;
User user = (User) clz.newInstance();

第二種:通過Constructor物件的newInstance()方法。

Class clz = Class.forName("com.choupangxia.reflect.User");
Constructor constructor = clz.getConstructor();
User user = (User) constructor.newInstance();

其中第二種方法建立類物件可以選擇特定構造方法,而通過 Class物件則只能使用預設的無引數構造方法。

Class clz = User.class;
Constructor constructor = clz.getConstructor(String.class);
User user = (User) constructor.newInstance("公眾號:程式新視界");

獲取類屬性、方法、構造器
通過Class物件的getFields()方法獲取非私有屬性。

Field[] fields = clz.getFields();
for(Field field : fields){
System.out.println(field.getName());
}

上述例項中的User物件屬性都是private,無法直接通過上述方法獲取,可將其中一個屬性改為public,即可獲取。

通過Class物件的getDeclaredFields()方法獲取所有屬性。

Field[] fields = clz.getDeclaredFields();
for(Field field : fields){
System.out.println(field.getName());
}

執行列印結果:

username
age

當然針對屬性的其他值也是可以獲取的,針對私有屬性的修改需要先呼叫field.setAccessible(true)方法,然後再進行賦值。關於具體應用,回頭看我們最開始關於註解的例項中的使用。

獲取方法的示例如下:

Method[] methods = clz.getMethods();
for(Method method : methods){
System.out.println(method.getName());
}

列印結果:

setUsername
setAge
getUsername
getAge
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

可以看到,不僅獲取到了當前類的方法,還獲取到了該類父類Object類中定義的方法。

關於獲取構造器的方法上面已經講到了,就不再贅述。而上述的這些方法在Class中都有相應的過載的方法,可根據具體情況進行靈活使用。

利用反射建立陣列

最後,我們再看一個通過反射建立陣列的例項。

@Test
public void createArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,5);
// 向陣列新增內容
Array.set(array,"Hello");
Array.set(array,1,"公眾號");
Array.set(array,2,"程式新視界");
Array.set(array,3,"二師兄");
Array.set(array,4,"Java");
// 獲取陣列中指定位置的內容
System.out.println(Array.get(array,2));
}

小結

想必經過上述的學習,對Java反射機制有了更進一步的瞭解,在最開始我們已經說了反射機制也是有不足的。因此,如果可能儘量使用正統的寫法,但如果你在開發通用框架,則可考慮使用。

以上就是全面瞭解Java反射機制的詳細內容,更多關於Java反射機制的資料請關注我們其它相關文章!