反射機制(Reflection)
一、Java反射機制概述
1. 準動態語言
-
動態語言
- 可以在執行時,根據某些條件改變自身結構
- Object-C、C#、JavaScript、PHP、Python
-
靜態語言
- 執行時結構不可改變
- Java、C、C++
- 利用反射機制可以使Java獲得類似動態語言的特性
2. 反射
-
反射機制在執行期間藉助ReflectionAPI
- 可以取得任何類的內部資訊
- 並能直接操作任意物件的內部屬性及方法(包括private修飾的)
-
載入完類後,在堆記憶體的方法去產生一個Class型別的物件——包含完整的類的結構資訊
3. 反射的優缺點
- 優點
- 實現動態建立物件和編譯
- 缺點
- 對效能有影響,反射是一種解釋操作結構,操作JVM
4. 反射獲取物件和new的區別
new——通過類來建立物件
class——通過獲取class物件獲取類、class物件有且只有一個
public class Test01 { public static void main(String[] args) throws ClassNotFoundException { //正常建立物件——通過類建立物件 Test t1 = new Test(); Test t2 = new Test(); t1.getAge(); //new建立的不是同一個物件 System.out.println(t1.hashCode());//460141958 System.out.println(t2.hashCode());//1163157884 //反射獲取物件——通過物件獲取類 Class c1 = Class.forName("com.shelton.reflection.Test"); Class c2 = Class.forName("com.shelton.reflection.Test"); c1.getName(); //Class物件是唯一的 System.out.println(c1.hashCode());//1956725890 System.out.println(c2.hashCode());//1956725890 } } //實體類 class Test { private String name; private int age; private String hobby; public Test() { } public Test(String name, int age, String hobby) { this.name = name; this.age = age; this.hobby = hobby; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } @Override public String toString() { return super.toString(); } }
二、理解Class類並獲取Class例項
1. 如何獲取Class類的例項
-
已知具體的類,通過類的class屬性獲取(最安全,效能最高)
Class c1 = Person.class;
-
已知某 個類的例項,呼叫該例項的getClass()方法獲取Class物件
Class c1 = person.getClass();
-
已知類的全名且該類在類路徑下,靜態的forName()方法獲取,並丟擲異常
Class c1 = Class.forName("demo01.Student");
-
內建基本資料型別可以直接用類名.Type
-
ClassLoader
public class Test02 { public static void main(String[] args) throws ClassNotFoundException { //new建立物件 Person p1 = new Student();//多型 System.out.println(p1.name);//學生 //方式1—— Class c1 = Student.class; System.out.println(c1.hashCode());//460141958 //方式2——通過物件獲得 Class c2 = p1.getClass(); System.out.println(c2.hashCode());//460141958 //方式3——通過包名獲得 Class c3 = Class.forName("com.shelton.reflection.Student"); System.out.println(c3.hashCode());//460141958 //方式4——基本型別的包狀態 Class c4 = Integer.TYPE; System.out.println(c4);//int //獲取父類 Class c5 = c1.getSuperclass(); System.out.println(c5);//class com.shelton.reflection.Person } } class Person { String name; } class Student extends Person { public Student() { this.name = "學生"; } } class Teacher extends Person { public Teacher() { this.name = "老師"; } }
2. 哪些類可以有class物件
public class Test03 {
public static void main(String[] args) {
Class c1 = Object.class;//類
Class c2 = Runnable.class;//介面
Class c3 = String[].class;//一維陣列
Class c4 = int[][].class;//二維陣列
Class c5 = Target.class;//註解
Class c6 = ElementType.class;//列舉型別
Class c7 = Integer.class;//包裝類
Class c8 = void.class;//空型別
Class c9 = Class.class;//類
System.out.println(c1);//class java.lang.Object
System.out.println(c2);//interface java.lang.Runnable
System.out.println(c3);//class [Ljava.lang.String;
System.out.println(c4);//class [[I
System.out.println(c5);//interface java.lang.annotation.Target
System.out.println(c6);//class java.lang.annotation.ElementType
System.out.println(c7);//class java.lang.Integer
System.out.println(c8);//void
System.out.println(c9);//class java.lang.Class
//同一個類只有一個class物件
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());//325040804
System.out.println(b.getClass().hashCode());//325040804
}
}
三、類的載入與ClassLoader
- 程式使用某個類的時候
1 類的載入
-
載入
- 生成class物件
-
連線
- 驗證:確保載入類的資訊符合JVM規範,沒有安全方面的問題
- 準備:正式為類變數(Static)再方法區中分配記憶體,並設定變數的預設值
- 解析:虛擬機器常量池內的符號(常量名)引用替換為直接引用(地址)
-
初始化
-
類構造器
<clinit>()方法
-
先初始化父類
-
虛擬機器會保證一個類的
()方法在多執行緒環境中被正確加鎖和同步 public class Test04 { public static void main(String[] args) { //1. Test004 test載入到記憶體,產生一個Class物件 A test = new A(); System.out.println("呼叫完類的a值:"+ A.a);//靜態變數 } } class A { //2. 連結 連結結束後,a = 0 static { System.out.println("靜態程式碼塊初始化"); int a = 300; } static int a = 100;//靜態變數 //3. new Test004()構造器初始化前,先執行類構造器clinic(由靜態域組成) /* <clinit>(){ System.out.println("靜態程式碼塊初始化"); int a = 300; static int a = 100;//靜態變數 } */ public A() { System.out.println("A類的無參構造"); } } /* 靜態程式碼塊初始化 A類的無參構造 呼叫完類的a值:100 */
-
2 為什麼會發生初始化
-
類的主動引用——一定發生初始化
new和反射一定會初始化
- 虛擬機器啟動,先初始化main方法所在的類
- 呼叫靜態成員(final常量除外)和靜態方法
- java.lang.reflect包方法對類反射呼叫
- 初始化一個類,先初始化它的父類
-
類的被動引用——不會發生初始化
-
訪問靜態域時,只有真正宣告這個域的類才會被初始化
- 子類引用父類的靜態變數,子類不會初始化
-
陣列定義類引用
-
引用常量
public class Test07 { static { System.out.println("Main類被載入"); } public static void main(String[] args) throws ClassNotFoundException { //主動引用 //1. new Son son = new Son(); /* Main類被載入 父類被載入 子類被載入 */ //2. 反射 Class c1 = Class.forName("com.shelton.reflection.Son"); /* Main類被載入 父類被載入 子類被載入 */ // 被動引用 // 1.子類呼叫父類的靜態變數、方法,子類不會被載入 System.out.println(Son.b); /* Main類被載入 父類被載入 2 */ //2. 陣列 Son[] array = new Son[10]; /* Main類被載入 */ //3. 靜態常量 System.out.println(Son.M); /* Main類被載入 1 */ } } class Father { static int b = 2; static { System.out.println("父類被載入"); } } class Son extends Father { static { System.out.println("子類被載入"); m = 300; } static int m = 100; static final int M = 1; }
-
3 類載入器
-
類載入:將class檔案位元組碼載入到記憶體中,生成唯一的Class物件,作為方法區類資料的訪問入口
-
類快取:類載入器載入了某個類後,會維持載入一段時間(快取),JVM垃圾回收機制可以回收這些Class物件
- 提高效率
-
類載入器的作用
-
自定義類載入器App--->系統類載入器Syc ---> 擴充套件類載入器Exc ---引導類載入器Boot(無法獲取)
public class Test08 { public static void main(String[] args) throws ClassNotFoundException { //1. 載入器型別 //獲取系統類的載入器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //獲取系統類載入器的父類載入器--->擴充套件類載入器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); //獲取擴充套件類載入器的父類載入器--->根載入器 ClassLoader parent1 = parent.getParent(); System.out.println(parent1); //2. 測試類是哪個載入器載入的 System.out.println("***********測試***********"); //當前類——使用者自定義類 ClassLoader classLoader = Class.forName("com.shelton.reflection.Test08").getClassLoader(); System.out.println(classLoader);//APP //Object類——JDK內建類 ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader(); System.out.println(classLoader1);//null——C++寫的無法獲取 //3. 獲得系統類載入器可以載入的路徑 System.out.println(System.getProperty("java.class.path")); /* C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar; C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar; D:\Codes\JAVA\JavaSE\Reflection\out\production\Reflection; C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar */ } }
-
-
雙親委派機制
- 使用者自己寫了一個String類,但在java.lang.String裡有這個String包了
public class String { }
- 如果呼叫String類裡的方法,不會呼叫這個String類,而是呼叫java.lang.String
- 雙親委派機制會在建立物件後,先向上找包(App ---> Exc ---Boot),找不到就再使用使用者自定義載入器
四、獲取執行時類的完整結構
-
通過反射獲取執行時類的完整結構
- Field、Method、Constructor、Superclass、Interface、Annotation
-
例如有一個實體類User
public class User { private String name; private int id; private int age; public String hobby; public User(String name, int id, int age) { this.name = name; this.id = id; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name" + name + '\'' + ", id" + id + ", age" + age + '}'; } }
1 獲得類的名字
-
getName()
-
getSimpleName()
User user = new User(); Class c2 = user.getClass(); //獲得類的名字——通過物件 System.out.println(c2.getName()); //包名+類名 System.out.println(c2.getSimpleName()); //類名
2 獲得類的屬性
-
getFields()
-
getDeclaredFields()
Class c1 = Class.forName("com.shelton.reflection.User"); //獲得類的屬性 //只能找到public屬性 Field[] fields = c1.getFields(); for (Field field : fields) { System.out.println(field); } //找到全部的屬性 Field[] declaredFields = c1.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //獲得指定屬性 Field name = c1.getDeclaredField("name"); System.out.println(name); /* ============getFields()============ public java.lang.String com.shelton.reflection.User.hobby ============getDeclaredFields()============ private java.lang.String com.shelton.reflection.User.name private int com.shelton.reflection.User.id private int com.shelton.reflection.User.age public java.lang.String com.shelton.reflection.User.hobby ============getDeclaredField("name")============ private java.lang.String com.shelton.reflection.User.name */
3 獲得類的方法
-
getMethods()
-
getDeclaredMethods()
Class c1 = Class.forName("com.shelton.reflection.User"); //獲得本類及其父類的所有方法 Method[] methods = c1.getMethods(); for (Method method : methods) { System.out.println(method); } //獲得本類的所有方法 Method[] declaredMethods = c1.getDeclaredMethods(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //獲得指定方法 //需要這個引數,因為方法有過載,方法名是一樣的 Method getName = c1.getMethod("getName", null); Method setName = c1.getMethod("setName", String.class); System.out.println(getName); System.out.println(setName); /* ============getMethods()============ public java.lang.String com.shelton.reflection.User.toString() public java.lang.String com.shelton.reflection.User.getName() public int com.shelton.reflection.User.getId() public void com.shelton.reflection.User.setName(java.lang.String) public int com.shelton.reflection.User.getAge() public void com.shelton.reflection.User.setId(int) public void com.shelton.reflection.User.setAge(int) public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ============getDeclaredMethods()============ private java.lang.String com.shelton.reflection.User.name private int com.shelton.reflection.User.id private int com.shelton.reflection.User.age public java.lang.String com.shelton.reflection.User.hobby ============getMethod("getName", null)============ public java.lang.String com.shelton.reflection.User.getName() public void com.shelton.reflection.User.setName(java.lang.String) */
4 獲得類的構造器
-
getConstructors()
-
getDeclaredConstructors()
//獲得本類及父類的構造器 Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } //獲得本類的構造器 Constructor[] declaredConstructors = c1.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor); } //獲得指定的構造器 Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class); System.out.println(declaredConstructor); /* ============getConstructors()============ public com.shelton.reflection.User() public com.shelton.reflection.User(java.lang.String,int,int) ============getDeclaredConstructors============ public com.shelton.reflection.User() public com.shelton.reflection.User(java.lang.String,int,int) ============getDeclaredConstructor============ public com.shelton.reflection.User(java.lang.String,int,int) */
五、有了class物件,能做什麼?
1 通過反射獲取類構造器建立物件
-
newInstance()方法
-
類必須有無參構造
-
類的構造器訪問許可權必須足夠
Class c1 = Class.forName("com.shelton.reflection.User"); //本質是呼叫無參構造建立物件,因此刪除User的無參構造後會報錯 User user = (User)c1.newInstance(); System.out.println(user);//User{name123', id1, age1}
-
也可以使用有參構造建立user物件
Class c1 = Class.forName("com.shelton.reflection.User"); //呼叫User的有參構造器使用 Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class); //建立User物件 User user2 = (User)declaredConstructor.newInstance("shelton", 1, 20); System.out.println(user2);//User{nameshelton', id1, age20}
-
2 通過反射獲取一個方法
Class c1 = Class.forName("com.shelton.reflection.User");
User user3 = (User)c1.newInstance();
//獲取set方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//使用方法
//invoke(物件,方法的值)
setName.invoke(user3,"哈哈");
System.out.println(user3);//User{name哈哈', id0, age0}
3 通過反射操作屬性
如果是private屬性,則要設定accessible為true
Class c1 = Class.forName("com.shelton.reflection.User");
//建立user物件
User user4 = (User)c1.newInstance();
//獲取name屬性
Field name = c1.getDeclaredField("name");
//關閉安全檢測——因為name屬性是private,不能直接操作
name.setAccessible(true);
//賦值
name.set(user4,"嘿嘿");
System.out.println(user4.getName());//嘿嘿
4 通過accessible來提高效率
public class Test11 {
//普通方式
public static void Test1 () {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000_000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式運行了:"+(endTime-startTime)+"ms");
}
//反射方式
public static void Test2 () throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class c1 = Class.forName("com.shelton.reflection.User");
User user = (User)c1.newInstance();
Method getName = c1.getDeclaredMethod("getName",null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000_000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式運行了:"+(endTime-startTime)+"ms");
}
//反射方式——關閉檢測
public static void Test3 () throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class c1 = Class.forName("com.shelton.reflection.User");
User user = (User)c1.newInstance();
Method getName = c1.getDeclaredMethod("getName",null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000_000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射關閉檢測方式運行了:"+(endTime-startTime)+"ms");
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Test1();
Test2();
Test3();
}
}
/*
普通方式運行了:5ms
反射方式運行了:2837ms
反射關閉檢測方式運行了:2049ms
*/
5 反射操作泛型
//通過反射獲取泛型
public class Test12 {
public void method01(Map<String,User> map, List<User> list) {
System.out.println("method01");
}
public Map<String,User> method02() {
System.out.println("method02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//獲取泛型資訊
//1. 獲取方法,輸入引數,然後生成一個物件
Method method01 = Test12.class.getDeclaredMethod("method01", Map.class, List.class);
//2. 獲取泛型型別,返回一個Type型別的陣列,生成物件
Type[] genericParameterTypes = method01.getGenericParameterTypes();
//3. 列印輸出這個Type型別陣列
for (Type genericParameterType : genericParameterTypes) {
System.out.println("泛型資訊:"+genericParameterType);
//4. 獲取泛型的引數
//判斷泛型的引數型別 是否為 結構化引數型別
if (genericParameterType instanceof ParameterizedType) {
//是則強轉成 結構化引數型別,呼叫getActualTypeArguments()方法獲取引數
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(" 引數:"+actualTypeArgument);
}
}
}
}
}
/* 泛型資訊:java.util.Map<java.lang.String, com.shelton.reflection.User>
引數:class java.lang.String
引數:class com.shelton.reflection.User
泛型資訊:java.util.List<com.shelton.reflection.User>
引數:class com.shelton.reflection.User
*/
6 通過反射操作註解
ORM——object relationship mapping
通過反射操作註解
-
建立註解(類、屬性)
//註解——類 @Target(value = ElementType.TYPE)//作用於類 @Retention(value = RetentionPolicy.RUNTIME)//執行時有效 @interface MyClass { String value() default ""; } //註解——屬性 @Target(value = ElementType.FIELD)//作用於屬性 @Retention(RetentionPolicy.RUNTIME) @interface Myfield { String columnName() default "null";//欄位名 String type() default "null"; //型別 int length() default 0;//長度 }
-
建立一個實體類Student
//實體類 @MyClass("this is a boy") class People { @Myfield(columnName = "boy_id",type = "int",length = 10) private int id; @Myfield(columnName = "boy_age",type = "int",length = 10) private int age; @Myfield(columnName = "boy_name",type = "varchar",length = 3) private String name; public People() { } public People(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Man{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } }
-
通過反射操作註解
public class test13 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { //1. 獲取類的註解資訊 //獲取Man類的class物件 Class c1 = Class.forName("com.shelton.reflection.People"); //通過class物件呼叫 getAnnotations()方法,獲取註解,建立物件 Annotation[] annotations = c1.getAnnotations(); //遍歷列印輸出 for (Annotation annotation : annotations) { System.out.println(annotation); //@com.shelton.reflection.MyClass(value=this is a boy) } //2. 獲取類註解,value的值 //強轉成MyClass型別,獲取value引數 MyClass annotation = (MyClass)c1.getAnnotation(MyClass.class); System.out.println(annotation.value());//this is a boy //3. 獲取指定屬性的註解資訊 Field name = c1.getDeclaredField("name"); Myfield annotation1 = name.getAnnotation(Myfield.class); System.out.println(annotation1.columnName());//boy_name System.out.println(annotation1.type());//varchar System.out.println(annotation1.length());//3 } }