深入解析Java反射之基礎(chǔ)篇
前言
因?yàn)楸救俗罱I備Samsara框架的開(kāi)發(fā),而其中的IOC部分非常依靠反射,因此趁這個(gè)機(jī)會(huì)來(lái)總結(jié)一下關(guān)于Java反射的一些知識(shí)。本篇為基本篇,基于JDK 1.8。
一、回顧:什么是反射?
反射 (Reflection) 是 Java 的特征之一,它允許運(yùn)行中的 Java 程序獲取自身的信息,并且可以操作類(lèi)或?qū)ο蟮膬?nèi)部屬性。
Oracle 官方對(duì)反射的解釋是:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
簡(jiǎn)而言之,通過(guò)反射,我們可以在運(yùn)行時(shí)獲得程序或程序集中每一個(gè)類(lèi)型的成員和成員的信息。程序中一般的對(duì)象的類(lèi)型都是在編譯期就確定下來(lái)的,而 Java 反射機(jī)制可以動(dòng)態(tài)地創(chuàng)建對(duì)象并調(diào)用其屬性,這樣的對(duì)象的類(lèi)型在編譯期是未知的。所以我們可以通過(guò)反射機(jī)制直接創(chuàng)建對(duì)象,即使這個(gè)對(duì)象的類(lèi)型在編譯期是未知的。
反射的核心是 JVM 在運(yùn)行時(shí)才動(dòng)態(tài)加載類(lèi)或調(diào)用方法/訪問(wèn)屬性,它不需要事先(寫(xiě)代碼的時(shí)候或編譯期)知道運(yùn)行對(duì)象是誰(shuí)。
Java 反射主要提供以下功能:
- 在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類(lèi);
- 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類(lèi)的對(duì)象;
- 在運(yùn)行時(shí)判斷任意一個(gè)類(lèi)所具有的成員變量和方法(通過(guò)反射甚至可以調(diào)用private方法);
- 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法
重點(diǎn):是運(yùn)行時(shí)而不是編譯時(shí)
二、反射的主要用途
很多人都認(rèn)為反射在實(shí)際的 Java 開(kāi)發(fā)應(yīng)用中并不廣泛,其實(shí)不然。當(dāng)我們?cè)谑褂?IDE(如 Eclipse,IDEA)時(shí),當(dāng)我們輸入一個(gè)對(duì)象或類(lèi)并想調(diào)用它的屬性或方法時(shí),一按點(diǎn)號(hào),編譯器就會(huì)自動(dòng)列出它的屬性或方法,這里就會(huì)用到反射。
反射最重要的用途就是開(kāi)發(fā)各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過(guò) XML 文件配置 Bean),為了保證框架的通用性,它們可能需要根據(jù)配置文件加載不同的對(duì)象或類(lèi),調(diào)用不同的方法,這個(gè)時(shí)候就必須用到反射,運(yùn)行時(shí)動(dòng)態(tài)加載需要加載的對(duì)象。
舉一個(gè)例子,在運(yùn)用 Struts 2 框架的開(kāi)發(fā)中我們一般會(huì)在 struts.xml 里去配置 Action,比如:
<action name="login"
class="org.ScZyhSoft.test.action.SimpleLoginAction"
method="execute">
<result>/shop/shop-index.jsp</result>
<result name="error">login.jsp</result>
</action>
配置文件與 Action 建立了一種映射關(guān)系,當(dāng) View 層發(fā)出請(qǐng)求時(shí),請(qǐng)求會(huì)被 StrutsPrepareAndExecuteFilter 攔截,然后 StrutsPrepareAndExecuteFilter 會(huì)去動(dòng)態(tài)地創(chuàng)建 Action 實(shí)例。比如我們請(qǐng)求 login.action,那么 StrutsPrepareAndExecuteFilter就會(huì)去解析struts.xml文件,檢索action中name為login的Action,并根據(jù)class屬性創(chuàng)建SimpleLoginAction實(shí)例,并用invoke方法來(lái)調(diào)用execute方法,這個(gè)過(guò)程離不開(kāi)反射。
對(duì)與框架開(kāi)發(fā)人員來(lái)說(shuō),反射雖小但作用非常大,它是各種容器實(shí)現(xiàn)的核心。而對(duì)于一般的開(kāi)發(fā)者來(lái)說(shuō),不深入框架開(kāi)發(fā)則用反射用的就會(huì)少一點(diǎn),不過(guò)了解一下框架的底層機(jī)制有助于豐富自己的編程思想,也是很有益的。
三、反射的基本運(yùn)用
上面我們提到了反射可以用于判斷任意對(duì)象所屬的類(lèi),獲得 Class 對(duì)象,構(gòu)造任意一個(gè)對(duì)象以及調(diào)用一個(gè)對(duì)象。這里我們介紹一下基本反射功能的使用和實(shí)現(xiàn)(反射相關(guān)的類(lèi)一般都在 java.lang.relfect 包里)。
1、獲得 Class 對(duì)象
方法有三種:
(1) 使用 Class 類(lèi)的 forName 靜態(tài)方法:
public static Class<?> forName(String className) ``` 比如在 JDBC 開(kāi)發(fā)中常用此方法加載數(shù)據(jù)庫(kù)驅(qū)動(dòng): ```java Class.forName(driver);
(2)直接獲取某一個(gè)對(duì)象的 class,比如:
Class<?> klass = int.class; Class<?> classInt = Integer.TYPE;
(3)調(diào)用某個(gè)對(duì)象的 getClass() 方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
2、判斷是否為某個(gè)類(lèi)的實(shí)例
一般地,我們用 instanceof 關(guān)鍵字來(lái)判斷是否為某個(gè)類(lèi)的實(shí)例。同時(shí)我們也可以借助反射中 Class 對(duì)象的 isInstance() 方法來(lái)判斷是否為某個(gè)類(lèi)的實(shí)例,它是一個(gè) native 方法:
public native boolean isInstance(Object obj);
3、創(chuàng)建實(shí)例
通過(guò)反射來(lái)生成對(duì)象主要有兩種方式。
使用Class對(duì)象的newInstance()方法來(lái)創(chuàng)建Class對(duì)象對(duì)應(yīng)類(lèi)的實(shí)例。
Class<?> c = String.class; Object str = c.newInstance();
先通過(guò)Class對(duì)象獲取指定的Constructor對(duì)象,再調(diào)用Constructor對(duì)象的newInstance()方法來(lái)創(chuàng)建實(shí)例。這種方法可以用指定的構(gòu)造器構(gòu)造類(lèi)的實(shí)例。
//獲取String所對(duì)應(yīng)的Class對(duì)象
Class<?> c = String.class;
//獲取String類(lèi)帶一個(gè)String參數(shù)的構(gòu)造器
Constructor constructor = c.getConstructor(String.class);
//根據(jù)構(gòu)造器創(chuàng)建實(shí)例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
4、獲取方法
獲取某個(gè)Class對(duì)象的方法集合,主要有以下幾個(gè)方法:
getDeclaredMethods 方法返回類(lèi)或接口聲明的所有方法,包括公共、保護(hù)、默認(rèn)(包)訪問(wèn)和私有方法,但不包括繼承的方法。
public Method[] getDeclaredMethods() throws SecurityException
getMethods 方法返回某個(gè)類(lèi)的所有公用(public)方法,包括其繼承類(lèi)的公用方法。
public Method[] getMethods() throws SecurityException
getMethod 方法返回一個(gè)特定的方法,其中第一個(gè)參數(shù)為方法名稱,后面的參數(shù)為方法的參數(shù)對(duì)應(yīng)Class的對(duì)象。
public Method getMethod(String name, Class<?>... parameterTypes)
只是這樣描述的話可能難以理解,我們用例子來(lái)理解這三個(gè)方法:
package org.ScZyhSoft.common;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test1 {
public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c = methodClass.class;
Object object = c.newInstance();
Method[] methods = c.getMethods();
Method[] declaredMethods = c.getDeclaredMethods();
//獲取methodClass類(lèi)的add方法
Method method = c.getMethod("add", int.class, int.class);
//getMethods()方法獲取的所有方法
System.out.println("getMethods獲取的方法:");
for(Method m:methods)
System.out.println(m);
//getDeclaredMethods()方法獲取的所有方法
System.out.println("getDeclaredMethods獲取的方法:");
for(Method m:declaredMethods)
System.out.println(m);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}
程序運(yùn)行的結(jié)果如下:
getMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
可以看到,通過(guò) getMethods() 獲取的方法可以獲取到父類(lèi)的方法,比如 java.lang.Object 下定義的各個(gè)方法。
5、獲取構(gòu)造器信息
獲取類(lèi)構(gòu)造器的用法與上述獲取方法的用法類(lèi)似。主要是通過(guò)Class類(lèi)的getConstructor方法得到Constructor類(lèi)的一個(gè)實(shí)例,而Constructor類(lèi)有一個(gè)newInstance方法可以創(chuàng)建一個(gè)對(duì)象實(shí)例:
public T newInstance(Object ... initargs)
此方法可以根據(jù)傳入的參數(shù)來(lái)調(diào)用對(duì)應(yīng)的Constructor創(chuàng)建對(duì)象實(shí)例。
6、獲取類(lèi)的成員變量(字段)信息
主要是這幾個(gè)方法,在此不再贅述:
- getFiled:訪問(wèn)公有的成員變量
- getDeclaredField:所有已聲明的成員變量,但不能得到其父類(lèi)的成員變量
getFileds 和 getDeclaredFields 方法用法同上(參照 Method)。
7、調(diào)用方法
當(dāng)我們從類(lèi)中獲取了一個(gè)方法后,我們就可以用 invoke() 方法來(lái)調(diào)用這個(gè)方法。invoke 方法的原型為:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
下面是一個(gè)實(shí)例:
public class test1 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = methodClass.class;
//創(chuàng)建methodClass的實(shí)例
Object obj = klass.newInstance();
//獲取methodClass類(lèi)的add方法
Method method = klass.getMethod("add",int.class,int.class);
//調(diào)用method對(duì)應(yīng)的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
System.out.println(result);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}
關(guān)于 invoke 方法的詳解,后面我會(huì)專門(mén)寫(xiě)一篇文章來(lái)深入解析 invoke 的過(guò)程。
8、利用反射創(chuàng)建數(shù)組
數(shù)組在Java里是比較特殊的一種類(lèi)型,它可以賦值給一個(gè)Object Reference。下面我們看一看利用反射創(chuàng)建數(shù)組的例子:
public static void testArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);
//往數(shù)組里添加內(nèi)容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//獲取某一項(xiàng)的內(nèi)容
System.out.println(Array.get(array,3));
}
其中的Array類(lèi)為java.lang.reflect.Array類(lèi)。我們通過(guò)Array.newInstance()創(chuàng)建數(shù)組對(duì)象,它的原型是:
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
而 newArray 方法是一個(gè) native 方法,它在 HotSpot JVM 里的具體實(shí)現(xiàn)我們后邊再研究,這里先把源碼貼出來(lái):
private static native Object newArray(Class<?> componentType, int length)
throws NegativeArraySizeException;
源碼目錄:openjdk\hotspot\src\share\vm\runtime\reflection.cpp
arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
if (element_mirror == NULL) {
THROW_0(vmSymbols::java_lang_NullPointerException());
}
if (length < 0) {
THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
}
if (java_lang_Class::is_primitive(element_mirror)) {
Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
} else {
Klass* k = java_lang_Class::as_Klass(element_mirror);
if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
return oopFactory::new_objArray(k, length, THREAD);
}
}
另外,Array 類(lèi)的 set 和 get 方法都為 native 方法,在 HotSpot JVM 里分別對(duì)應(yīng) Reflection::array_set 和 Reflection::array_get 方法,這里就不詳細(xì)解析了。
四、反射的一些注意事項(xiàng)
由于反射會(huì)額外消耗一定的系統(tǒng)資源,因此如果不需要?jiǎng)討B(tài)地創(chuàng)建一個(gè)對(duì)象,那么就不需要用反射。
另外,反射調(diào)用方法時(shí)可以忽略權(quán)限檢查,因此可能會(huì)破壞封裝性而導(dǎo)致安全問(wèn)題。
到此這篇關(guān)于Java反射之基礎(chǔ)篇的文章就介紹到這了,更多相關(guān)Java反射之基礎(chǔ)篇內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java線程池隊(duì)列中的延遲隊(duì)列DelayQueue
這篇文章主要為大家詳細(xì)介紹了Java線程池隊(duì)列中的延遲隊(duì)列DelayQueue的相關(guān)資料,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12
SpringBoot實(shí)現(xiàn)無(wú)限級(jí)評(píng)論回復(fù)的項(xiàng)目實(shí)踐
本文主要介紹了SpringBoot實(shí)現(xiàn)無(wú)限級(jí)評(píng)論回復(fù)的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
springboot3.0整合mybatis-flex實(shí)現(xiàn)逆向工程的示例代碼
逆向工程先創(chuàng)建數(shù)據(jù)庫(kù)表,由框架負(fù)責(zé)根據(jù)數(shù)據(jù)庫(kù)表,自動(dòng)生成mybatis所要執(zhí)行的代碼,本文就來(lái)介紹一下springboot mybatis-flex逆向工程,感興趣的可以了解一下2024-06-06
一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)
這篇文章主要介紹了Spring中事務(wù)的兩種實(shí)現(xiàn)方式:聲明式事務(wù)、編程式事務(wù)以及他們的本質(zhì)。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作
這篇文章主要介紹了JAVA使用quartz添加定時(shí)任務(wù),并依賴注入對(duì)象操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Calcite使用SQL實(shí)現(xiàn)查詢excel內(nèi)容
因?yàn)閏alcite本身沒(méi)有excel的適配器,?所以本文將模仿calcite-file,?搞一個(gè)calcite-file-excel實(shí)現(xiàn)查詢excel內(nèi)容,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-01-01
Tomcat能起開(kāi),但是訪問(wèn)不進(jìn)8080首頁(yè)的問(wèn)題解決方案
這篇文章主要介紹了Tomcat能起開(kāi),但是訪問(wèn)不進(jìn)8080首頁(yè)的問(wèn)題解決方案的相關(guān)資料,需要的朋友可以參考下2016-10-10
Redis高并發(fā)場(chǎng)景防止庫(kù)存數(shù)量超賣(mài)少賣(mài)
商品超賣(mài)是銷(xiāo)售數(shù)量超過(guò)實(shí)際庫(kù)存的情況,常因庫(kù)存管理不當(dāng)引發(fā),傳統(tǒng)庫(kù)存管理在高并發(fā)環(huán)境下易出錯(cuò),可通過(guò)線程加鎖或使用Redis同步庫(kù)存狀態(tài)解決,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-09-09
Java數(shù)據(jù)結(jié)構(gòu)之對(duì)象比較詳解
這篇文章主要為大家詳細(xì)介紹了Java中對(duì)象的比較、集合框架中PriorityQueue的比較方式以及PriorityQueue的模擬實(shí)現(xiàn),感興趣的可以了解一下2022-07-07

