Java反射詳解
原文位址:反射詳解
為什麼單獨又寫一篇呢。。。。因為到後面架構時,我發現自己有點基礎不牢,而且排版好像也有點問題(不知道為什麼從本地copy到部落格的md編輯器後有些地方給我變成代碼塊了)。。。是以單獨寫一篇詳細點的。
Class類
閱讀時請注意大小寫
在Java中用來表示運作時類型資訊的對應類就是Class類,Class類也是一個實實在在的類,存在于JDK的
java.lang
包中。java程式編譯生成
.class
檔案,
.class
裡面的内容就是你編譯的
.java
檔案的内容,不過是被編譯為位元組形式,給JVM檢視的。而用來描述
.class
檔案的類,就是
Class
類
Class類被建立後的對象就是Class對象。實際上在Java中每個類都有一個Class對象,每當我們編寫并且編譯一個新建立的類就會産生一個對應Class對象并且這個Class對象會被儲存在同名.class檔案裡,編譯後的位元組碼檔案儲存的就是Class對象,那為什麼需要這樣一個Class對象呢?是這樣的,當我們new一個新對象或者引用靜态成員變量時,Java虛拟機(JVM)中的類加載器子系統會将對應Class對象加載到JVM中,然後JVM再根據這個類型資訊相關的Class對象建立我們需要執行個體對象或者提供靜态變量的引用值。需要特别注意的是,手動編寫的每個class類,無論建立多少個執行個體對象,在JVM中都隻有一個Class對象,即在記憶體中每個類有且隻有一個相對應的Class對象。而且Class對象隻能由JVM來進行建立和銷毀。
另外需要知道的一點是
在JVM建立的Class類型的對象中
成員變量被封裝為Field對象
構造方法被封裝為Constructor對象
成員方法被封裝為Method對象
其實在這裡我初學時就有些疑惑,既然Class類是按照我自己定義的類一起建立的,而且跟我自己定義的類是一模一樣的。當我需要調用裡面的方法時,我可以使用自己定義的類來調用啊。隻不過要把方法定義為靜态方法罷了。
但是,我後面想到一個問題,這是面對的普通的方法,那麼在最初的構造方法,我要怎麼來調用呢。也就是說,我怎麼通過類來調用構造方法。
上面是我們構造一個A類對象時需要進行的一步。衆所周知,對象是能夠調用方法的,那麼我如何通過對象a來調用A類的構造方法?
調用不到,對吧。因為a本來就是由構造方法構造出來的,a根本就調用不到構造方法。而且,再進一步想,A這個類也調用不到自己的構造方法。
那麼,如果,我要在某些情況下,使用到A的構造方法,或者說,将A的構造方法作為一個函數的參數傳遞,那該怎麼辦?
這就需要用到下面将寫的反射技術,Class類也是反射的基礎
類的加載
第一次使用類的資訊時,
.class
位元組碼檔案會被加載到記憶體當中,存儲在方法區中。
JVM會為加載到方法區的
.class
檔案建立一個Class類型對象,該對象儲存在堆記憶體中,等同于堆記憶體中的Class類型的對象,指向了方法區中的
.class
檔案
一個類隻會被加載一次,是以Class類型的對象隻有一個,且任意類型都有對應的Class類型的對象
反射
那麼,什麼叫反射呢?我這裡用我的了解來講一下,反射就是通過Class類來擷取
.class
檔案的内容,因為
.class
的内容記載了類的所有屬性(成員變量,構造方法,成員方法等)。
當然,具體的定義。。。可以去檢視官方文檔
擷取Class對象
- 通過
類的成員方法java.lang.Object
public Class<?> getClass()
Person p = new Person(); Class c1 = p.getClass(); System.out.println(c1);
- 通過class屬性
Class<Person> c2 = Person.class; System.out.println(c2);
- 通過
類的靜态方法java.lang.Class
: 擷取方法參數指定類名對應的Class類型的對象(使用得最多,因為不需要知道具體的類的名字)public static Class<?> forName(String className)
Class<?> c3 = Class.forName("domain.Person"); System.out.println(c3);
總結一下
擷取class對象方式 | 作用 | 應用場景 |
---|---|---|
Class.forName(“全類名”) | 通過指定的字元串路徑擷取 | 多用于配置檔案,将類名定義在配置檔案中。讀取檔案,加載類 |
類名.class | 通過類名的屬性class擷取 | 多用于參數的傳遞 |
對象.getClass() | 通過對象的getClass()方法擷取 | 多用于對象的擷取位元組碼的方式 |
其他的一些方法
String getSimpleName(); 獲得簡單類名,隻是類名,沒有包
String getName(); 擷取完整類名,包含包名+類名
- T newInstance() ;建立此 Class 對象所表示的類的一個新執行個體。要求:類必須有public的無參數構造方法
擷取構造方法對象
在通過反射擷取構造方法之前,請一定保證存在了相應的Class對象。
-
: 擷取public修飾的所有的構造方法,每個構造方法被封裝成了Constructor類型的對象,被存儲數組中。Constructor類是專門用來描述構造方法的一個類public Constructor[] getConstructors()
Constructor<?>[] cons = c.getConstructors(); //周遊 for (Constructor<?> con : cons) { System.out.println(con); }
-
: 擷取指定參數類型的public修飾的構造方法。如果不存在對應的構造方法,則會抛出public Constructor<T> getConstructor(Class... parameterTypes)
java.lang.NoSuchMethodException
異常。
參數說明:
Class... parameterTypes
: 必須傳遞資料類型對應的Class對象,而且是可變參數,可以傳遞數組,參數清單,不傳遞(擷取空參)。
例如:擷取 構造方法
對應的Construtor對象,填寫的參數清單為public Person(String name, int age) { ...}
,String.class
int.class
//擷取public修飾的空參構造方法對象 Constructor<?> con = c.getConstructor(); System.out.println(con); //擷取public修飾的第一個參數是String類型,第二個參數是int類型的構造方法對象 Constructor<?> con2 = c.getConstructor(String.class, int.class); System.out.println(con2);
私有方法也能通過其他方法反射出來,但最好不要這麼做這樣的暴力反射,因為破壞了封裝性。如果有這樣的需求,查閱API文檔:
getDeclaredConstructor
等相關方法
使用構造方法對象構造對象
-
類的成員方法:java.lang.reflect.Constructor
:執行構造方法對象,建立一個具體的對象public Object newInstance(Object... params)
//擷取Class類型的對象 Class<?> c = Class.forName("domain.Person"); //通過Class類型的對象擷取空參構造方法對象 Constructor<?> con = c.getConstructor(); //執行構造方法對象,建立一個具體的對象 //執行的是空參構造方法對象,不需要傳遞參數,傳回的類型是Object,強轉為Person類 Person p = (Person)con.newInstance();
-
類的成員方法:java.lang.Class
執行空參構造方法,建立一個對象。public T newInstance()
//擷取Class類型的對象 Class<?> c = Class.forName("domain.Person"); //java.lang.Class類 成員方法: newInstance Person p = (Person)c.newInstance();
擷取成員方法對象
在擷取成員方法之前,需要擷取Class類型的對象
-
類的成員方法:java.lang.Class
:擷取所有public修飾的成員方法,包含繼承下來的方法,每個成員方法被封裝成了一個Method對象,存儲Method數組中。public Method[] getMethods()
類是用來描述成員方法的一個類。java.lang.reflect.Method
//擷取Class類型的對象 Class<?> c = Class.forName("domain.Person"); //擷取所有public修飾的成員方法,包含繼承下來的 Method[] ms = c.getMethods(); for (Method m : ms) { System.out.println(m); }
-
類的成員方法:java.lang.Class
擷取public修飾的指定方法名稱,指定參數類型對應的成員方法對象。參數清單中public Method getMethod(String name, Class... parameterTypes)
為方法名稱,String name
為必須傳遞資料類型對應的Class對象,而且是可變參數,可以傳遞數組,參數清單,不傳遞則擷取空參。Class... parameterTypes
//擷取Class類型的對象 Class<?> c = Class.forName("domain.Person"); //擷取public修飾的名稱為toString的沒有參數的方法 Method m1 = c.getMethod("toString"); System.out.println(m1); //擷取public修飾的名稱為setName的參數為String類型的方法 Method m2 = c.getMethod("setName", String.class); System.out.println(m2);
使用成員方法對象執行方法
使用
java.lang.reflect.Method
類的成員方法
public Object invoke(Object obj,Object... args)
來對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。參數
Object obj
為包含成員方法的對象,
Object... arg
執行方法時,該方法需要的具體的參數。傳回值在沒有傳回值的方法傳回一個null,有傳回值的方法則封裝為具體結果。
//1.擷取Class類型的對象
Class<?> c = Class.forName("domain.Person");
//成員方法的調用,需要包含成員方法的對象
//建立一個對象
Object obj = c.newInstance();
//2.擷取setName方法對象
Method setNameMethod = c.getMethod("setName", String.class);
//3.執行setName方法對象
Object result = setNameMethod.invoke(obj, "茶葉蛋");
System.out.println(result);//null
System.out.println(obj);// Person{name='茶葉蛋',age=0}
//2.擷取getName方法對象
Method getNameMethod = c.getMethod("getName");
//3.執行getName方法對象
result = getNameMethod.invoke(obj);
System.out.println(result);
類加載器
将class檔案(硬碟)加載到記憶體生成Class對象。
詳情參見
https://chayedan.site/index.php/archives/23/