day42-反射01
Java反射01
1.反射(reflection)機制
1.1反射機制問題
一個需求引出反射
請看下面問題:
- 根據配置檔案 re.properties 指定資訊,建立Cat物件並呼叫方法hi
classfullpath=li.reflection.Cat method=hi
使用現有的技術,你能做的到嗎?
-
這樣的需求在學習框架時特別多,即通過外部檔案配置,在不修改原始碼的情況下來控制程式,也符合設計模式的ocp原則(開閉原則)
開閉原則:不修改原始碼,擴充套件功能
例子:
re.properties:
classfullpath=li.reflection.Cat method=cry
Cat:
package li.reflection; public class Cat { private String name = "招財貓"; public void hi() { System.out.println("hi " + name); } public void cry() { System.out.println(name + " cry"); } }
ReflectionQuestion:
package li.reflection; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //根據配置檔案 re.properties 指定資訊,建立Cat物件 並呼叫方法hi //1.傳統方法 new 物件 -->呼叫方法 //Cat cat = new Cat(); //cat.hi(); //2.嘗試使用讀取檔案的方法 //2.1使用properties類,可以讀寫配置檔案 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpatch = properties.get("classfullpath").toString(); String methodName = properties.get("method").toString(); System.out.println("classfullpath" + classfullpatch); System.out.println("method" + methodName); //2.2建立物件 //使用傳統的方法行不通 //3.使用反射機制解決 //3.1載入類,返回一個Class型別的物件cls(這裡的Class就是一個類,他的類名就叫Class) Class cls = Class.forName(classfullpatch); //3.2通過cls得到載入的類 li.reflection.Cat 的一個物件例項 Object o = cls.newInstance(); System.out.println("o的執行型別=" + o.getClass());//o的執行型別 //3.3通過 cls 得你載入的類 li.reflection.Cat 的methodName"hi" 的方法物件 // 即:在反射機制中,可以把方法視為一個物件(萬物皆物件) Method method1 = cls.getMethod(methodName); //3.4通過 method1 呼叫方法:即通過方法物件來實現呼叫方法 System.out.println("========"); method1.invoke(o);//傳統方法: 物件.方法() , 反射機制:方法.invoke(物件) //意義在與反射機制可以通過不求該原始碼就完成功能的拓展 /** * 例如,在Cat類中有兩個方法,hi()和cry(),現在要求將呼叫用的hi方法改為呼叫cry方法, * 這時候只需要在配置檔案re.properties中修改引用的方法名即可 * 不需像想傳統方法一樣,需要在原始碼中修改 */ } }
1.2反射機制
1.2.1Java Reflection
- 反射機制允許程式在執行期間藉助於Reflection API取得任何類的內部資訊(比如成員變數,構造器,成員方法等等),並能夠操作物件的屬性以及方法。反射在設計模式和框架底層都會用到
- 載入完類之後,在堆中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件包含了類的完整結構資訊。通過這個物件得到類的結構。這個Class物件就像一面鏡子,透過這個鏡子看到了的結構,所以形象地稱之為:反射
例如:一個Person型別的例項物件叫 p
則 p 物件 - -> 型別 Person類
Class 物件 cls --> 型別 Class 類(這個類的名字就叫Class)
1.2.2Java反射機制原理圖
- Java反射機制可以完成
- 在執行時判斷任意一個物件所屬的類
- 在執行時構造任意一個類的物件
- 在執行時得到任意一個類所具有的成員變數和方法
- 在執行時呼叫任意一個物件的成員變數和方法
- 生成動態代理
1.2.3反射相關類
反射相關的主要類:
- java.lang.Class:代表一個類,Class物件表示某個類載入過後在堆中的物件
- java.lang.reflect.Method:表示類的方法(Method物件表示某個類的方法)
- java.lang.reflect.Field:表示類的成員變數(Field物件表示某個類的成員變數)
- java.lang.reflect.Constructor:表示類的構造方法(Constructor物件表示某個類的構造器)
這些類在 java.lang.reflect
例子:
Cat:
package li.reflection; public class Cat { private String name = "招財貓"; public int age = 10; public Cat() { }//無參構造器 public Cat(String name) { this.name = name; } public void hi() { System.out.println("hi " + name); } public void cry() { System.out.println(name + " cry"); } }
re.properties:
classfullpath=li.reflection.Cat method=hi
Reflection01:
package li.reflection; import java.io.FileInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Properties; public class Reflection01 { public static void main(String[] args) throws Exception { //使用properties類,讀寫配置檔案 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpatch = properties.get("classfullpath").toString(); String methodName = properties.get("method").toString(); System.out.println("classfullpath" + classfullpatch); System.out.println("method" + methodName); //反射機制 //1.載入類,返回一個Class型別的物件cls(這裡的Class就是一個類,他的類名就叫Class) Class cls = Class.forName(classfullpatch); //通過cls得到載入的類 li.reflection.Cat 的一個物件例項 Object o = cls.newInstance(); System.out.println("o的執行型別=" + o.getClass());//o的執行型別 //2.通過 cls得到載入的類 li.reflection.Cat 的methodName"hi" 的方法物件 // 即:在反射機制中,可以把方法視為一個物件(萬物皆物件) Method method1 = cls.getMethod(methodName); //通過 method1 呼叫方法:即通過方法物件來實現呼叫方法 System.out.println("========"); method1.invoke(o);//傳統方法: 物件.方法() , 反射機制:方法.invoke(物件) //3.java.lang.reflect.Field:表示類的成員變數(Field物件表示某個類的成員變數) //得到name欄位 //getField不能得到私有的屬性 Field nameField = cls.getField("age"); System.out.println(nameField.get(o));//傳統方法:物件.成員變數 , 反射:成員變數的物件.get(物件) //4.java.lang.reflect.Constructor:表示類的構造方法(Constructor物件表示某個類的構造器) Constructor constructor1 = cls.getConstructor();//()中可以指定構造器的型別,這裡返回無參構造器 System.out.println(constructor1);//Cat() Constructor constructor2 = cls.getConstructor(String.class);//這裡傳入的String.class 就是String類的Class物件 System.out.println(constructor2);//Cat(String name) } }
1.2.4反射優點和缺點
- 優點:可以動態地建立和使用物件(也是底層框架的核心),使用靈活,沒有反射機制,框架技術就失去底層支撐。
- 缺點:使用反射機制基本是解釋執行,對執行速度有影響
- 呼叫反射優化-關閉訪問檢查
- Method和Field、Constructor物件都有setAccessible()方法
- setAccessible作用是啟動和禁用服務安全檢查的開關
- 引數值為true表示 反射的物件在使用時 取消訪問檢查,提高反射效率。引數值為false則表示反射的物件執行訪問檢查
例子:測試反射呼叫的效能 和優化方案
package li.reflection; import java.lang.reflect.Method; //測試反射呼叫的效能 和 優化方案 public class Reflection02 { public static void main(String[] args) throws Exception { m1(); m2(); m3(); } //傳統方法,呼叫hi public static void m1() { Cat cat = new Cat(); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { cat.hi(); } long end = System.currentTimeMillis(); System.out.println("m1() 耗時=" + (end - start)); } //反射機制呼叫hi public static void m2() throws Exception { //拿到Cat類的Class物件 Class cls = Class.forName("li.reflection.Cat");//這裡為了方便,就不讀檔案了 //構造Cat類的物件 Object o = cls.newInstance(); //得到Cat類的成員方法 Method hi = cls.getMethod("hi"); long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射機制呼叫方法 } long end = System.currentTimeMillis(); System.out.println("m2() 耗時=" + (end - start)); } //反射呼叫優化 public static void m3() throws Exception { //拿到Cat類的Class物件 Class cls = Class.forName("li.reflection.Cat");//這裡為了方便,就不讀檔案了 //構造Cat類的物件 Object o = cls.newInstance(); //得到Cat類的成員方法 Method hi = cls.getMethod("hi"); hi.setAccessible(true);//在反射呼叫方法時,取消訪問檢查 long start = System.currentTimeMillis(); for (int i = 0; i < 900000000; i++) { hi.invoke(o);//反射機制呼叫方法 } long end = System.currentTimeMillis(); System.out.println("m3()反射呼叫優化耗時=" + (end - start)); } }
2.Class類
2.1基本介紹
-
Class類也是類,因此也繼承Object類
-
Class類物件不是new出來的,而是系統建立的
-
對於某個類的Class類物件,在記憶體中只有一份,因為類只加載一次
-
每個類的例項都會記得自己是由哪個Class例項所生成
-
通過Class物件可以得到一個類的完整結構(通過一系列API)
-
Class物件是存放在堆的
-
類的位元組碼二進位制資料,是放在方法區的,有的地方稱為類的元資料(包括 方法程式碼,變數名,方法名,訪問許可權等)
當我們載入完類之後,除了會在堆裡生成一個Class類物件,還會在方法區生成一個類的位元組碼二進位制資料(元資料)
例子:
package li.reflection.class_; import li.reflection.Cat; //對Class類的特點的梳理 public class Class01 { public static void main(String[] args) throws ClassNotFoundException { //1.Class類物件不是new出來的,而是系統建立的 //1.1.傳統的 new物件 /**通過ClassLoader類中的loadClass方法: * public Class<?> loadClass(String name) throws ClassNotFoundException { * return loadClass(name, false); * } */ //Cat cat = new Cat(); //1.2反射的方式 /**在這裡debug,需要先將上面的Cat cat = new Cat();註釋掉,因為同一個類只加載一次,否則看不到loadClass方法 * (這裡也驗證了:3.對於某個類的Class類物件,在記憶體中只有一份,因為類只加載一次) * 仍然是通過 ClassLoader類的loadClass方法載入 Cat類的 Class物件 * public Class<?> loadClass(String name) throws ClassNotFoundException { * return loadClass(name, false); * } */ Class cls1 = Class.forName("li.reflection.Cat"); //2.對於某個類的Class類物件,在記憶體中只有一份,因為類只加載一次 Class cls2 = Class.forName("li.reflection.Cat"); //這裡輸出的hashCode是相同的,說明cls1和cls2是同一個Class類物件 System.out.println(cls1.hashCode());//1554874502 System.out.println(cls2.hashCode());//1554874502 } }
Class類物件不是new出來的,而是系統建立的:
- 在
Cat cat = new Cat();
處打上斷點,點選force step into,可以看到
- 註釋
Cat cat = new Cat();
,在Class cls1 = Class.forName("li.reflection.Cat");
處打上斷點,可以看到 仍然是通過 ClassLoader類載入 Cat類的 Class對
2.2Class類常用方法
public static Class<?> forName(String className)//傳入完整的“包.類”名稱例項化Class物件 public Constructor[] getContructors() //得到一個類的全部的構造方法 public Field[] getDeclaredFields()//得到本類中單獨定義的全部屬性 public Field[] getFields()//得到本類繼承而來的全部屬性 public Method[] getMethods()//得到一個類的全部方法 public Method getMethod(String name,Class..parameterType)//返回一個Method物件,並設定一個方法中的所有引數型別 public Class[] getInterfaces() //得到一個類中鎖實現的全部介面 public String getName() //得到一個類完整的“包.類”名稱 public Package getPackage() //得到一個類的包 public Class getSuperclass() //得到一個類的父類 public Object newInstance() //根據Class定義的類例項化物件 public Class<?> getComponentType() //返回表示陣列型別的Class public boolean isArray() //判斷此class是否是一個數組
「其他文章」
- 記一次批量更新整型型別的列 → 探究 UPDATE 的使用細節
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- Django 之路由層
- 【前端必會】webpack loader 到底是什麼
- day42-反射01
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 設計模式---享元模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- [ML從入門到入門] 支援向量機:從SVM的推導過程到SMO的收斂性討論
- 從應用訪問Pod元資料-DownwardApi的應用
- Springboot之 Mybatis 多資料來源實現
- Java 泛型程式設計
- CAS核心思想、底層實現