12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路
文章目錄
- Java反射機制
- 理解Class類
- 獲取Class類實例
- 類的加載過程
- 類加載器ClassLoader
- 創建運行時類的對象
- 獲取運行時類的結構
- 調用運行時類的指定結構
- 動態代理
Java反射機制
Reflection是被視為動態語言的關鍵,反射機制允許程序在執行期借助于Reflection API取得任何類的內部信息,并能直接操作任意對象的內部屬性及方法。
加載完類之后,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射。
程序經過javac.exe命令以后,會生成一個或多個字節碼文件(.class結尾)。接著我們使用java.exe命令對某個字節碼文件進行解釋運行。相當于將某個字節碼文件
加載到內存中。此過程就稱為類的加載。加載到內存中的類,我們就稱為運行時類,此運行時類,就作為CLass的一個實例。換句話說,Class 的實例就對應著一個運行時類。
Java反射機制提供的功能:
反射相關的主要API:
理解Class類
在Object類中定義了以下的方法,此方法將被所有子類繼承:
public final Class getClass()以上的方法返回值的類型是一個Class類,此類是Java反射的源頭,實際上所謂反射從程序的運行結果來看也很好理解,即:可以通過對象反射求出所屬類。
對象照鏡子后可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了哪些接口。對于每個類而言,JRE 都為其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個結構(class/interface/enum/annotation/primitivetype/void/[])的有關信息。
①Class 本身也是一個類。
②Class 對象只能由系統建立對象。
③一個加載的類在 JVM 中只會有一個Class實例。
④一個Class對象對應的是一個加載到JVM中的一個.class文件。
⑤通過Class可以完整地得到一個類中的所有被加載的結構。
⑥Class類是Reflection的根源,針對任何想動態加載、運行的類,唯有先獲得相應的Class對象。
獲取Class類實例
加載到內存中的運行時類, 會緩存一 定的時間。在此時間之內,我們可以通過不同的方式來獲取此運行時類。
import com.atyeman.java.Person;/*** @Author: Yeman* @Date: 2021-09-29-22:39* @Description:*/ public class ClassTest {public static void main(String[] args) throws ClassNotFoundException {//方式一:調用運行時類的屬性:.classClass clazz1 = Person.class;System.out.println(clazz1);//方式二:通過運行時類的對象調用getClass()Person p2 = new Person();Class clazz2 = p2.getClass();System.out.println(clazz2);//方式三:調用Class的靜態方法:forName(String,classPath)Class clazz3 = Class.forName("com.atyeman.java.Person");System.out.println(clazz3);//方式四:使用類的加載器ClassLoaderClassLoader classLoader = ClassTest.class.getClassLoader();Class clazz4 = classLoader.loadClass("com.atyeman.java.Person");System.out.println(clazz4);//比較,true則為同一個運行時類,也證明了就加載了一個而已System.out.println(clazz1 == clazz2 && clazz2 == clazz3 && clazz3==clazz4);} }
哪些類型可以有Class對象?
①class:外部類,成員(成員內部類,靜態內部類),局部內部類,匿名內部類
②interface:接口
③[]:數組
④enum:枚舉
⑤annotation:注解@interface
⑥primitive type:基本數據類型
⑦void
類的加載過程
當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過如下三個步驟來對該類進行初始化。
加載:將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區的運行時數據結構,然后生成一個代表這個類的java.lang.Class對象,作為方法區中類數據的訪問入口(即引用地址)。所有需要訪問和使用類數據只能通過該Class對象。這個加載的過程需要類加載器參與。
鏈接:將Java類的二進制代碼合并到JVM的運行狀態之中的過程。
驗證:確保加載類信息符合JVM規范,例如:以cafe開頭,沒有安全方面的問題。
準備:正式為類變量(static)分配內存并設置類變量默認初始值的階段,這些內存都將在方法區中進行分配。
解析:虛擬機常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
初始化:執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并產生的(類構造器是構造類信息的,不是構造該類對象的構造器)。當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確加鎖和同步。
public class ClassLoadingTest {public static void main(String[] args) {System.out.println(A.m);} } class A {static {m = 300;}static int m = 100; } //第二步:鏈接結束后m=0 //第三步:初始化后,m的值由<clinit>()方法執行決定 // 這個A的類構造器<clinit>()方法由類變量的賦值和靜態代碼塊中的語句按照順序合并產生,相當于 // <clinit>(){ // m = 300; // m = 100; // }什么時候會發生類初始化?
1、類的主動引用(一定會發生類的初始化)
①當虛擬機啟動,先初始化main方法所在的類
②new一個類的對象
③調用類的靜態成員(除了final常量)和靜態方法
④使用java.lang.reflect包的方法對類進行反射調用
⑤當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
2、類的被動引用(不會發生類的初始化)
①當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化
②當通過子類引用父類的靜態變量,不會導致子類初始化
③通過數組定義類引用,不會觸發此類的初始化
④引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池中了)
類加載器ClassLoader
類加載器的作用:
①類加載:將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區的運行時數據結構,然后在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中類數據的訪問入口。
②類緩存:標準的JavaSe類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象。
類加載器作用是用來把類(class)裝載進內存的。JVM 規范定義了如下類型的類的加載器。
創建運行時類的對象
public class NewInstanceTest {public static void main(String[] args) throws InstantiationException, IllegalAccessException {Class<Person> pc = Person.class;Person person = pc.newInstance();System.out.println(person);} }.
調用newInstance ()方法,創建對應的運行時類的對象。內部調用了運行時類的空參的構造器。
調用newInstance()方法正常地創建運行時類的對象,要求:
①運行時類必須提供空參的構造器
②空參的構造器的訪問權限通常為public
在javabean中要求提供一個public的空參構造器。 原因:
①通過反射創建運行時類的對象
②子類繼承此運行時類時,默認調用super()時,保證父類有此構造器
獲取運行時類的結構
1、獲取屬性結構
public Field[] getFields():此Class及其父類、接口中聲明為public的屬性
public Field[] getDeclaredFields():此Class中聲明的所有屬性
Field方法中:
public int getModifiers():以整數形式返回此Field的修飾符,可以使用Modifier.toString(int modifier)還原
public Class<?> getType():得到Field的屬性類型
public String getName():返回Field的名稱
2、獲取方法結構
public Method[] getMethods():此Class及其父類、接口中聲明為public的方法
public Method[] getDeclaredMethods():此Class中聲明的所有方法
Method類中:
public Class<?> getReturnType():取得全部的返回值
public Class<?>[] getParameterTypes():取得全部的參數
public int getModifiers():取得修飾符
public Class<?>[] getExceptionTypes():取得異常信息
3、獲取構造器
public Constructor<T>[] getConstructors():此Class中聲明為public的構造器
public Constructor<T>[] getDeclaredConstructors():此Class中所有構造器
Constructor類中:
取得修飾符::public int getModifiers()
取得方法名稱: public String getName()
取得參數的類型:public Class<?>[] getParameterTypes()
4、其他
public Class<?>[] getInterfaces():此Class實現的所有接口
public Class<? Super T> getSuperclass():此Class的父類
Type getGenericSuperclass():此Class帶泛型的父類
get Annotation(Class<T> annotationClass):此結構的注解
ParameterizedType:此Class的泛型類型
Package getPackage():此Class所在的包
調用運行時類的指定結構
1、調用指定屬性
在反射機制中,可以直接通過Field類操作類中的屬性,通過Field類提供的set()和
get()方法就可以完成設置和取得屬性內容的操作。
public Field getField(String name):返回此Class對象表示的類或接口的指定的public的Field。(不通用,不常用)
public Field getDeclaredField(String name):返回此Class對象表示的類或接口的指定的Field。
在Field中:
public Object get(Object obj):取得指定對象obj上此Field的屬性內容
public void set(Object obj,Object value):設置指定對象obj上此Field的屬性內容
2、調用指定方法
通過反射,調用類中的方法,通過Method類完成。步驟:
①通過Class類的getMethod(String name,Class…parameterTypes)方法取得一個Method對象,并設置此方法操作時所需要的參數類型。
②使用Object invoke(Object obj, Object[] args)進行調用,并向方法中傳遞要設置的obj對象的參數信息。
3、調用指定構造器(不常用)
與屬性和方法類似。
動態代理
使用一個代理將對象包裝起來, 然后用該代理對象取代原始對象。任何對原始對象的調用都要通過代理。代理對象決定是否以及何時將方法調用轉到原始對象上。靜態代理特征是代理類和目標對象的類都是在編譯期間確定下來,不利于程序的擴展。同時,每一個代理類只能為一個接口服務,這樣一來程序開發中必然產生過多的代理。最好可以通過一個代理類完成全部的代理功能。
動態代理是指客戶通過代理類來調用其它對象的方法,并且是在程序運行時根據需要動態創建目標類的代理對象。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;/*** @Author: Yeman* @Date: 2021-10-03-19:33* @Description:*/interface Human {String getBelief();void eat(String food); }//某被代理類 class superMan implements Human {@Overridepublic String getBelief() {return "I believe the light!";}@Overridepublic void eat(String food) {System.out.println("吃" + food);} }//動態代理類 class ProxyFactory {//調用此方法,返回一個代理類的對象public static Object getProxyInstance(Object obj){ //obj即為被代理類MyInvocationHandle handle = new MyInvocationHandle();handle.bind(obj); //將被代理類告知return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handle);} } //用來實現調用代理類對象的方法時候調用對應的被代理類對象中的同名同參方法 class MyInvocationHandle implements InvocationHandler {private Object obj; //被代理類的對象public void bind(Object obj){this.obj = obj;}//當代理類的對象調用方法a時,就會自動地調用如下的方法//將被代理類要執行的方法a的功能聲明在invoke()中@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//method:即為代理類對象調用的方法,此方法也就作為了被代理類對象調用的方法//obj:被代理類的對象return method.invoke(obj,args);} }public class ProxyTest {public static void main(String[] args) {superMan superMan = new superMan();Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); //代理類對象String belief = proxyInstance.getBelief(); //當代理類對象調用方法時,調用的是被代理里中同名同參方法System.out.println(belief);proxyInstance.eat("火鍋");} }Proxy :專門完成代理的操作類,是所有動態代理類的父類。通過此類為一個或多個接口動態地生成實現類。
提供用于創建動態代理類和動態代理對象的靜態方法:
動態代理步驟:
總結
以上是生活随笔為你收集整理的12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 台式电脑辐射怎么办(电脑台式机辐射)
- 下一篇: 提高Java表达能力!不落伍一起掌握Ja