牛逼!不得不服,第一次有人把Java 反射机制讲解这么透!
反射概述
什么是反射
將類的各個組成部分封裝為其他對象的過程就叫做 反射,其中 組成部分 指的是我們類的 成員變量(Field)、構造方法(Constructor)、成員方法(Method)。
使用反射的優缺點
優點
在程序運行過程中可以操作類對象,增加了程序的靈活性;
解耦,從而提高程序的可擴展性,提高代碼的復用率,方便外部調用;
對于任何一個類,當知道它的類名后,就能夠知道這個類的所有屬性和方法;而對于任何一個對象,都能夠調用它的一個任意方法。
缺點
- 性能問題:Java 反射中包含了一些動態類型,JVM 無法對這些動態代碼進行優化,因此通過反射來操作的方式要比正常操作效率更低。
- 安全問題:使用反射時要求程序必須在一個沒有安全限制的環境中運行,如果程序有安全限制,就不能使用反射。
程序健壯性:反射允許代碼執行一些平常不被允許的操作,破壞了程序結構的抽象性,導致平臺發生變化時抽象的邏輯結構無法被識別。
Class 對象的獲取及使用
獲取 Class 對象的方式
Class.forName(“全類名”)
源代碼階段,它能將字節碼文件加載進內存中,然后返回 Class 對象,多用于配置文件中,將類名定義在配置文件中,通過讀取配置文件來加載類。
類名.class
類對象階段,通過類名的 class 屬性來獲取,多用于參數的傳遞。
對象.getClass()
運行時階段,getClass() 定義在 Object 類中,表明所有類都能使用該方法,多用于對象的獲取字節碼的方式。
我們首先定義一個 Person 類,用于后續反射功能的測試;
package com.cunyu;/*** @author : cunyu* @version : 1.0* @className : Person* @date : 2021/4/7 22:37* @description : Person 類*/public class Person {private int age;private String name;public long id;public long grade;protected float score;protected int rank;public Person(int age, String name, long id, long grade, float score, int rank) {this.age = age;this.name = name;this.id = id;this.grade = grade;this.score = score;this.rank = rank;}public Person() {}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;}public long getId() {return id;}public void setId(long id) {this.id = id;}public long getGrade() {return grade;}public void setGrade(long grade) {this.grade = grade;}public float getScore() {return score;} //加入Java開發交流君樣:756584822一起吹水聊天public void setScore(float score) {this.score = score;}public int getRank() {return rank;}public void setRank(int rank) {this.rank = rank;}@Overridepublic String toString() {final StringBuffer sb = new StringBuffer("Person{");sb.append("age=").append(age);sb.append(", name='").append(name).append('\'');sb.append(", id=").append(id);sb.append(", grade=").append(grade);sb.append(", score=").append(score);sb.append(", rank=").append(rank);sb.append('}');return sb.toString();} }定義好 Person 類之后,我們嘗試用 3 種不同的方式來獲取 Class 對象,并比較它們是否相同。
package com.cunyu;/*** @author : cunyu* @version : 1.0* @className : Demo1* @date : 2021/4/7 23:29* @description : Class 對象的獲取*/public class Demo1 {public static void main(String[] args) throws ClassNotFoundException { // 第一種方式,Class.forName("全類名")Class class1 = Class.forName("com.cunyu.Person");System.out.println(class1); //加入Java開發交流君樣:756584822一起吹水聊天 // 第二種方式,類名.classClass class2 = Person.class;System.out.println(class2);// 第三種方式,對象.getName()Person person = new Person();Class class3 = person.getClass();System.out.println(class3);// 比較三個對象是否相同System.out.println(class1 == class2);System.out.println(class1 == class3);} }
上述代碼中,會發現最后輸出的比較結果返回的是兩個 true,說明通過上述三種方式獲取的 Class 對象都是同一個,同一個字節碼文件(*.class)在一次運行過程中只會被加載一次。
Class 對象的使用
獲取成員變量
Field[] getFields()package com.cunyu;import java.lang.reflect.Field;/*** @author : cunyu* @version : 1.0* @className : Demo2* @date : 2021/4/7 23:39* @description : Class 對象的使用*/ //加入Java開發交流君樣:756584822一起吹水聊天 public class Demo2 {public static void main(String[] args) throws ClassNotFoundException {Class class1 = Class.forName("com.cunyu.Person");Field[] fields = class1.getFields();for (Field field : fields) {System.out.println(field);}} }
回顧下我們的 Person 類,可以發現 id、grade 成員變量都是被 public 所修飾的,說明該方法是用于獲取類中所有被 public 所修飾的成員變量(包括父類)。
從上面的結果分析可知,該方法只能用于獲取類中指定名稱的 public 所修飾的成員變量,對于 protected、private 所修飾的成員變量,該方法是無法獲取的(包括父類)。而獲取或設置成員變量值時,可以通過 get/set 方法來操作,具體操作方法如下。
觀察上面的結果可知,該方法可用于獲取指定的成員變量,不用考慮成員變量修飾符的限制(不包括父類)。但是在利用 set、get 方法來獲取和設置 private、protected 修飾的成員變量時,需要利用 setAccessible() 來忽略訪問全新啊修飾符的安全檢查,否則程序將會報錯。
獲取構造方法
package com.cunyu;import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;/*** @author : cunyu* @version : 1.0* @className : Demo3* @date : 2021/4/8 13:28* @description : 構造對象獲取*/public class Demo3 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class personClass = Class.forName("com.cunyu.Person");// 1. 獲取所有構造方法System.out.println("所有構造方法");Constructor[] constructors = personClass.getConstructors();for (Constructor constructor : constructors) {System.out.println(constructor);} //加入Java開發交流君樣:756584822一起吹水聊天 // 2. 獲取指定構造方法// 空參構造方法System.out.println("空參構造方法");Constructor constructor1 = personClass.getConstructor();System.out.println(constructor1); //` 帶參構造方法System.out.println("帶參構造方法");Constructor constructor2 = personClass.getConstructor(int.class, String.class, long.class, long.class, float.class, int.class);System.out.println(constructor2); //加入Java開發交流君樣:756584822一起吹水聊天 // 獲取構造方法后,可以利用它來創建對象System.out.println("空參創建對象"); // 第一種方法Object person = constructor1.newInstance();System.out.println(person); // 第二種方法Object person1 = personClass.newInstance();System.out.println(person1);System.out.println("帶參創建對象");Object object = constructor2.newInstance(20, "村雨遙", 1312020, 3, 99.0F, 2);System.out.println(object);} }
Constructor<?>[] getConstructors()
類似于通過 Class 實例來獲取成員變量,該方法用于獲取所有 public 所修飾的構造方法(包括父類);
Constructor<T> getConstructor(類<?>... parameterTypes)
該方法用于獲取某一指定參數類型后的 public 所修飾的構造方法(包括父類);
Constructor<?>[] getDeclaredConstructors()
該方法用于獲取所有 public 所修飾的構造方法(不包括父類);
Constructor<T> getDeclaredConstructor(類<?>... parameterTypes)
該方法用于獲取某一指定參數類型后的 public 所修飾的構造方法(不包括父類);
而獲取到構造方法之后,我們就可以利用 newInstance() 方法來創建類的實例。特殊的,如果我們的構造方法是無參的,此時則可以直接利用 Class.newInstance()來構造實例。
獲取成員方法
package com.cunyu;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;/*** @author : cunyu* @version : 1.0* @className : Demo4* @date : 2021/4/8 13:51* @description : 成員方法獲取*/public class Demo4 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {Class personClass = Class.forName("com.cunyu.Person");// 獲取所有 public 成員方法System.out.println("獲取所有成員方法");Method[] methods = personClass.getMethods();for (Method method : methods) {System.out.println(method);} //加入Java開發交流君樣:756584822一起吹水聊天 // 獲取指定名稱的方法System.out.println("獲取指定名稱的方法");Method getAgeMethod = personClass.getMethod("getAge");System.out.println(getAgeMethod);// 執行方法Person person = new Person(20, "村雨遙", 1312020, 3, 99.0F, 2);int age = (int) getAgeMethod.invoke(person);System.out.println(age);} }
Method[] getMethods()
用于獲取當前類的所有 public 所修飾的成員方法(包括父類)。
Method getMethod(String name, 類<?>… parameterTypes)
用于獲取當前類的某一個指定名稱 public 所修飾的成員方法(包括父類)。
Method[] getDeclaredMethods()
用于獲取當前類的所有 public 所修飾的成員方法(不包括父類)。
Method getDeclaredMethods(String name, 類<?>… parameterTypes)
用于獲取當前類的某一個指定名稱 public 所修飾的成員方法(不包括父類)。
而當我們獲取到類的成員方法后,如果要執行某一個方法,可以使用 invoke() 方法來執行該方法。
獲取類名
package com.cunyu;/*** @author : cunyu* @version : 1.0* @className : Demo5* @date : 2021/4/8 14:06* @description : 獲取類名*/ //加入Java開發交流君樣:756584822一起吹水聊天 public class Demo5 {public static void main(String[] args) throws ClassNotFoundException {Person person = new Person();Class personClass = person.getClass();String className = personClass.getName();System.out.println(className);} }
String getName()
從上述程序的結果可知,當我們獲取到 Class 對象之后,如果不知道類的全名,就可以使用 getName() 來獲取該類的全名。
反射實例
假設我們有如下需求:在不改變類的代碼的前提下,我們能夠創建任意類的對象,并執行其中的方法。
此時,我們可以通過 配置文件 + 反射 的方式來實現這一效果,而這也就是我們現在所用框架中的基礎,當我們使用反射后,只需要通過修改配置文件中的內容就能夠不用去改代碼就實現對應的功能。
假設我們有兩個類,一個 Student,一個 Teacher,兩者的定義如下;
package com.cunyu; //加入Java開發交流君樣:756584822一起吹水聊天 /*** @author : cunyu* @version : 1.0* @className : Teacher* @date : 2021/4/8 15:15* @description : 教師類*/public class Teacher {private String name;private int age;public void teach() {System.out.println("教書育人……");} }package com.cunyu; //加入Java開發交流君樣:756584822一起吹水聊天 /*** @author : cunyu* @version : 1.0* @className : Student* @date : 2021/4/8 15:16* @description : 學生類*/public class Student {private String name;private float score;public void study() {System.out.println("好好學習,天天向上……");} }要實現我們的需求,通常需要如下步驟:
將要創建對象的全類名和要執行的方法都配置在配置文件中;
定義的配置文件 prop.properties ,其中主要內容包括 className 和 methodName 兩個屬性,分別代表類的全類名和要調用方法的名字。一個具體實例如下,分別代表名為 Student 的類和名為 study 的方法。
className=com.cunyu.Student
methodName=study
然后在主方法中加載讀取配置文件;
// 創建配置文件對象 Properties properties = new Properties(); // 加載配置文件 ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("prop.properties"); properties.load(inputStream);// 獲取配置文件中定義的數據 String className = properties.getProperty("className"); String methodName = properties.getProperty("methodName"); 利用反射技術將類加載到內存中;// 加載進內存 Class name = Class.forName(className); 接著利用 newInstance() 方法創建對象;// 創建實例 Object object = name.newInstance(); 最后則是利用 invoke() 方法來執行方法; //加入Java開發交流君樣:756584822一起吹水聊天 // 獲取并執行方法 Method method = name.getMethod(methodName); method.invoke(object);將整個流程匯總起來就是:
package com.cunyu;import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties;/*** @author : cunyu* @version : 1.0* @className : ReflectTest* @date : 2021/4/8 15:27* @description : 測試*/public class ReflectTest {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException { // 創建配置文件對象Properties properties = new Properties(); // 加載配置文件ClassLoader classLoader = ReflectTest.class.getClassLoader();InputStream inputStream = classLoader.getResourceAsStream("prop.properties");properties.load(inputStream); //加入Java開發交流君樣:756584822一起吹水聊天 // 獲取配置文件中定義的數據String className = properties.getProperty("className");String methodName = properties.getProperty("methodName");// 加載進內存Class name = Class.forName(className);// 創建實例Object object = name.newInstance();// 獲取并執行方法Method method = name.getMethod(methodName);method.invoke(object);} }此時,我們只需要改動配置文件 prop.properties 中的配置即可輸出不同結果;
`
最后,祝大家早日學有所成,拿到滿意offer
總結
以上是生活随笔為你收集整理的牛逼!不得不服,第一次有人把Java 反射机制讲解这么透!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 兴业银行随借金怎么还
- 下一篇: java美元兑换,(Java实现) 美元