Java反射01 1.反射(reflection)機制 1.1反射機制問題 一個需求引出反射 請看下麵問題: 根據配置文件 re.properties 指定信息,創建Cat對象並調用方法hi classfullpath=li.reflection.Cat method=hi 使用現有的技術,你能做的 ...
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取得任何類的內部信息(比如成員變數,構造器,成員方法等等,包括private許可權的屬性和方法),並能夠操作對象的屬性以及方法。
反射在設計模式和框架底層都會用到
-
載入完類之後,在堆中就產生了一個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是否是一個數組