Java ”框架 = 注解 + 反射 + 設計模式“ 之 反射詳解

每博一文案
無論幸福還是苦難,無論光榮還是屈辱,你都要自己遭遇與承受,
—————— 《平凡的世界》 孫少平
多少美好的東西消失和毀滅了,世界還像什么事也沒有發生,是的,生活在繼續著,
可是,生活中的每一個卻在不斷地失去自己最珍貴的東西,
生活永遠是美好的;人的痛苦卻時時在發生,
—————— 《平凡的世界》
我時常在想:第二天天一亮,一切都是新的,
—————— 《平凡的世界》
@
目錄- Java ”框架 = 注解 + 反射 + 設計模式“ 之 反射詳解
- 每博一文案
- 1. 反射的概念
- 2. 動態語言 vs 靜態語言
- 3. Class 類
- 4. Class 類中常用的方法
- 5. 獲取Class類的實體(三種方式)
- 5.1 方式一:
- 5.2 方式二:
- 5.3 方式三:重點 ???
- 6. 類加載器的理解
- 6.1 類的加載程序:
- 6.2 類加載的作用
- 6.2.1 雙親委派
- 6.3 ClassLoader 的使用
- 7. 反射機制查看類
- 8. 反射機制呼叫構造器
- 8.1 通過反射呼叫私有構造器 ??????
- 9. 反射機制獲取類中的屬性
- 9.1 通過反射機制呼叫私有的屬性(set/get) ??????
- 10. 反射機制獲取類中的方法
- 10.1 通過反射呼叫類中私有的方法
- 11. 反射讀取檔案資訊
- 12. 反射機制獲取到類中的注解資訊
- 13. 23種設計模式:動態代理模式
- 14. 總結:
- 15. 最后:
1. 反射的概念
- Reflection (反射)是被視為 動態語言的關鍵 ,反射機制允許程式在執行期借助 Reflection API 取得任何類的內部資訊,并能直接操作任意物件內部屬性以及方法,
- 反射機制有什么用 ???
- 通過 Java語言中的反射機制可以操作位元組碼檔案,優點類似于黑客,(可以讀和修改位元組碼檔案)
- 在Java中加載完類之后,會在堆記憶體的方法區中就產生一個 Class 型別的物件(一個類只有一個 Class 物件(因為類在記憶體當中只會生產一份,一個類對應的
.class位元組碼檔案,所有物件共用,這僅用的一份)),這個物件就包含了完整的類的結構資訊,我們可以通過這個物件看到類的結構,這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為 : 反射,

- 反射機制的相關類在 **
java.lang.reflect.***包下- java.lang.Class : 代表整個位元組碼,代表一個型別,代表整個類,
- java.lang.reflect.Constructor : 代表位元組碼中的方法位元組碼,代表類中的方法,
- java.lang.reflect.Field : 代表位元組碼中的構造方法位元組碼,代表類中的構造方法,
- java.lang.reflect.Field : 代表位元組碼中的屬性位元組碼,代表類中的成員變數(靜態變數 + 實體變數),
public class User {
// 這個是時屬性 Field
int no;
public User(){
}
// 這個是構造器 Constructor
public User (int no) {
}
// 這個是方法 Method()
public void setNo(int no) {
}
public int getNo() {
return no;
}
}
2. 動態語言 vs 靜態語言
- 動態語言:
動態語言: 是一類在運行時可以改變其結構的語言:例如:新的函式,物件,甚至代碼可以被引進,已有的函式可以被洗掉或是其他結構上的變化,通俗點說就是在運行時代碼可以根據某些條件自身結構 ,
主要動態語言: Object-C,C#,JavaScript,PHP,Python,Erlang
- 靜態語言:
與動態語言相應的,運行時結構不可變的語言就是靜態語言,如:Java,C,C++
Java不是動態語言,但Java可以稱之為 ”準動態語言“,即 Java有一定的動態性,我們可以利用反射機制,位元組碼操作獲得類似動態語言的特性,Java的動態性讓編程的時候更加靈活!
3. Class 類



除了int等基本型別外,Java的其他型別全部都是class(包括interface),例如:
StringObjectRunnableExceptioninterface介面enum列舉annotaion注解void[]陣列class 外部類,成員(成員內部類,靜態內部類),區域內部類,匿名內部類- ......

- Class 本身也是一個類,注意是 大寫的 C ,小寫的表示的就成了 class 關鍵字了,
- Class 物件只能由系統建立物件,
- 一個加載的類 在 JVM 中只會有一個 Class 實體,因為 一個 Class 物件對應的是一個加載到 JVM 中的一個
.class位元組碼檔案,一個類只會加載一份對應的.class位元組碼檔案,所有這個類的物件共用, - 每個類的的實體都會記得自己是由哪個 Class 實體所生成的,
- 通過 Class 可以完整地得到一個類中的所有被加載的結構,
- Class 類是 Reflection 的根源,針對任何你想要:動態加載,運行的類,唯有先獲得相應的 Class 物件為前提,
4. Class 類中常用的方法
Class 這里類是沒有公開的構造器的,所以是無法通過 new 的方式創建該 Class 物件的,
| 方法名 | 作用 |
|---|---|
| public static Class<?> forName(String className) throws ClassNotFoundException | 回傳指定類名 name(全類名)的 Class 物件 |
| public T newInstance() throws InstantiationException, IllegalAccessException | 呼叫無參構造器,回傳該 Class 中對應的類的物件實體,等同于 new 物件 注意該方法并不會創建對應類中的實體物件,但是會呼叫該類中的靜態代碼塊 |
| public String getName() | 回傳此 Class 物件所表示的物體(類,介面,陣列類,基本資料型別或 void) 的名稱 |
| public Class<? super T> getSuperclass() | 回傳當前Class 物件的父類的 Class 物件 |
| public Class<?>[] getInterfaces() | 獲取當前Class 物件的介面 |
| public ClassLoader getClassLoader() | 回傳該類的類加載器 |
| public Constructor<?>[] getConstructors() throws SecurityException | 回傳該類中的所有的構造器存盤到 (Constructor[ ])物件的陣列中 |
| public Method[] getDeclaredMethods() throws SecurityException | 回傳該類中的所有方法存盤到Method[ ]物件陣列 |
| public Field[] getDeclaredFields() throws SecurityException | 回傳該類中所有的屬性存盤到 Field[ ]物件陣列中 |
5. 獲取Class類的實體(三種方式)
5.1 方式一:
前提: 若已知具體的類,通過類的 class屬性獲取,該方法最為安全可靠, 程式性能最高,任何型別(除了基本資料型別不行,但是可以用對應的包裝類代替)都可以 .class
舉例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
// <User> 泛型
Class<User> userClass = User.class;
System.out.println(userClass);
// <String> 泛型
Class<String> stringClass = String.class;
System.out.println(stringClass);
//這里自動裝箱了
Class integerClass = int.class;
System.out.println(integerClass);
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

5.2 方式二:
前提: 已知某個類的實體,呼叫該實體的 getClass()方法獲取 Class物件,
舉例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
User user = new User();
// <? extends User >上界通配符
Class<? extends User> aClass = user.getClass();
System.out.println(aClass);
String s = new String();
Class<? extends String> aClass2 = s.getClass();
System.out.println(aClass2);
Integer integer = new Integer(123);
Class aClass3 = integer.getClass();
System.out.println(aClass3);
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

5.3 方式三:重點 ???
前提: 已知一個類的全類名,且該類在類路徑下,可通過 Class類的靜態方 法forName()獲取,可能拋出ClassNotFoundException該類不存在例外
全類名 : 指的是將包含一個類的包名,比如:java.lang.String ,blogs.blog11.User 這樣含有指定包下的類名,
這里使用 fonName(全類名) 的原因是:不同的包下,會存在同名的類名,這樣Java程式無法確定你需要的是哪個包下的類了,所以需要指定包下的類,
可以使用 IDEA 工具,快速復制獲取到一個類的:全類名,如下:


舉例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("blogs.blog11.User");
System.out.println(clazz);
Class<?> clazz2 = Class.forName("java.lang.String");
System.out.println(clazz2);
Class clazz3 = Class.forName("java.lang.Integer");
System.out.println(clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

這第三種方式還又一個好處: 會自動呼叫該類中的靜態代碼塊,因為靜態代碼塊,是和類一起加載到記憶體當中的,當類加載到記憶體時,該靜態代碼就會自動執行其中的陳述句,僅僅只會執行一次,因為類也僅僅只會加載到記憶體一次,而 Class.forName() 類/靜態方法,就是加載對應類的,該方法并不會創建該類的實體化物件,而是將該類加載到記憶體當中,
如果你只想讓一個類的 "靜態代碼塊" 執行的話,你可以怎么做?
class.forName("該類的類名");
這樣類就加載,類加載的時候,靜態代碼塊執行!!!應用實體:JDBC 中加載對應資料庫的驅動,執行其中的靜態代碼塊,
舉例: 如下,我們可以看到,其中的構造器并沒有被呼叫(也就沒有創建實體物件了,),但是靜態代碼塊執行了,
package blogs.blog11;
/**
* 獲取Class 物件的三種方式
*/
public class ReflectTest01 {
public static void main(String[] args) {
// 會執行blogs.blog11.User 類中的靜態代碼塊,而不會創建器實體化物件(不會呼叫該類的構造器)
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz);
}
}
class User {
String name;
public User() {
System.out.println("User 的構造器");
}
// 靜態代碼塊
static {
System.out.println("User 類中的 靜態代表塊");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

注意:每個類對應的.class 位元組碼檔案只有一份,所以對應的 每個類的 Class 物件也只有一個,所有的類物件共用
舉例:
package blogs.blog11;
/**
* 獲取Class 物件的三種方式
*/
public class ReflectTest01 {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("java.lang.String");
Class<String> clazz2 = String.class;
System.out.println(clazz == clazz2); // true
// 因為每個類只有一個 Class 物件,類只會加載一次到記憶體當中,并只生成一份 .class 檔案
// 所有物件共用,
Class clazz3 = Class.forName("blogs.blog11.User");
Class clazz4 = User.class;
System.out.println(clazz3 == clazz4); // 回傳 true
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

上述代碼的記憶體圖決議:
6. 類加載器的理解

6.1 類的加載程序:
當程式主動使用了某個類時,如果該類還未加載到記憶體中,則系統會通過,如下三個步驟對該類進行初始化:

- 加載:
將 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;
// }
什么時候發生類初始化 ???
- 類的主動參考(一定會發生類的初始化)
當虛擬機啟動,先運行 main 方法所在的類,new 一個類的物件,呼叫類的靜態成員(除了 final 常量)和靜態方法,使用 java.lang.reflect 包的方法對類進行反射呼叫,當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類,
- 類的被動參考(不會發生類的初始化)
當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化,當通過子類參考父類的靜態變數,不會導致子類初始化,
通過陣列定義類參考,不會觸發此類的初始化,參考常量不會觸發此類的初始化(常量在鏈接階段存入呼叫類的常量池中了,)
6.2 類加載的作用

類加載器: 負責將對應類生成的.class 檔案(可能在磁盤上,也可能在網路上)加載到記憶體中,并為之在堆區中生成對應的 java.lang.Class物件,作為方法區中類資料的訪問入口,
類加載器作用是用來把類(class)裝載進記憶體的,JVM 規范定義了如下型別的 類的加載器,

類加載機制:
類加載器負責加載所有的類,系統為所有被載入記憶體中的類生成
java.lang.Class實體物件,一個類被載入 JVM 中,同一個類就不會再次載入了,正如一個物件有一個唯一的標識一樣,一個載入 JVM 中的類也有一個唯一的標識,
在 Java 中,一個類用其全限定類名(包括包名和類名) 作為標識,但在JVM中,一個類用其全限定類名和其類加載器作為唯一標識,
例如:如果在 pg 的包中有一個名為 Person 的類,被類加載器 ClassLoader 實體 Loader 負責加載,則該 Person 類對應的 Class 物件在 JVM 中表示為(Person,pg,Loader)這意味著兩個類加載器加載同名類:(Person,pg,Loader)和 (Person,pg,Loader)是不同的,它們所加載的類也是完全不同,互不兼容的,
當JVM啟動時,會形成由三個類加載器組成的初始類加載器層次結構:
- 根類加載器(Bootstrap ClassLoader) :其負責加載Java的核心類,比如String,System 這些類,一般是在
C:\Program Files\jdk1.8.0_101\jre\lib\rt.jar路徑下,rt.jar中都是JDK最核心的類別庫 - 擴展類加載器(Extension ClassLoader) :其負責加載 JRE 的擴展類別庫,一般是在
:\Program Files\jdk1.8.0_101\jre\lib\*.jar路徑下的 - 系統類加載器(System ClassLoader) : 其負責加載 CLASSPATH 環境變數所指定的 JAR 包和類路徑,
除了可以使用 Java 提供的類加載器之外, 開發者也可以實作自己的類加載器, 自定義的類加載器通過繼承 ClassLoader 來實作,
- 用戶類加載器:用戶自定義的加載器,以類加載器為父類
6.2.1 雙親委派
java中為了保證類加載的安全,使用了雙親委派機制,優先從啟動類加載器中加載,這個稱為"父","父"無法加載到,再從擴展類加載器中加載,這個稱為"母",雙親委派,如果都加載不到,才會考慮從應用類加載器中加載,直到加載到為止,
如下是 :類加載器的層次結構圖

雙親委派模型:如上圖所示的類加載器之間的這種層次關系,就稱為類加載器的雙親委派模型(Parent Delegation Model),該模型要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器,子類加載器和父類加載器不是以繼承 (Inheritance) 的關系實作,而是通過組合(Composition)關系來復用父加載器的代碼,
雙親委派模型的作業程序為:
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的加載器都是如此,因此所有的類加載請求都會傳給頂層的啟動類加載器 ,只有當父加載反饋自己無法完成該加載請求(該加載的搜索范圍中沒有找到對應的類)時,子加載器才會嘗試自己去加載,
JVM 的類加載機制主要有如下三種:
- 全盤負責: 所謂全盤負責,就是當一個類加載器負責加載某一個 Class 時,該 Class 所依賴的和參考的其他 Class 也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入,
- 父類委托: 所謂的父類委托,則是 先讓 parent(父)類加載器試圖加載該 Class,只有在父類加載器無法加載該類時,才嘗試從自己的類路徑中加載該類,
- 快取機制: 快取機制將保證所有加載過的 Class 都會被快取,當程式中需要使用某個 Class 時,類加載器先從快取區中搜尋該 Class,只有當快取區不存在該 Class 物件時,系統才會讀取該類對應的二進制資料,并將其轉換成 Class 物件,存入快取區中,這就是為什么修改了 類后,必須重新啟動 JVM ,程式所做的修改才會生效的原因,
補充: 標準的 JavaSE 類加載器可以按要求查找類,但一旦某個類被加載到類加載器 中,它將維持加載(快取)一段時間,不過JVM垃圾回識訓制可以回收這些Class物件,
6.3 ClassLoader 的使用


舉例:
- 獲取一個系統類加載器 格式如下:
ClassLoader classLoader = 類名.class.getClassLoader();
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 對于自定義類,使用的是系統類加載進行加載的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
System.out.println(classLoader);
}
}

- 獲取系統類加載器的父類加載器,即擴展類加載器
格式:
// 首先通過: 類名.class 呼叫getClassLoader() 方法獲取到該類的類加載器
// 再通過獲取到的類加載器,呼叫 getParent()犯法,獲取都器父類的類加載器,
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
ClassLoader classLoaderParent = classLoader.getParent();
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 對于自定義類,使用的是系統類加載進行加載的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
System.out.println(classLoader);
// 呼叫對應類中父類中的getParent(): 獲取擴展類加載器
ClassLoader classLoaderParent = classLoader.getParent();
System.out.println(classLoaderParent);
}
}

- 獲取擴展類加載器的父類加強器,即引導類加載器
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 對于自定義類,使用的是系統類加載進行加載的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
// 呼叫對應類中父類中的getParent(): 獲取擴展類加載器
ClassLoader classLoaderParent = classLoader.getParent();
// 呼叫擴展類加強器到getParent(): 是無法獲取引導類加載器
// 引導類加載器主要負責: 加載Java的核心型別,無法加載自定義類的
// 所以一般是回傳 null 的,我們無法獲取到的,
ClassLoader parent = classLoaderParent.getParent();
System.out.println(parent);
}
}

- 測驗當前類由哪個類加載器進行加載的
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
try {
// Java內置的 String 型別
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
// 自定義的類參考型別
ClassLoader classLoader2 = Class.forName("blogs.blog11.Test").getClassLoader();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Test {
public int num = 0;
public Test() {
}
@Override
public String toString() {
return "Test{" +
"num=" + num +
'}';
}
}

7. 反射機制查看類
通過反射機制獲取到對應類中的 包資訊,父類名,介面,修飾符,類名,泛型,類中實作的介面,父類中實作的介面
舉例
package blogs.blog11;
import java.lang.reflect.Modifier;
public class ReflectTest03 {
public static void main(String[] args) {
// 1. 獲取到對應類中的 Class 物件
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 2. 通過該 Class 物件獲取到對應類中的包名
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
// 3. 獲取到該類中的修飾符
int modifiers = clazz.getModifiers(); // 注意這里回傳的是對應 修飾符中序號
String s = Modifier.toString(modifiers);
System.out.println(s); // 如果默認是預設的權限修飾符的話,回傳的是 空
// 4. 獲取到該類的類名
String name = clazz.getName();
System.out.println(name);
// 5. 獲取該類中所繼承的父類名
String superName = clazz.getSuperclass().getName();
System.out.println(superName);
// 6. 獲取該類中的所實作的介面.因為一個類中可以繼承多個介面所以,回傳的是Class[]型別的陣列
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
}
}
@MyAnnotation
class MyClass<String> extends MySuperClass implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
@interface MyAnnotation {
String value() default "Hello";
}
class MySuperClass { // protected
}

8. 反射機制呼叫構造器
-
public Constructor[] getConstructors():回傳此 Class 物件所表示的類的所有public構造方法,
-
public Constructor[] getDeclaredConstructors():回傳此 Class 物件表示的類宣告的所有構造方法,包括私有的構造器
-
Constructor類中:
-
public int getModifiers() 獲取到修飾符,回傳的是對應Modifier 修飾符類中的 數值常量,可以通過 Modifier .toString(對應修飾符的數值) 獲取對應修飾符的名稱;

-
public String getName() 獲取到方法名稱
-
public Class[] getParameterTypes() 獲取到引數的型別
通過反射獲取到類中所有的構造器中的資訊
舉例:
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* 反射處理構造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
StringBuilder s = new StringBuilder(); // 用于字串的拼接
// 獲取到對應的類中的 Class 物件.
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass3");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 獲取該類中所有的構造方法
// 因為一個類中可能會存在多個構造器,所有回傳的是一個Constructor 的陣列
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors) {
// 獲取到構造方法的修飾符
int modifiers = c.getModifiers();
String s2 = Modifier.toString(modifiers);
System.out.print(s2 + "\t");
// 獲取到構造器的名字,其實就是類名
String simpleName = clazz.getSimpleName();
System.out.println(simpleName);
// 獲取該構造方法中的實參串列
// 因為一個方法中會存在多個引數,所以回傳的是一個 Class[]陣列
Class[] parameterTypes = c.getParameterTypes();
// 當該回傳的引數陣列,含有資料,不是 0 ,不為 null
// 則說明該構造器是有引數的,
if(!(parameterTypes.length == 0 || parameterTypes == null)) {
// 則獲取到該構造器中從引數
for(Class aClass : parameterTypes) {
String type = aClass.getSimpleName(); // 實參型別
System.out.println(type);
}
}
System.out.println();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
// 定義無參構造器,用于反射呼叫
}
public MyClass3(String name) {
this.name = name;
}
public MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
}

8.1 通過反射呼叫私有構造器 ??????
- 通過反射呼叫無參構造器
使用方法:newInstance()
該方法的實際上是呼叫了類中的無參構造器,如果類中沒有定義無參構造器,就無法通過該反射機制呼叫類中的構造器
舉例:
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* 反射處理構造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
// 1.獲取對應的Class 物件
try {
Class clazz = Class.forName("blogs.blog11.MyClass3"); // 全類路徑
// 2.呼叫 newInstance();這個構造器
// 該方法實際上是呼叫了類中的無參構造器
Object o = clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
System.out.println("無參構造器");
}
public MyClass3(String name) {
this.name = name;
}
public MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
}

注意: 如果該類中沒有定義無參構造器,則無法反射,會例外:java.lang.InstantiationException,所以一般定義的類都會創建一個無參構造器,用于反射機制的獲取,

- 通過反射機制呼叫私有的構造器
如果想要呼叫類中 權限范圍小的,比如 private 私有的,必須使用 setAccessible(true) 方法將其中的值設定為 true
- Method(方法) 和 Field(屬性),Constructor(構造器) 都有 serAccessible() 方法
- setAccessible() 啟動和禁用訪問安全檢查的開關,
- 引數值為
true則指示反射的物件在使用時,應該取消 Java 語言訪問檢查,- 提高反射的效率,如果代碼中必須用反射,而該代碼需要頻繁的被呼叫,那么請設定為
true - 可以使得原本無法訪問的私有成員/方法/構造器也可以訪問,
- 提高反射的效率,如果代碼中必須用反射,而該代碼需要頻繁的被呼叫,那么請設定為
- 引數值為
false則指示反射的物件應該實施 Java語言訪問檢查,
舉例: 這里訪問私有的構造器,通過反射機制,
注意 需要將 setAccessible(true) 中設定為 true 是保證反射可以訪問私有的構造器,
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
/**
* 反射處理構造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
// 1.獲取對應的Class 物件
try {
Class clazz = Class.forName("blogs.blog11.MyClass3"); // 全類路徑
// 第一步:先獲取到這個類中有引數的構造器的Constructor物件
// 注意對應呼叫的構造器中的引數型別要保持一致,用于后面的賦值
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
// 第二步: 設定將該獲取到的Constructor(構造器)物件中的setAccessible(true)保證私有的可以被訪問
// 如果缺少這一步設定的話,是無法訪問私有的構造器的
declaredConstructor.setAccessible(true);
// 第三步: 傳構造器中對應的引數,呼叫構造器,創建物件
// 通用是呼叫 newInstance() 方法呼叫其中類對應的構造器,注意這里我們呼叫的是帶了引數的構造器了,
Object tom = declaredConstructor.newInstance("Tom", 18);
System.out.println(tom);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
}
private MyClass3(String name) {
this.name = name;
}
private MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "MyClass3{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

注意: 如果缺少了,setAccessible(true); 這一步設定的話,是無法呼叫到私有的構造器的,會報該例外:java.lang.IllegalAccessException: 非法訪問例外,

9. 反射機制獲取類中的屬性
- public Field[] getFields() : 回傳此Class物件所表示的類或介面的public的Field,
- public Field[] getDeclaredFields() : 回傳此Class物件所表示的類或介面的全部Field,
- public int getModifiers() : 以整數形式回傳此Field的修飾符.同理可以使用 Modifier .toString(對應修飾符的數值) 獲取對應修飾符的名稱;
- public Class getType() 得到Field的屬性型別
- public String getName() 回傳Field的名稱,
舉例:
package blogs.blog11;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) {
// 這里我們創建一個字串,用于字串的拼串
StringBuilder s = new StringBuilder();
Class clazz = null;
try {
// 1. 獲取到該類的Class 位元組碼物件
clazz = Class.forName("blogs.blog11.MyClass6");
// 2. 實體化該物件:因為這里我們已經知道需要實體讀的物件是什么了,所以可以直接強制轉換
// newInstance()方法呼叫的是該類中的無參構造器,所以無參構造器必須要有
MyClass6 myClass = (MyClass6) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 將該類中的 修飾符 類名 添加到該字串中
s.append(Modifier.toString(clazz.getModifiers()) + "class \t" + clazz.getSimpleName() + "\t{ \n");
// 3. 獲取到該類中的所有屬性,包括private 私有的
// 因為一個類中的屬性是多個,所以回傳的是 Field 屬性陣列
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
s.append("\t");
// 獲取到該屬性中的修飾符
int modifiers = f.getModifiers();
String bedeck = Modifier.toString(modifiers);
s.append(bedeck); //添加到字串中
s.append(" ");
// 獲取到該屬性的型別
Class<?> type = f.getType();
// 獲取到該屬性的型別名稱
String nameType = type.getName();
s.append(nameType); // 添加
s.append(" ");
// 獲取到屬性名
String nameAttribute = f.getName();
s.append(nameAttribute); // 添加
s.append("\n");
}
s.append("}");
System.out.println(s);
}
}
class MyClass6 {
public String name;
protected int age;
private int id;
boolean sex;
public MyClass6() {
// 定義無參構造器,用于反射呼叫
}
@Override
public String toString() {
return "MyClass6{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", sex=" + sex +
'}';
}
}

9.1 通過反射機制呼叫私有的屬性(set/get) ??????
在反射機制中,可以直接通過Field類操作類中的屬性,通過Field類提供的set()和get()方法就可以完成設定和取得屬性內容的操作,
-
public Field getField(String name) :回傳此Class物件表示的類或介面的指定的 public 的Field 屬性的位元組碼物件,只能獲取到 public的屬性
-
public Field getDeclaredField(String name) :回傳此Class物件表示的類或介面的 指定的Field屬性的位元組碼物件,所以的都可以,包括 private 私有的
-
public Object get(Object obj) :取得指定物件obj上此Field的屬性內容,獲取到該對應物件中指明的屬性的值
-
public void set(Object obj,Object value) :設定指定物件obj上此Field的屬性內容,修改該對應物件中指明的屬性的值
核心代碼:
//獲取需要訪問的屬性的 Field 物件((根據屬性的名稱來獲取Field)) : 通過屬性名找到你想要set/get的屬性的 Field (屬性)位元組碼物件
Field fieldName = clazz.getDeclaredField("name");
// 通過將為setAccessible(true)反射可以訪問私有的屬性
fieldName.setAccessible(true);
// 使用 set()對屬性賦值
// set(哪個物件中的屬性,賦的值)
fieldName.set(myClass,"Tom"); // 注意所賦的值,需要符合該類中的這個屬性的定義,不然報錯
// 6.讀取屬性的值:
// 兩個要素:MyClass物件的 name 屬性的值,哪個物件中的屬性值,獲取到該物件中對應的屬性值
Object o = fieldName.get(myClass);
System.out.println(o);
舉例:
package blogs.blog11;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) {
try {
// 1. 創建該類的 Class 位元組碼物件
Class clazz = Class.forName("blogs.blog11.MyClass6");
// 2. 獲取該類的實體化物件: 因為這里我們已經知道需要實體化的物件是什么了,所以可以直接強制轉換
// newInstance()實際呼叫的是該類中的無參構造器,所以該類必須要有無參構造器
MyClass6 myClass = (MyClass6) clazz.newInstance();
// 3. 獲取需要訪問的屬性的 Field 物件((根據屬性的名稱來獲取Field))
Field fieldName = clazz.getDeclaredField("name");
// 4. 給MyClass6 物件中的 私有 name 屬性賦值
/*
雖然使用了反射機制,但是三要素還是缺一不可:
要素1: Object 物件,修改賦值的物件上的屬性值
要素2: no 屬性
要素3: 100屬性值
*/
// 通過將為setAccessible(true)反射可以訪問私有的屬性
fieldName.setAccessible(true);
// 5.使用 set()對屬性賦值
// set(對應的賦值的物件,該物件中想要賦的屬性值)
fieldName.set(myClass,"Tom"); // 注意所賦的值,需要符合該類中的這個屬性的定義,不然報錯
// 6.讀取屬性的值:
// 兩個要素:MyClass 物件的 name 屬性的值
Object o = fieldName.get(myClass);
System.out.println(o);
System.out.println(myClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
class MyClass6 {
private String name;
protected int age;
private int id;
boolean sex;
public MyClass6() {
// 定義無參構造器,用于反射呼叫
}
@Override
public String toString() {
return "MyClass6{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", sex=" + sex +
'}';
}
}

10. 反射機制獲取類中的方法
-
public Method[] getDeclaredMethods():回傳此Class物件所表示的類或介面的全部方法,包括私有的方法
-
public Method[] getMethods() :回傳此Class物件所表示的類或介面的public的方法
-
public Class getReturnType():獲取到該方法中的回傳值型別
-
public Class[] getParameterTypes():獲取到該方法全部的引數型別,
-
public int getModifiers():獲取到該方法的修飾符,回傳的是對應Modifier 修飾符類中的 數值常量,可以通過 Modifier .toString(對應修飾符的數值) 獲取對應修飾符的名稱;
-
public Class[] getExceptionTypes(): 獲取到該方法的例外資訊
舉例:
package blogs.blog11;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest07 {
public static void main(String[] args) {
// 定義一個字串,用于字串的拼接
StringBuilder stringBuilder = new StringBuilder();
// 1. 獲取到該類的 Class 位元組碼物件
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass7");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 首先將該類名,資訊添加到字串中
stringBuilder.append(Modifier.toString(clazz.getModifiers()) + "\tclass\t" + clazz.getSimpleName() +
"\t{\n");
// 2. 獲取到該類中的所有的方法,包括private 私有的
Method[] methods = clazz.getDeclaredMethods();
// 遍歷該 Method 陣列中的值
for (Method m : methods) {
stringBuilder.append("\t");
// 獲取到該方法中的修飾符 public
int modifiers = m.getModifiers();
String s = Modifier.toString(modifiers);
stringBuilder.append(s); // 添加上
stringBuilder.append(" ");
// 獲取到該方法的回傳值型別
Class<?> returnType = m.getReturnType();
// 獲取到該方法的回傳值型別名
String nameType = returnType.getName();
stringBuilder.append(nameType); // 添加上
stringBuilder.append(" ");
// 獲取到該方法名稱
String name = m.getName();
stringBuilder.append(name);
stringBuilder.append(" ( ");
// 獲取到該方法的引數串列:
// 因為方法中可能存在多個引數串列,也可能一個也沒有
Class<?>[] parameterTypes = m.getParameterTypes();
// 如果該方法從實引數是 0 個,就不要獲取該方法中的實參型別了
if (!(parameterTypes.length == 0 || parameterTypes == null)) {
// 有遍歷該實參串列
for (Class<?> p : parameterTypes) {
// 獲取到該實參的型別
String simpleName = p.getSimpleName();
stringBuilder.append(simpleName); // 添加上
stringBuilder.append(",");
}
// 洗掉指定下標位置上的字符,這里洗掉的是:最后一個引數多了一個 “,”逗號
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
}
stringBuilder.append("){}\n");
}
stringBuilder.append("}");
System.out.println(stringBuilder);
}
}
final class MyClass7 {
public MyClass7() {
// 定義無參構造器,用于反射呼叫
}
public static void fun2() {
}
static final void fun4(String s, double d) {
}
private int fun1(int num) {
return num;
}
private String fun3() {
return "Hello World";
}
}

10.1 通過反射呼叫類中私有的方法
通過反射,呼叫類中的方法,通過Method類完成,步驟:
1.通過Class類的getMethod(String name,Class…parameterTypes)方法取得 一個Method 方法位元組碼物件,并設定此方法操作時所需要的引數型別,之后使用Object invoke(Object obj, Object[] args)進行呼叫,并向方法中傳遞要設定的obj物件的引數資訊,
Object invoke(Object obj, Object … args) 方法的說明:
-
invoke()方法的回傳值,就是呼叫的原方法的回傳值,若原方法無回傳值,此時回傳null
-
若原方法若為靜態方法,此時形參Object obj 可為null
-
若原方法形參串列為空,則 Object[] args 可為null
-
若原方法宣告為 private,則需要在呼叫此 invoke()方法前,顯式呼叫 方法物件的
setAccessible(true)方法,將可訪問private的方法,
舉例:
package blogs.blog11;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest07 {
public static void main(String[] args) {
try {
// 1. 獲取到該類的 Class 位元組碼物件
Class clazz = Class.forName("blogs.blog11.MyClass7");
//2. 創建對應的類物件,通過反射呼叫其無參構造器
// 這里因為我們知道,實體化的物件是什么型別的可以直接強制轉換
MyClass7 myClass = (MyClass7) clazz.newInstance();
// 3. 獲取指定想要呼叫的方法中的 Method 方法的位元組碼物件
// 方法存在多載:所以想要確定一個方法的要素是:方法名,實參型別
// 獲取到 fun1()方法的 Method 物件
Method method = clazz.getDeclaredMethod("fun1");
// 獲取到 fun3(String s ,int num) 方法的 Method 物件
Method method2 = clazz.getDeclaredMethod("fun3", String.class, int.class); // 注意需要和實際呼叫的方法中的型別保持一致
// 4. 設定為 true 保證反射可以呼叫私有的方法
method.setAccessible(true);
method2.setAccessible(true);
// 5.
/*
呼叫方法
1. fun1() fun2() 方法名
2. myClass呼叫哪個物件中的方法
3. 方法中的引數串列的賦值:“Tom" ,999實參
4. o,o2 回傳型別
該 invoke()方法的回傳型別就是,對應反射機制呼叫的物件中的方法的回傳型別
*/
// 呼叫 method 中的 fun1() 方法
// 因為我們的 fun1()方法是沒有實參的所以傳個 null 就可以了
Object o = method.invoke(myClass, null);
System.out.println(o); // 因為fun1()方法是沒有回傳值的所以回傳的是 null
// 呼叫 method2 中的fun2()方法
Object o2 = method2.invoke(myClass, "Tom", 999);
System.out.println(o2); // invoke()方法的回傳值,就是 fun2()方法的回傳值
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
final class MyClass7 {
public MyClass7() {
// 定義無參構造器,用于反射呼叫
}
public static void fun2() {
}
static final void fun4(String s, double d) {
}
private void fun1() {
System.out.println("你好世界");
}
private String fun3(String s, int num) {
return "Hello World" + num;
}
}

11. 反射讀取檔案資訊

// 使用反射進行獲取
@Test
public void test3() {
// Map 集合
Properties properties = new Properties();
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// 注意: 該檔案路徑需要在 src 目錄下才行,注意: 再包下時需要說明包路徑下的位置
// 讀取組態檔的方式二: 使用ClassLoader
// 組態檔默認識別為: 當前module的src 下
InputStream resourceAsStream = classLoader.getResourceAsStream("day28/jdbc.properties");
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
String user = properties.getProperty("user");
System.out.println(user);
String password = properties.getProperty("password");
System.out.println(password);
}

12. 反射機制獲取到類中的注解資訊
想要讓反射可以讀取到注解中的資訊,則該反射中的元注解必須是: @Retention(RetentionPolicy.RUNTIME) 才行,
舉例: 這里我們使用反射讀取到 fun() 方法中的 注解中的 value 屬性值:
注解
package blogs.blog10;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 生命周期在:運行期,才可以被反射讀取到
public @interface MyAnnotation {
String value() default "Tom";
}
package blogs.blog10;
import java.lang.reflect.Method;
public class AnnotationTest {
public static void main(String[] args) {
Method method = null;
try {
// 獲取類加載器,類物件
Class clazz = Class.forName("blogs.blog10.AnnotationTest"); // 全類路徑名
// 獲取 fun()方法
method = clazz.getDeclaredMethod("fun");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 判斷該方法是否存在該注解,存在才讀取該注解上的屬性值
if(method.isAnnotationPresent(MyAnnotation.class)) {
// 獲取該注解物件
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// // 獲取該注解的屬性值,就像物件.屬性一樣
String value = https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/23/annotation.value();
System.out.println(value);
}
}
@MyAnnotation("lihua")
public void fun() {
int num = 0;
}
}

13. 23種設計模式:動態代理模式
想要了解:動態代理模式的大家可以移步至:?????? https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
14. 總結:
- 反射的作用:通過 Java語言中的反射機制可以操作位元組碼檔案,優點類似于黑客,(可以讀和修改位元組碼檔案),可以呼叫私有的構造器,set/get 屬性,呼叫私有的方法,
- 獲取 Class 類實體的三種方式,重點掌握方式三:該方式會自動呼叫該類中的靜態代碼塊,因為靜態代碼塊,而不會實體化類的,可以應用于 JDBC 加載資料庫驅動,執行其中的靜態代碼塊,而不實體化,
- 類加載的程序,
- 類加載器的作用: 負責將對應類生成的
.class檔案(可能在磁盤上,也可能在網路上)加載到記憶體中,并為之在堆區中生成對應的java.lang.Class物件,作為方法區中類資料的訪問入口, - 雙親委派的原理,類加載器的種類,層次
- 反射機制中的:呼叫私有的構造器,呼叫私有的屬性,呼叫私有的方法,獲取注解中的屬性值,
15. 最后:
?????? ???????????????????? 感謝以下大佬提供的參考資料 ???????????????????? ??????
【1】: https://fighter3.blog.csdn.net/article/details/103758938
【2】:https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
限于自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善 ,謝謝大家,江湖再見,后會有期 !!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/544823.html
標籤:其他
上一篇:【牛客】3 時序邏輯


