一、反射概念
1.反射機制允許程式在執行期借助于ReflectionAPI取得任何類的內部資訊(比如成員變數,構造器,成員方法等等),并能操作物件的屬性及方法,反射在設計模式和框架底層都會用到,
2.加載完類之后,在堆中就產生了一個Class型別的物件(一個類只有一個Class物件) ,這個物件包含了類的完整結構資訊,通過這個物件得到類的結構,這個Class物件就像一面鏡子,透
過這個鏡子看到類的結構,所以,形象的稱之為:反射
Java反射最大的好處就是能在運行期間,獲得某個類的結構、成員變數,用來實體化,
而且反射是框架的靈魂,反射也是框架的底層基石,學好反射對于我們閱讀框架原始碼也是有非常大的幫助!

二、反射原理圖

三、反射相關的主要類具體使用
1. java.lang.Class:代表一個類,Class物件表示某 個類加載后在堆中的物件
2. java.lang.reflect.Method:代表類的方法,Method物件表示某個類的方法
3. java.lang.reflect.Field:代表類的成員變數,Field物件表示某個類的成員變數
4. java.lang.reflect.Constructor:代表類的構造方法,Constructor物件表示構造器

public class Dog { private String name; public int age; public Dog(){ } public Dog(String name){ } public void eat(){ System.out.println("吃骨頭"); } public void call(){ System.out.println("汪汪汪"); } }
public class ReflectionQuestion { public static void main(String[] args) throws Exception { //1、使用Properties類,可以讀寫組態檔 Properties properties = new Properties(); properties.load(new FileInputStream("src\\com\\ycl\\reflection\\reflection.properties")); String classPath = properties.get("classPath").toString(); String methodName = properties.get("method").toString(); //2、創建物件 傳統方法行不通 --> 反射機制 //new classPath(); //3、使用反射機制解決 //(1)加載類,回傳Class型別的物件 Class aClass = Class.forName(classPath); //(2)通過 aClass 得到加載的類 com.ycl.reflection.Dog 的物件實體 Object o = aClass.newInstance(); //(3)通過 aClass 得到你加載的類 com.ycl.reflection.Dog 的 methodName "eat"的方法物件 //java.Lang.reflect.Method:代表類的方法,Method物件表示某個類的方法 //即反射中,可以把方法視為物件(萬物皆物件) Method method = aClass.getMethod(methodName); //(4)通過method呼叫方法:即通過方法物件來實作呼叫方法 method.invoke(o);//傳統方法 物件.方法();反射機制 方法.invoke(物件) //java.Lang.reflect.Field:代表類的成員變數,FieLd物件表示某個類的成員變數 //得到name欄位 //getField 不能得到私有屬性 Field nameField = aClass.getField("age"); Object o1 = nameField.get(o); //傳統寫法 物件.成員變數 , 反射:成員變數物件.get(物件) System.out.println(o1); //java.Lang.reflect.Constructor:代表類的構造方法,Constructor物件表示構造器 Constructor constructor1 = aClass.getConstructor();//()中可以指定構造器引數型別,不寫回傳的是無參構造器 Constructor constructor2 = aClass.getConstructor(String.class);//這里的String.class就是String類的Class物件 System.out.println(constructor1); System.out.println(constructor2); } }

四、反射的優點和缺點
1. 優點:可以動態的創建和使用物件(也是框架底層核心),使用靈活,沒有反射機制,框架技術就失去底層支撐,
2. 缺點:使用反射基本是解釋執行,對執行速度有影響
五、傳統和反射呼叫方法性能比較及優化
/** * 測驗反射呼叫的性能,和優化方案 */ public class Reflection1 { public static void main(String[] args) throws Exception { m1(); m2(); m3(); } //傳統方法呼叫方法 public static void m1(){ Dog dog = new Dog(); long s = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { dog.eat(); } System.out.println("傳統呼叫方法用時"+(System.currentTimeMillis() - s)+"毫秒"); } //通過反射呼叫方法 public static void m2() throws Exception { Class<Dog> aClass = Dog.class; Dog dog1 = aClass.newInstance(); Method method = aClass.getMethod("eat"); long s = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { method.invoke(dog1);//反射呼叫方法 } System.out.println("反射呼叫方法用時"+(System.currentTimeMillis()-s)+"毫秒"); } //反射呼叫優化 (關閉呼叫檢查) public static void m3() throws Exception { Class<Dog> aClass = Dog.class; Dog dog1 = aClass.newInstance(); Method method = aClass.getMethod("eat"); method.setAccessible(true); //關閉呼叫檢查 long s = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { method.invoke(dog1);//反射呼叫方法 } System.out.println("反射優化后呼叫方法用時"+(System.currentTimeMillis()-s)+"毫秒"); } }

六、Class類系統介紹
1. Class類特點梳理
/** * 對Class類特點的梳理 */ public class Class01 { public static void main(String[] args) throws ClassNotFoundException { //1、Class也是類,因此也繼承Object類 //2、Class類物件不是new出來的而是系統創建出來的 //(1)傳統new物件 /* ClassLoader public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } */ Dog dog = new Dog(); //(2)反射方式 /* ClassLoader類,仍然是通過ClassLoader類加載Class物件 public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } */ Class<?> aClass1 = Class.forName("com.ycl.reflection.Dog"); //3、對于某個類的Class物件,在記憶體中只有一份,因為類只加載一次 Class<?> aClass2 = Class.forName("com.ycl.reflection.Dog"); System.out.println(aClass1.hashCode());// 81628611 System.out.println(aClass2.hashCode());// 81628611 //4、每個類的實體都會記得自己是由哪個 Class 實體所生成 //5、通過Class物件可以得到一個類的完整結構,通過一系列API //6、Class物件是存放在堆里的 //7、類的位元組碼進制資料,是放在方法區的,有的地方稱為類的元資料(包括 方法代碼,變數名,方法名,訪問權限等等) } }
2. Class類常用方法

代碼實作
/** * 演示Class類的一些常用方法 */ public class Class02 { public static void main(String[] args) throws Exception { String classPath="com.ycl.reflection.Car"; //1. 獲取到Car類對應的Class物件 //<?>表示不確定的Java型別 Class<?> cls = Class.forName(classPath); //2. 輸出cls System.out.println(cls);//顯示cls物件,是哪一個類的Class物件 class com.ycl.reflection.Car System.out.println(cls.getClass());//輸出運行型別 class java.lang.Class //3. 得到包名 System.out.println(cls.getPackageName()); //com.ycl.reflection //4. 得到全類名 System.out.println(cls.getName()); //com.ycl.reflection.Car //5. 通過cls創建物件實體 Car car = (Car) cls.newInstance(); System.out.println(car); //Car{band='BMW', price=500000, color='白色'} //6. 通過反射獲取屬性 brand Field brand= cls.getField("brand"); System.out.println(brand.get(car)); //BMW //7. 通過反射給屬性賦值 brand.set(car,"奧迪"); System.out.println(brand.get(car)); //奧迪 //8. 通過反射獲取所有的欄位屬性 Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field.getName()); } // brand price color } }
3. 獲取class類物件的六種方法
1. 前提:已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName(獲取,可能拋出ClassNotFoundException, 實體: Class cls1 = Class.forName( "java.lang.Cat" );
應用場景:多用于組態檔,讀取類全路徑,加載類.
2. 前提:若已知具體的類,通過類的class獲取,該方式最為安全可靠,程式性能最高,實體: Class cls2 = Cat.class;
應用場景:多用于引數傳遞,比如通過反射得到對應構造器物件.
3. 前提:已知某個類的實體,呼叫該實體的getClass(方法獲取Class物件,實體: Class clazz =物件.getClass(); //運行型別
應用場景:通過創建好的物件,獲取Class物件.
4. 通過類加載器獲取類的Class物件,具體實作看以下代碼展示
5. 基本資料(int, char,boolean,float,double,byte,long,short)按如下方式得到Class類物件,Class cls =基本資料型別.class
6.基本資料型別對應的包裝類,可以通過.TYPE得到Class類物件,Class cls =包裝類.TYPE
代碼實作
/** * 演示獲取Class物件的各種方式(6) */ public class GetClass_ { public static void main(String[] args) throws Exception { //1. Class.forName String classAllPath="com.ycl.reflection.Car";//實際運用時可以通過讀取組態檔獲取 Class<?> cls1 = Class.forName(classAllPath); System.out.println(cls1); //2. 類名.class ,應用場景:用于引數傳遞 Class<Car> cls2 = Car.class; System.out.println(cls2); //3. 物件.getClass 應用場景:有物件實體 Car car = new Car(); Class<? extends Car> cls3 = car.getClass(); System.out.println(cls3); //4. 通過類加載器【4種】,來獲取到類的Class物件 //(1)先得到類加載器 car的類加載器(類加載器是公用的) ClassLoader classLoader = car.getClass().getClassLoader(); //(2)通過類加載器得到Class物件 Class<?> cls4 = classLoader.loadClass(classAllPath); System.out.println(cls4); // cls1 cls2 cls3 cls4 其實是同一個物件 //5. 基本資料型別(byte、short、int、long、float、double、char、boolean)按以下方式得到Class類物件 Class<Integer> integerClass = int.class; Class<Character> characterClass = char.class; Class<Boolean> booleanClass = boolean.class; System.out.println(integerClass); //int //6. 基本資料型別對應的包裝類,可以通過.TYPE得到Class類物件 Class<Integer> type1 = Integer.TYPE; Class<Character> type2 = Character.TYPE; //int 和Integer 的Class物件其實是同一個 System.out.println(type1.hashCode()); //685325104 System.out.println(integerClass.hashCode()); //685325104 } }
4. 哪些型別有Class物件
1.外部類,成員內部類,靜態內部類,區域內部類,匿名內部類
2.interface :介面
3.陣列
4.enum :列舉
5.annotation :注解
6.基本資料型別
7.void
七、類加載
1. 基本說明
反射機制是java實作動態語言的關鍵,也就是通過反射實作類動態加載,
1.靜態加載:編譯時加載相關的類,如果沒有則報錯,依賴性太強
2.動態加載:運行時加載需要的類,如果運行時不用該類,即使不存在該類,則不報錯,降低了依賴性
2. 類加載時機
1.當創建物件時(new) //靜態加載
2.當子類被加載時,父類也加載 //靜態加載
3.呼叫類中的靜態成員時 //靜態加載
4.通過反射 //動態加載
3. 類加載流程圖

類加載各階段完成的任務

3.1 加載階段
JVM在該階段的主要目的是將位元組碼從不同的資料源(可能是class檔案、也可能是jar包,甚至網路)轉化為進制位元組流加載到記憶體中,并生成一個代表該類的java.lang.Class物件
3.2 連接階段-驗證
1.目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機的要求,并且不會危害虛擬機自身的安全,
2.包括:檔案格式驗證(是否以魔數oxcafebabe開頭)、元資料驗證、位元組碼驗證和符號參考驗證[舉例說明]
3.可以考慮使用-Xverify:none引數來關閉大部分的類驗證措施,縮短虛擬機類加載的時間,
3.3 連接階段-準備
JVM會在該階段對靜態變數,分配記憶體并默認初始化(對應資料型別的默認初始值,如0、OL、null、 false等) ,這些變數所使用的記憶體都將在方法區中進行分配
class A { //屬性-成員變數-欄位 //類加載的鏈接階段-準備 屬性是如何處理 //1. n1是實體屬性,不是靜態變數,因此在準備階段是不會分配內存 //2. n2是靜態變數,分配內存 n2 是默認初始化 0,而不是20 //3. n3是static final 是常量,他和靜態變數不一樣,因為一旦賦值就不變了 n3 = 30 public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; }
3.4 連接階段-決議
虛擬機將常量池內的符號參考替換為直接參考的程序
3.5 初始化階段
1.到初始化階段,才真正開始執行類中定義的Java程式代碼,此階段是執行<clinit> ()方法的程序,
2.< clinit> ()方法是由編譯器按陳述句在源檔案中出現的順序,依次自動收集類中的所有靜態變數的賦值動作和靜態代碼塊中的陳述句,并進行合并,
3.虛擬機會保證一個類的< clinit> ()方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類,那么只會有一個執行緒去執行這個類的<clinit> )方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit> ()方法完畢,
八、通過反射獲取類的結構資訊




九、通過反射創建物件
1.方式一:呼叫類中的public修飾的無參構造器
2.方式二:呼叫類中的指定構造器
3. Class類相關方法
newInstance :呼叫類中的無參構造器,獲取對應類的物件
getConstructor(Class..clazz):根據引數串列,獲取對應的構造器物件
getDecalaredConstructor(Class..clazz):根據引數串列,獲取對應的構造器物件
4. Constructor類相關方法
setAccessible:暴破
newlnstance(0bjec..obj):呼叫構造器
/** * 通過反射機創建實體 */ public class CreateNewInstance { public static void main(String[] args) throws Exception { //1、先獲取到User類的Class物件 Class<?> cls = Class.forName("com.ycl.reflection.User"); //2、通過public的無參構造器創造實體 Object o1 = cls.newInstance(); System.out.println(o1); // User{age=0, name='null'} //3、通過public的有參構造器創建實體 Constructor<?> constructor = cls.getConstructor(String.class); Object o2 = constructor.newInstance("張三"); System.out.println(o2); // User{age=0, name='張三'} //4、通過非public的有參構造器創建實體 Constructor<?> declaredConstructor = cls.getDeclaredConstructor(int.class, String.class); declaredConstructor.setAccessible(true); //暴破(暴力破解),使用反射可以訪問private 構造器,反射面前,都是紙老虎 Object o3 = declaredConstructor.newInstance(20, "李四"); System.out.println(o3);// User{age=20, name='李四'} } } class User{ private int age; private String name; public User(){ } public User(String name){ //public 有參構造 this.name=name; } private User(int age,String name){ //private 有參構造 this.age=age; this.name=name; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
十、通過反射操作屬性
/** * 反射操作屬性 */ public class ReflectionAccessProperty { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { //1、得到Student類對應的Class物件 Class<?> cls = Class.forName("com.ycl.reflection.Student"); //2、創建物件 Object o = cls.newInstance(); System.out.println(o.getClass()); //class com.ycl.reflection.Student //3、反射得到age 屬性物件 Field age = cls.getDeclaredField("age"); age.set(o,30); //反射設定屬性 System.out.println(o);// Student{age=30,name=null} //4、使用反射操作name 屬性 private static Field name = cls.getDeclaredField("name"); name.setAccessible(true);// 暴破 name.set(o,"YCL");// 以為name是static所以它在類加載的時候就已經有了(所有實體都有),也可以不用指定實體物件 o-->null System.out.println(o);// Student{age=30,name=YCL} System.out.println(name.get(o));//獲取屬性值 YCL System.out.println(name.get(null));//獲取屬性值,要求name是static } } class Student{ public int age; private static String name; public Student(){//構造器 } @Override public String toString() { return "Student{" + "age=" + age +","+ "name=" + name + '}'; } }
十一、通過反射呼叫方法
/** * 通過反射呼叫方法 */ public class ReflectionAccessMethod { public static void main(String[] args) throws Exception { //1、得到Person類對應的Class物件 Class<?> cls = Class.forName("com.ycl.reflection.Person"); //2、創建物件 Object o = cls.newInstance(); //3、呼叫public的hi() Method hi = cls.getDeclaredMethod("hi", String.class);//獲取hi()物件 hi.invoke(o,"你好");//呼叫方法,設定形參 //4、呼叫private static 方法 //得到say()的物件 Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);//獲取say()物件 say.setAccessible(true);// 暴破 System.out.println(say.invoke(o, 2, "王五", 's')); // 2 王五 s System.out.println(say.invoke(null, 200, "王五", '女')); //因為他是static方法所以實體物件引數可設定為null 200 王五 女 //5、在反射中,如果方法有回傳值,統一回傳Object Object reVal = say.invoke(null, 500, "YCL", '男'); System.out.println(reVal); // 500 YCL 男 } } class Person{ public int age; private static String name; public Person(){//構造器 } private static String say(int n,String s,char c){//靜態方法 return n+" "+s+" "+c; } public void hi(String s){ //普通方法 System.out.println("hi"+s); } @Override public String toString() { return "Person{" + "age=" + age +","+ "name=" + name + '}'; } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/423921.html
標籤:其他
