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