java的反射機制,看完這篇輕松應對高級框架(超詳細總結)


導讀:很多優秀的高級框架都是通過反射完成的,反射的重要性,由此可見一斑。反射機制可以使得程序更加靈活,只有學習好反射的基礎語法,這樣才能自己寫出優秀的框架。好了一起打卡學習吧,別忘記了素質三連哦!

1.反射機制概述

  • 反射機制有什么作用?
    • java語言中反射機制可以操作字節碼文件。
    • 優點:可以直接讀和修改字節碼文件。通過反射機制可以操作class文件。
    • 反射在框架中使用很多,掌握它,你注定不凡。
  • 在java.lang.reflect.*;包下。
  • 反射機制相關重要的類:
    • java.lang.Class:代表字節碼,代表一個類型(整個類)。
    • java.lang.reflect.Method:代表字節碼中的方法字節碼(類中的方法)
    • java.lang.reflect.Constructor:代表字節碼中的構造方法字節碼(類中的構造方法)
    • java.lang.reflect.Field:代表字節碼中的屬性字節碼。(類中的靜態變量和成員變量)
  • 要操作一個類的字節碼,需要首先獲取到這個類的字節碼,那如何獲取java.lang.Class實例呢?
    • 有三種方式:
    • 第一種:class.forName(“ ”)方法:靜態方法,方法的參數是一個字符串(完整的類名),不能省略包。返回一個Class類型.(lang.class字節碼文件)。使用這個方法會加載類到JVM,使static靜態代碼塊執行(只執行一次),如果你只想讓程序執行static靜態代碼塊,只用這個方法就好了。(重點,JDBC中會用)
    • 第二種:getClass()方法,任何一個對象都有這個方法。(返回字節碼文件)同一個類的對象返回的java.lang.Class字節碼相同(所指的地址相同)。
    • 第三種:java語言中任何一中類型,包括基本數據類型,他都有.class屬性。例如:
      • Class x = String.class;
      • Class a = int.class;

2.通過反射機制實例化對象

  • 通過反射機制,獲取Class,通過Class來實例化對象.
    • newInstance()方法:創建一個和之前反射相同類型的對象,會自動調用類的無參構造方法。沒有無參構造方法,會報異常。(JDK8之后過時了,不過現在主要用的就是JDK8,sun公司長期支持JDK8和JDK11)
    • 舉個栗子:
package Day2; public class ReflectTest1 { public static void main(String[] args) { Class s1 = null; try { //通過反射機制獲取Class s1= Class.forName("Day2.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Object a=null; try { a =s1.newInstance();//用反射獲得的Class,創建一個對象 System.out.println(a); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class User{ public User() { System.out.println("無參構造方法!"); } } 
  • 使用反射機制創建對象有什么作用呢?
    • 反射機制讓程序更加靈活。
    • java代碼寫一遍,在不改變java源代碼的基礎上,可以做到不同對象的實例化。(符合OCP原則:對外開放,修改關閉。)
    • 高級框架:底層實現原理:采用了反射機制。
    • 舉個栗子:(使用IO流,properties集合,通過屬性文件來實例化對象)(重點)
package Day2; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class ReflectTest2 { public static void main(String[] args) throws Exception { //通過IO流讀取flie.properties文件 FileReader reader = new FileReader("file.properties"); //創建屬性類對象(Properties對象,Map集合,key和value都是字符串類型 Properties pro = new Properties(); //將文件中的數據加載到Map集合中加載 pro.load(reader); reader.close();//這個流不能在沒加載數據之前就關閉了 //通過key獲取value String a = pro.getProperty("ClassName"); Class bb = Class.forName(a);//反射獲得Class Object c = bb.newInstance();//通過Class,新建一個對象 System.out.println(c); } } class User1 { public User1() { System.out.println("無參構造方法!"); } } 
  • 如何獲取類路徑下文件的絕對路徑?
    • 為什么聊這個?因為上述代碼可能換一個位置,當前路徑名就失效了,(即移植性差)所以我們的聊聊這個。
    • 使用以下通用方式,這個文件必須保存在類路徑下,(即src目錄下)。
    • String path = Thread.currentThread().getContextClassLoader().getResource(" ").getPath();(重點)使用這個方法可以獲取文件的絕對路徑。(適用於各種操作系統)
    • 哈哈,看不懂?別急解釋一下上述程序:
    • Thread.currentThread()當前線程對象
    • getContextClassLoader()使對象的方法,可以獲取當前線程的類加載器對象。
    • getResource()這是類加載器對象的方法,當前線程的類加載器默認從類的根路徑下加載資源。
    • 示例:
public class Path1 { public static void main(String[] args) { String path = Thread.currentThread().getContextClassLoader().getResource("File2").getPath(); System.out.println(path); } } 
  • 從執行結果可以看出:源文件和字節碼文件存放在不同的位置,執行后與之對應的文件保存在out/production目錄下(IDEA中)。
  • 直接以流的方式讀取文件,省去獲取絕對路徑的環節。
    • InputStream reader = Thread.currentThread().getContextClassLoader().getResoutceAsStream("文件名“(類路徑下))
    • 舉個栗子:
package Day2; import java.io.InputStream; import java.util.Properties; public class Path2 { public static void main(String[] args) throws Exception { //創建一個流,讀取文件 InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("File2"); //創建一個Properties集合,保存數據 Properties pro = new Properties(); pro.load(reader);//將文件數據加載到集合中 reader.close(); String f =pro.getProperty("NewName"); Class bb = Class.forName(f);//反射獲得Class Object cc = bb.newInstance();//通過Class實例化對象 System.out.println(cc); } } class User2 { public User2() { System.out.println("無參構造方法!"); } } 
  • 資源綁定器(重點)
    • 在java.util包下,便於獲取屬性配置文件的內容。
    • 使用這種方式也必須將屬性配置文件放置類路徑下。
    • 只能綁定xxx.properties文件。並且不用寫路徑的擴展名。
    • 此方法簡潔簡單。所以,以后就用這個方法就好了,集合配合流的方法就不需要用了。
    • 舉個栗子:
import java.util.ResourceBundle; public class Path3 { public static void main(String[] args) { ResourceBundle bundle = ResourceBundle.getBundle("File2"); String Name = bundle.getString("NewName"); System.out.println(Name); } } 

3.反射Field(屬性)獲取字節碼

  • 反射屬性之前,應該獲取整個類。(Class.forName(" ")方法獲取。)
  • 獲取整個類中的所有的Field。
    • **getFields()方法;用之前獲取的整個類調用;**其獲取所有public修飾的屬性,返回到Field[]數組中。
    • **getDeclaredFields()方法;獲取所有屬性。**不受修飾符現在,返回到Field[]數組中。
    • getName()方法:可以獲取屬性名字。對於類也有名字,也用這個方法,對於反射獲得的類還有simpleName()方法,來獲取簡單名字。
    • **getType()方法:獲取屬性的類型,返回值為類,**然后我們再通過這個得到的類用getName方法獲取屬性類型的名字。
    • getModifiers()方法:獲取屬性的修飾符列表,返回的是int類型,每個修飾符的數字是,修飾符的代號。我們可以用toString方法(Modifier.toString),Modefier為reflect的子類,返回一個字符串類型。
    • 舉個栗子:
package Day1; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class FieldTest1 { public static void main(String[] args) { Class First=null; try { First = Class.forName("Day1.Student"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Field[] fields = First.getDeclaredFields(); System.out.println(fields.length); for(Field arg:fields) { int a = arg.getModifiers(); System.out.println(Modifier.toString(a));//Modefier為reflect的子類 Class ff = arg.getType(); System.out.println(ff.getName()); System.out.println(arg.getName()); } } } class Student{ public String name; private int age; protected double scroe; boolean flag; } 
  • 通過反射機制,反編譯一個類的屬性Field
  • 怎么通過反射機制訪問對象屬性?(賦值,獲取屬性的值)
    • 首先獲取整個Class。
    • 然后通過Class實例化對象。
    • 獲取對象的屬性;依據名字。
    • 屬性.set(對象,222)方法:以獲取的屬性調用,給對象的屬性賦值。
    • 反射機制代碼復雜,但靈活。
    • 讀取屬性的值:屬性.get(對象)。
    • 舉個栗子:
package Day1; import java.lang.reflect.Field; public class FieldTest2 { public static void main(String[] args) { Class fio = null;//獲取Class Object ff = null; { try { fio = Class.forName("Day1.Student"); ff = fio.newInstance();//實例化對象 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } //獲取屬性 Field fl = null; { try { fl = fio.getDeclaredField("name");//通過類獲取屬性,無法通過對象獲取 } catch (NoSuchFieldException e) { e.printStackTrace(); } } //給fio對象的fl屬性賦值 try { fl.set(ff,"sfdsa"); } catch (IllegalAccessException e) { e.printStackTrace(); } //獲取屬性的值 try { System.out.println(fl.get(ff)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } 
  • 可以用上述方法訪問私有屬性嗎?
    • 不可以?當時我們可以用:屬性.setAccessible(true)方法打破封裝。
    • 因此安全性差。

4.反射Mothod(方法)

反射Mothod

  • 首先獲取Class。
  • 獲取所有的Method方法。
  • Method[] methods=類.getDeclaredMethod();方法:獲取所有的Method.
  • 獲取方法的返回值類型:方法.getReturnType().getSimpleName();
  • 獲取修飾符列表:Modifer.toString(方法.getModifiers();
  • 獲取方法參數的類型:方法.getParameterTypes();返回Class[]數組。再調用getSimpleName()即可。
  • Mothod的反編譯(不要求掌握,有興趣的小伙伴可以自行了解一下)
  • 通過反射機制怎么調用對象的方法。(重點)
  • 1.獲取Calss,並用Calss實例化對象。
  • 2.獲取Method:使用Method logMethod = 類.getDeclaredMethod("方法名“,類型名.class,類型名.class);方法。
  • 3.調用方法:(對象,方法名,參數,返回值)
    • Object value = 方法.invoke("對象”,“參數值”,“參數值”);invoke方法返回一個Object類型。
    • 舉個栗子:
package Day1; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MethodTest1 { public static void main(String[] args) { Class fos =null; try { fos = Class.forName("Day1.Student1");//獲取Class } catch (ClassNotFoundException e) { e.printStackTrace(); } Object fis =null; try { fis = fos.newInstance();//創建對象 } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } Method method =null; try { method = fos.getDeclaredMethod("GetScore",String.class,double.class);//獲取方法 } catch (NoSuchMethodException e) { e.printStackTrace(); } Object value = null; try { value = method.invoke(fis,"jia",520);//調用方法 } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } System.out.println(value); } } class Student1{ private String name; private int age; private double score; public String GetScore(String name,double score){ if(score>500){ System.out.println("恭喜成功!"); } else { System.out.println("繼續加油!"); } return name; } } 
  • 反射機制的優點:讓代碼具有通用性,可變化的內容都是寫到配置文件中,將來修改配置文件之后,創建的對象不一樣了,調用的方法也不同了,但是java代碼不需要做任何修改。非常的靈活。

5.反射Constructor(構造方法)

  • 反射機制怎么調用構造方法?
  • 使用反射機制創建對象?
    • 1.調用無參構造方法。(newInstance方法)
    • 2.調用有參構造方法(也可以用這種方法調用無參構造方法)。
    • 第一步:先獲取這個有參構造方法;Constructor con = 類.getDeclaredConstructor(int.class,String.classs)第二步:調用構造方法new對象。Object aa =con.newInstance(參數列表)。
    • 舉個栗子:
package Day1; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class ConstructorTest1 { public static void main(String[] args){ Class fio=null; Constructor con=null; Object cc = null; try { //獲取類Class fio = Class.forName("Day1.Student3"); //獲取構造方法,參數很重要 con =fio.getDeclaredConstructor(int.class,String.class); cc= con.newInstance(11,"Jia");//調用構造方法new對象,這種方法沒有過期 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } //輸出對象 System.out.println(cc);//自動調用toString方法 } } class Student3{ private int age; private String name; //構造方法 public Student3() { } public Student3(int age, String name) { this.age = age; this.name = name; System.out.println("調用成功!"); } } 

6.通過反射獲取父類和父接口

  • 1.獲取類的Class
  • 2.獲取Class父類:Class superClass = 子類.getSuperclass();
  • 獲取子類實現的所有接口:Class[] interfaces = 子類.getInterfaces();方法。
  • 舉個栗子:
package Day1; public class SuperClassTest1 { public static void main(String[] args){ Class fos =null; Class superClass=null; try { //獲取類的Class fos= Class.forName("Day1.Student5"); //獲取父類的Class superClass = fos.getSuperclass(); //實例化父類對象 Object cc = superClass.newInstance(); System.out.println(cc); //獲取子類的所有接口 Class[] interfaces = superClass.getInterfaces(); for(Class ff : interfaces){ //獲取接口的簡稱,即不含包名 System.out.println(ff.getSimpleName()); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } } //學生類 class Student5 extends Parents{ private int age; private String name; //構造方法 public Student5() { } public Student5(int age, String name) { this.age = age; this.name = name; System.out.println("調用成功!"); } } //家長類 class Parents implements Study,Sport{ public Parents() { super(); } } //接口 interface Study{ } interface Sport{ } 

7.擴展知識

1.類加載器

  • 什么是類加載器?
    • 專門負責加載類的命令/工具。
    • ClassLoader
  • JDK中自帶了3個類加載器
    • 啟動類加載器(加載jre\lib\rt.jar)父
    • 擴展類加載器(母)
    • 應用類加載器
  • 假設有這樣一段代碼:
  • String s = “abc”;
    • 代碼在開始執行之前,會將所需要的類全部加載JVM。看到以上代碼,類加載器會找到String.class文件,找到就加載,那么是怎么進行加載的呢?
    • 首先通過:啟動類加載器”加載。
    • rt.jar中都是Jdk最基本的核心類庫。
    • 如果通過“啟動類加載器"加載不到的時候:會通過”擴展類加載器“加載。
    • 注意:擴展類加載器專門加載:jre\lib\ext*.jar
    • 如果擴展類加載器沒有加載到,那么會通過“應用類加載器”加載。
    • 應用類加載器:classpath中的jar包的類。(class文件)
  • java中為了保證類加載的安全,使用了雙親委派機制。
    • 優先從啟動類,(父)然后是擴展類(母),最后是應用類加載器。

2.可變長度參數

  • int…args:這就是可變長度參數。個數為0到N個。
  • 可變長只能在參數的最后一個,且只能有一個。
  • 可變成長度參數可以當做數組看待,具有length屬性,有下標,調用的時候可以傳一個數組。

3.注解

  • 注解:或者叫做注釋(Annotation)
  • 注解有什么作用?
    • 對程序的注釋。
  • 注解是一個引用數據類型。編譯之后也是生成xxx.class文件。
  • 怎么自定義注解呢?語法格式是怎么樣的?
    • [修飾符列表] @interface 注解類型名{
      }
  • 注解怎么使用,用在什么地方?
  • 注解使用時語法格式時:@注解類型名
  • 可以用在構造方法,類,屬性,接口,方法,注解等,注解可以修飾注解。
  • 自定義注解:
    • 如果注解中有屬性,必須給其賦值。可以在注釋的時候@注解名(屬性=值),屬性名為value時並且只有一個屬性的時候,注解的時候可以不寫value。定義屬性的時候后面需要小括號,可以用default給屬性賦默認值。
  • 注解的屬性可以是哪些類型?
    • byte,short,int, long,double,boolean,char,float,String,枚舉,Class類型,和其數組類型。如果數組中只有一個元素給其賦值時大括號可以省略。
  • 什么是元注解?
    • 用來標注“注解類型”的注解。
  • 常見的元注解:
    • Target注解:(用來標注,被注解的注解可以出現在哪個位置上,其由后面的小括號參數決定。)如:@Target({ElementType.Type,ElementType.MEYHOD})只允許該注解標注類,方法
    • Retention注解:用來標注“被注解的注解”最終保存在哪里。
    • @Retention(RetentionPolicy,SOURCE)表示只被保留在java源文件中。
    • @Retention(RetentionPolicy,CLASS)class文件中。
    • @Retention(RetentionPolicy,RUNTIME)class文件中,並能被注解。//RetentionPolicy是一個枚舉類型。
  • JDK內置的注解。
  • java.lang包下:
  • 1.Override注解:只能注解方法,只給編譯器參考。如果這個方法不是重寫父類的方法,編譯器報錯。(標識性注解)
  • 2.Deprecated注解:表示某個類,方法已過時。為了告知別人某個類,方法等已過時。

反射注解

如何獲取注解類的屬性?
1.獲取類Class
* Class dd = Class.forName(“完整類名”);
2.判斷類上面是否存在特定的注解。
* if(dd(類).isAnnotationPresent(注解類名.class)
3.如果存在特定的注解,則獲取注解對象。
* 注解類名 fs = (注解類名)dd.getAnnotation(注解類名.class);
4.獲取注解對象的屬性。
* 注解對象.屬性();
如果獲取方法上的注解信息?
1.獲取類Class。
2.獲取方法。
3.判斷方法上是否存在該注解。存在的話,獲取方法上的注解。調用方式為:方法.getAnnotation(注解類名.class)
4.再通過得到的注解獲得屬性。
注解在開發中的使用場景:
舉個栗子:假設存在一個注解,只能出現在類上面;當這個類有這個注解:類必須有int類型的屬性,否則報異常。

原創不易,碼字不易,點個贊再走吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM