1. 程式人生 > >8千字乾貨教程|java反射精講

8千字乾貨教程|java反射精講

# java反射機制精講 # 目錄 **1. 反射機制的概念** **2. 反射的基礎Class類** **3. 反射的用法** **4. 反射的應用示例** 作者簡介:全棧學習筆記,一個正在努力的人 ## 反射機制的概念: 在執行狀態中,對於任意一個類,都能夠獲取到這個類的所有屬性和方法,對於任意一個物件,都能夠呼叫它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的資訊以及動態呼叫物件的方法的功能就稱為java語言的反射機制。反射被視為動態語言的關鍵。簡單來說反射就是java的各種成分對映成對應的java類。 通俗點講,通過反射,該類對我們來說是完全透明的,想要獲取任何東西都可以。包括構造方法,屬性,方法。 **java反射機制提供的功能**: 在執行時判斷任意一個物件所屬的類; 在執行時構造任意一個類的物件; 在執行時判斷任意一個類所具有的成員變數和方法; 在執行時呼叫任意一個物件的方法; 生成動態代理。 這其實也涉及到了語言的動態與靜態,java語言本身不算是動態語言,但是他有一個非常突出的動態機制,就是我們所說的反射機制。 什麼是動態語言呢?就是說,程式在執行的時候,(注意是執行的時候,不是編譯的時候)允許改變程式結構或者變數型別。反之靜態就是沒有這些特點了。 ## 反射的基礎Class類 ## Class類是反射實現的基礎,所以想要學會反射,必須先掌握Class類的一些基本的概念。 類是什麼?類是Class類的例項物件,所以說Class類是所有類的類。 要想解剖一個類,必須先獲取到該類的位元組碼檔案物件。而解剖使用的就是Class 類中的方法,所以先要獲取每一個位元組碼檔案對應的Class型別的物件。 Class類沒有公共的構造方法,Class物件是在類載入的時候由Java虛擬機器以及通過呼叫類載入器中的 defineClass 方法自動構造的,因此不能顯式地宣告一個Class物件。這裡又涉及到一個東西,類的載入 簡要的說明一下: 類載入器:當程式需要是用某個類時,如果該類還沒有被載入到記憶體中,則,系統會通過載入,連線,初始化 這三步來對類進行初始化 載入:就是指將class檔案讀入記憶體(編譯之後的檔案是.class檔案),併為之建立一個Class物件 任何類被使用時,系統都會建立一個Class物件,第一次的時候會,第二次則會判斷這個類是否存在。 連線:驗證是否有正確的內部結構,並和其他類協調一致 準備為類的靜態成員分配記憶體,並設定預設初始化值 並做一個解析:將類的二進位制資料中的字元引用替換為直接引用。 上面說到Class物件是不能直接建立的,但是我們可以通過其他方式得到Class類的,目前有三種方式可以得到我們想要的Class類,得到Class類之後就能正常的使用反射了。 獲取Class的三種方式(獲取一個類的位元組碼物件): 第一種:使用物件獲取,使用物件的getClass獲取 Person person = new Person(); Class clazz = person.getClass(); 第二種:使用靜態屬性class Class clazz = Person.class 第三種:使用Class類的靜態方法forName(字串的類名) 注;類名要寫全包名 Class clazz = Calss.forName("……."); 好了,重點來了,反射怎麼玩才有趣! ## 反射的用法 上面說了通過反射可以得到任意一個類的什麼什麼,下面來看看是不是真的。 **第一步要幹啥**?當然是通過之前的哪三種方法來得到這個可以為所欲為的Class類。有三種方法,我們先都做個示例吧! 上程式碼 //獲取Class第一種方法 Student student = new Student(); Class clazz = student.getClass(); //獲取Class第二種方法 Class clazzTwo = Student.class; //獲取Class第三種方法 Class clazzThree = Class.forName("demo.qzxxbj.entity.Student"); System.out.println("第一個"+clazz+"\n第二個"+clazzTwo+"\n第三個"+clazzThree); 結果 第一個class demo.qzxxbj.entity.Student 第二個class demo.qzxxbj.entity.Student 第三個class demo.qzxxbj.entity.Student 可以看到三種方法得到的Class物件是一樣的,沒有區別。 第三種方法是會有一個找不到類的異常丟擲的。 其中Student就是一個簡單的類,可以是任何類。 **通過Class獲取任意一個類的屬性** Student類的程式碼 package demo.qzxxbj.entity; /** * @author 微信公眾號:全棧學習筆記 * @date 2020/3/29 * @description */ public class Student { private String name; private Integer age; private String sex; public int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } } 以下獲取Class的方法都採用第二種,比較簡潔 //獲取Class第二種方法 Class clazzTwo = Student.class; //獲取該類指定屬性名的public成員變數,包括父類的 Field field = clazzTwo.getField("number"); //field public int demo.qzxxbj.entity.Student.number System.out.println("該類指定屬性名的public成員變數,包括父類的"+field); //獲取該類指定名稱宣告的變數,即不包括父類的 Field deField = clazzTwo.getDeclaredField("name"); // deField private java.lang.String demo.qzxxbj.entity.Student.name System.out.println("該類所有宣告的變數,即不包括父類的"+deField); //獲取該類所有的public宣告的成員變數 Field fields[] = clazzTwo.getFields(); System.out.println("public宣告的變數:"); //public int demo.qzxxbj.entity.Student.number for (Field field1:fields){ System.out.println(field1); } //獲取該物件的所有成員變數 Field deFields[] = clazzTwo.getDeclaredFields(); System.out.println("該物件的所有成員變數"); //private java.lang.String demo.qzxxbj.entity.Student.name //private java.lang.Integer demo.qzxxbj.entity.Student.age //private java.lang.String demo.qzxxbj.entity.Student.sex //public int demo.qzxxbj.entity.Student.number for (Field field1:deFields){ System.out.println(field1); } 記住getFields(),getField(String name),getDeclaredFields(),getDeclaredField(String name)的區別,你就能好好掌握這個知識點! **通過Class獲取任意成員方法** 還是看程式碼吧! 獲取成員方法Method //獲取Class第二種方法 Class clazzTwo = Student.class; //根據方法名以及引數型別獲取,只能獲取public宣告的方法,包括父類的 Method method = clazzTwo.getMethod("setAge",Integer.class); //public java.lang.Integer demo.qzxxbj.entity.Student.getAge() System.out.println(method); //根據方法名以及引數名稱獲取該類宣告的所有的屬性方法,不包括父類的 Method deMethod = clazzTwo.getDeclaredMethod("setAge", Integer.class); System.out.println(deMethod); //獲取該物件宣告的所有的public方法,包括父類的 Method methods[] = clazzTwo.getMethods(); //獲取該物件宣告的所有的方法,但是不包含父類的方法 Method deMethods[] = clazzTwo.getDeclaredMethods(); 一個Method方法打印出來是什麼呢?上面程式碼中也包含了 public void demo.qzxxbj.entity.Student.setAge(java.lang.Integer) 和之前講的Field是不是很相似。 既然說到了方法,那麼就肯定涉及到了**方法呼叫**,我們得到了這些方法,又該怎麼呼叫這個類裡面的方法呢?使用invoke函式,Method這個類裡面包含了一個invoke函式,英語好的就知道了,這個invoke的中文意思就是“呼叫”。 怎麼用呢? //獲取Class第二種方法 Class clazzTwo = Student.class; //根據方法名以及引數型別獲取,只能獲取public宣告的方法,包括父類的 Method method = clazzTwo.getMethod("setAge",Integer.class); //public java.lang.Integer demo.qzxxbj.entity.Student.getAge() System.out.println(method); //利用Class建立一個物件的例項 Student student = (Student) clazzTwo.newInstance(); //函式呼叫 Object value = method.invoke(student,20); //null System.out.println(value); 以上的程式碼,你可能會看不懂,我來講一下,首先,我們獲取一個類的Class,然後我們通過這個Class獲取該類的一個setAge方法,獲取到這個方法後繼續呼叫這個方法,呼叫方法是不是應該呼叫一個例項物件裡面的方法?所以我們需要先例項化一個物件,通過什麼方法呢,通過class裡面的newInstance(),建立一個例項,這種方法需要該例項化的類具有一個無參構造方法。還有其他方法也能建立一個例項,後面我們會說。創建出一個例項物件之後,我們就能開始呼叫方法了。 通過invoke對方法進行呼叫,invoke的第一個引數就是一個例項化物件,不然我去哪找這個方法。第二個引數,或者第三個,等等,後面的所有引數都是我呼叫的該方法所具有的引數,按照順序填進去就OK了。然後這個函式返回的是一個Object物件,你都能想到,我呼叫一個方法是不是要讓他做一些事,做了這些事需要返回一個東西,不知道這個東西是啥,就用Object獲取嘛。由於我們呼叫的這個方法不需要返回值,所以就是null了。很簡單是不是。學到了記得給我點個關注哦!精彩美文第一時間推送到你的手中。 **通過Class獲取構造方法** 這個被我放到了最後來學習,畢竟我覺得用的比例比較少。一起來學習一下怎麼用Class獲取構造方法,並呼叫他。 public Student(String name, int id) { this.name = name; this.id = id; } 這裡我們在Student類裡面添加了一個構造方法。 然後我們來獲取這個構造方法。 //獲取Class第二種方法 Class clazzTwo = Student.class; //獲取無參構造方法,public宣告的,包括父類,加上引數時就是獲取特定的構造方法 Constructor constructor = clazzTwo.getConstructor(); //public demo.qzxxbj.entity.Student() System.out.println(constructor); //獲取該類所有的public宣告的構造方法 Constructor constructors[] = clazzTwo.getConstructors(); //獲取指定引數的構造方法 Constructor deConstructor = clazzTwo.getDeclaredConstructor(String.class,Integer.class); //獲取所有的該類的構造方法,不包括父類的 Constructor deConstructors[] =clazzTwo.getDeclaredConstructors(); 上面程式碼應該很容易看懂吧,我就不細說了。這裡說一下如何使用得到的構造方法,構造方法顧名思義就是來例項化物件的,上面我們也有說到怎麼通過Class例項化一個物件,現在我們來通過構造方方法例項化一個物件 Student student = (Student) deConstructor.newInstance("全棧學習筆記",21); //21 System.out.println(student.getAge()); 現在知道了吧,我們差不都將反射的功能講完了,就差一個反射的動態代理,這個比較重要,會專門出一篇部落格,碼字不易。希望點個關注。微信公眾號:全棧學習筆記,精彩美文每天為你推送。 最後我根據我自己以前的經驗寫了一個java反射的sql語句拼接,相當於是一個反射的應用吧。 ## 反射的應用示例 ## 通過反射動態的生成SQL語句,是不是也有點牛逼的感覺? 直接上程式碼吧,我只發一個SQL語句,感興趣的可以私信我找我拿完整的程式碼! public String insert(Object object) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {` //insert into student(id,name,sex) values (1,"全棧學習筆記","男") StringBuilder sql = new StringBuilder(); Class clazz = object.getClass(); sql.append("insert into "); sql.append(clazz.getSimpleName()+"("); Field[] fields = clazz.getDeclaredFields(); for(Field field:fields){ sql.append(field.getName()+","); } sql.deleteCharAt(sql.length()-1); sql.append(")"); sql.append(" values ("); for(Field field:fields){ field.setAccessible(true); Object value = field.get(object); String fieldName = field.getName(); String str1 = fieldName.substring(0,1).toUpperCase(); String str2 = fieldName.substring(1,fieldName.length()); String strValue = str1.concat(str2); //String strValue = fieldName.substring(0,1).toUpperCase().concat(fieldName.substring(1,fieldName.length())); Method method = clazz.getMethod("get"+strValue,null); Object value1 = method.invoke(object,null); // if(value1.getClass().equals(String.class)) // if(field.getType().equals(String.class)) if(value1 instanceof String){ sql.append("\"").append(value1).append("\"").append(","); }else { sql.append(value1).append(","); } } sql.deleteCharAt(sql.length()-1); sql.append(")"); System.out.println(sql.toString()); return sql.toString(); } 本期的講解就到這裡,後面應該也會出一期關於註解的文章,如果你發現文章中有錯誤的地方,歡迎指出來哦!如果你覺得你能學到不少知識,請點個關注哦!歡迎轉發!讓更多的朋友學到! 微信公眾號:公眾號日更,精彩美文每天推送 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200330104618729.jpg#pic_