一文搞懂Java中的反射機(jī)制
前一段時(shí)間一直忙,所以沒什么時(shí)間寫博客,拖了這么久,也該更新更新了。最近看到各種知識(shí)付費(fèi)的推出,感覺是好事,也是壞事,好事是對(duì)知識(shí)沉淀的認(rèn)可與推動(dòng),壞事是感覺很多人忙于把自己的知識(shí)變現(xiàn),相對(duì)的在沉淀上做的實(shí)際還不夠,我對(duì)此暫時(shí)還沒有什么想法,總覺得,慢慢來,會(huì)更快一點(diǎn),自己掌握好節(jié)奏就好。
好了,言歸正傳。
反射機(jī)制是Java中的一個(gè)很強(qiáng)大的特性,可以在運(yùn)行時(shí)獲取類的信息,比如說類的父類,接口,全部方法名及參數(shù),全部常量和變量,可以說類在反射面前已經(jīng)衣不遮體了(咳咳,這是正規(guī)車)。先舉一個(gè)小栗子,大家隨意感受一下:
public void testA(){
String name = "java.lang.String";
try{
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0){
System.out.print(modifiers + " ");
}
System.out.print(name);
if (supercl != null && supercl != Object.class){
System.out.print(" extents " + supercl.getName());
}
System.out.print("{\n");
printFields(cl);
System.out.println();
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println("}");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
System.exit(0);
}
private static void printConstructors(Class cl){
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors){
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0){
System.out.print(modifiers + " ");
}
System.out.print(name + "(");
Class[] paraTypes = c.getParameterTypes();
for (int j = 0; j < paraTypes.length; j++){
if (j > 0){
System.out.print(", ");
}
System.out.print(paraTypes[j].getSimpleName());
}
System.out.println(");");
}
}
private static void printMethods(Class cl){
Method[] methods = cl.getDeclaredMethods();
for (Method m : methods){
Class retType = m.getReturnType();
String name = m.getName();
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0){
System.out.print(modifiers + " ");
}
System.out.print(retType.getSimpleName() + " " + name +"(");
Class[] paramTypes = m.getParameterTypes();
for(int j = 0; j < paramTypes.length; j++){
if (j > 0){
System.out.print(", ");
}
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
private static void printFields(Class cl){
Field[] fields = cl.getFields();
for (Field f : fields){
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length() > 0){
System.out.print(modifiers + " ");
}
System.out.println(type.getSimpleName() + " " + name +";");
}
}
調(diào)用testA方法輸出如下:
public final java.lang.String{
public static final Comparator CASE_INSENSITIVE_ORDER;
public java.lang.String(byte[], int, int);
public java.lang.String(byte[], Charset);
public java.lang.String(byte[], String);
public java.lang.String(byte[], int, int, Charset);
public java.lang.String(byte[], int, int, String);
java.lang.String(char[], boolean);
public java.lang.String(StringBuilder);
public java.lang.String(StringBuffer);
public java.lang.String(byte[]);
public java.lang.String(int[], int, int);
public java.lang.String();
public java.lang.String(char[]);
public java.lang.String(String);
public java.lang.String(char[], int, int);
public java.lang.String(byte[], int);
public java.lang.String(byte[], int, int, int);
public boolean equals(java.lang.Object);
public String toString();
public int hashCode();
public int compareTo(java.lang.String);
public volatile int compareTo(java.lang.Object);
public int indexOf(java.lang.String, int);
public int indexOf(java.lang.String);
public int indexOf(int, int);
public int indexOf(int);
static int indexOf([C, int, int, [C, int, int, int);
static int indexOf([C, int, int, java.lang.String, int);
public static String valueOf(int);
public static String valueOf(long);
public static String valueOf(float);
public static String valueOf(boolean);
public static String valueOf([C);
public static String valueOf([C, int, int);
public static String valueOf(java.lang.Object);
public static String valueOf(char);
public static String valueOf(double);
public char charAt(int);
private static void checkBounds([B, int, int);
public int codePointAt(int);
public int codePointBefore(int);
public int codePointCount(int, int);
public int compareToIgnoreCase(java.lang.String);
public String concat(java.lang.String);
public boolean contains(java.lang.CharSequence);
public boolean contentEquals(java.lang.CharSequence);
public boolean contentEquals(java.lang.StringBuffer);
public static String copyValueOf([C);
public static String copyValueOf([C, int, int);
public boolean endsWith(java.lang.String);
public boolean equalsIgnoreCase(java.lang.String);
public static transient String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;);
public static transient String format(java.lang.String, [Ljava.lang.Object;);
public void getBytes(int, int, [B, int);
public byte[] getBytes(java.nio.charset.Charset);
public byte[] getBytes(java.lang.String);
public byte[] getBytes();
public void getChars(int, int, [C, int);
void getChars([C, int);
private int indexOfSupplementary(int, int);
public native String intern();
public boolean isEmpty();
public static transient String join(java.lang.CharSequence, [Ljava.lang.CharSequence;);
public static String join(java.lang.CharSequence, java.lang.Iterable);
public int lastIndexOf(int);
public int lastIndexOf(java.lang.String);
static int lastIndexOf([C, int, int, java.lang.String, int);
public int lastIndexOf(java.lang.String, int);
public int lastIndexOf(int, int);
static int lastIndexOf([C, int, int, [C, int, int, int);
private int lastIndexOfSupplementary(int, int);
public int length();
public boolean matches(java.lang.String);
private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder);
public int offsetByCodePoints(int, int);
public boolean regionMatches(int, java.lang.String, int, int);
public boolean regionMatches(boolean, int, java.lang.String, int, int);
public String replace(char, char);
public String replace(java.lang.CharSequence, java.lang.CharSequence);
public String replaceAll(java.lang.String, java.lang.String);
public String replaceFirst(java.lang.String, java.lang.String);
public String[] split(java.lang.String);
public String[] split(java.lang.String, int);
public boolean startsWith(java.lang.String, int);
public boolean startsWith(java.lang.String);
public CharSequence subSequence(int, int);
public String substring(int);
public String substring(int, int);
public char[] toCharArray();
public String toLowerCase(java.util.Locale);
public String toLowerCase();
public String toUpperCase();
public String toUpperCase(java.util.Locale);
public String trim();
}
這里把String類型的所有方法和變量都獲取到了,使用的僅僅是String類型的全名。當(dāng)然,反射的功能不僅僅是獲取類的信息,還可以在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建對(duì)象,回想一下,我們正常的對(duì)象使用,都是需要在代碼中先聲明,然后才能使用它,但是使用反射后,就能在運(yùn)行期間動(dòng)態(tài)創(chuàng)建對(duì)象并調(diào)用其中的方法,甚至還能直接查看類的私有成員變量,還能獲取類的注解信息,在泛型中類型判斷時(shí)也經(jīng)常會(huì)用到。反射可以說完全打破了類的封裝性,把類的信息全部暴露了出來。
上面的代碼看不太明白也沒關(guān)系,只要稍微感受一下反射的能力就好了。介紹完了反射能做的事情,本篇教程就不再寫一些玩具代碼了,這次以一個(gè)實(shí)用型的代碼為媒介來介紹反射。
在開發(fā)中,經(jīng)常會(huì)遇到兩個(gè)不同類對(duì)象之間的復(fù)制,把一個(gè)類中的字段信息get取出來,然后set到另一個(gè)類中,大部分情況下,兩個(gè)類對(duì)應(yīng)的字段是一樣,每次這樣使用是很麻煩的,那么利用反射就可以實(shí)現(xiàn)一個(gè)封裝,只需要調(diào)用一個(gè)方法即可實(shí)現(xiàn)簡單的類字段復(fù)制。
那么,先來想想,要復(fù)制一個(gè)類對(duì)象的所有字段信息到另一個(gè)類對(duì)象中,首先,怎么獲取一個(gè)類的某個(gè)字段的值呢?我們先來編寫一個(gè)方法:
/**
* 獲取對(duì)象的指定字段的值
* @param obj 目標(biāo)對(duì)象
* @param fieldName 目標(biāo)字段
* @return 返回字段值
* @throws Exception 可能拋出異常
*/
private static Object getFieldValue(Object obj, String fieldName) throws Exception{
//獲取類型信息
Class clazz = obj.getClass();
//取對(duì)應(yīng)的字段信息
Field field = clazz.getDeclaredField(fieldName);
//設(shè)置可訪問權(quán)限
field.setAccessible(true);
//取字段值
Object value = field.get(obj);
return value;
}
這里使用了兩個(gè)之前沒有說過的類,一個(gè)是Class,是不是很眼熟,想一想,我們每次定義一個(gè)類的時(shí)候是不是都要用到它,哈哈,那你就想錯(cuò)了,那是class關(guān)鍵詞,java是大小寫的敏感的,這里的Class是一個(gè)類名,那這個(gè)類是干嘛用的呢?
虛擬機(jī)在加載每一個(gè)類的時(shí)候,會(huì)自動(dòng)生成一個(gè)對(duì)應(yīng)的Class類來保存該類的信息,可以理解為Class類是那個(gè)類的代理類,是連接實(shí)際類與類加載器的橋梁,可以通過它來獲取虛擬機(jī)的類加載器引用,從而實(shí)現(xiàn)更多的騷操作。Class類是一個(gè)泛型類,每個(gè)類都有對(duì)應(yīng)的一個(gè)Class類,比如String對(duì)應(yīng)的Class類就是Class<String>。
Class有很多方法來獲取更多關(guān)于類的信息,這里使用getDeclaredField方法來獲取指定字段信息,返回的是Field類型對(duì)象,這個(gè)對(duì)象里存儲(chǔ)著關(guān)于字段的一些信息,如字段名稱,字段類型,字段修飾符,字段可訪問性等,setAccessible方法可以設(shè)置字段的可訪問性質(zhì),這樣就能直接訪問private修飾的字段了,然后使用get方法來獲取指定對(duì)象的對(duì)應(yīng)字段的值。
我們來測(cè)試一下:
public void testB(){
try{
Employee employee = new Employee();
employee.setName("Frank");
employee.setSalary(6666.66);
System.out.println((String)getFieldValue(employee,"name"));
System.out.println((double)getFieldValue(employee,"salary"));
}catch (Exception e){
e.printStackTrace();
}
}
輸出如下:
Frank
6666.66
接下來,我們需要獲取類中所有字段,然后在另一個(gè)類中查找是否有對(duì)應(yīng)字段,如果有的話就設(shè)置字段的值到相應(yīng)的字段中。
/**
* 復(fù)制一個(gè)類對(duì)象屬性到另一個(gè)類對(duì)象中
* @param objA 需要復(fù)制的對(duì)象
* @param objB 復(fù)制到的目標(biāo)對(duì)象類型
* @return 返回復(fù)制后的目標(biāo)對(duì)象
*/
private static void parseObj(Object objA,Object objB) throws Exception{
if (objA == null){
return;
}
//獲取objA的類信息
Class classA = objA.getClass();
Class classB = objB.getClass();
try {
//獲取objA的所有字段
Field[] fieldsA = classA.getDeclaredFields();
//獲取objB的所有字段
Field[] fieldsB = classB.getDeclaredFields();
if (fieldsA == null || fieldsA.length <= 0 || fieldsB == null || fieldsB.length <= 0){
return;
}
//生成查詢map
Map<String,Field> fieldMap = new HashMap<>();
for (Field field:fieldsA){
fieldMap.put(field.getName(),field);
}
//開始復(fù)制字段信息
for (Field fieldB : fieldsB){
//查找是否在objB的字段中存在該字段
Field fielaA = fieldMap.get(fieldB.getName());
if (fielaA != null){
fieldB.setAccessible(true);
fieldB.set(objB,getFieldValue(objA,fielaA.getName()));
}
}
} catch (IllegalStateException e) {
throw new IllegalStateException("instace fail: " ,e);
}
}
這里獲取到classA和classB的所有字段之后,先生成了一個(gè)map用于查找,可以減少遍歷次數(shù),然后之后只需要遍歷一次就可以判斷相應(yīng)字段是否存在,如果存在則取出對(duì)應(yīng)值設(shè)置到相應(yīng)的字段里去。
接下來測(cè)試一下:
public void testB(){
try{
//生成Employee對(duì)象
Employee employee = new Employee("Frank",6666.66);
//生成一個(gè)Manager對(duì)象
Manager manager = new Manager();
//復(fù)制對(duì)象
parseObj(employee,manager);
System.out.println(manager.getName());
System.out.println(manager.getSalary());
}catch (Exception e){
e.printStackTrace();
}
}
public class Employee {
private String name;
private Double salary;
public Employee(String name, Double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
}
public class Manager {
private String name;
private Double salary;
private Double bonus;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Double getBonus() {
return bonus;
}
public void setBonus(Double bonus) {
this.bonus = bonus;
}
}
輸出如下:
Frank
6666.66
完美,這樣我們就利用了反射機(jī)制完美的把相同的字段在不同類的對(duì)象之間進(jìn)行了復(fù)制,這里僅僅是兩個(gè)字段,所以可能好處不明顯,但事實(shí)上,實(shí)際開發(fā)中,經(jīng)常會(huì)有將BO轉(zhuǎn)換為VO的操作,這時(shí)候,這個(gè)操作就很有必要了,簡單的一行命令就可以代替一大堆的get和set操作。
當(dāng)然,使用反射機(jī)制固然高端大氣上檔次,但是也是一把雙刃劍,使用不當(dāng)很可能會(huì)帶來嚴(yán)重后果,而且使用反射的話,會(huì)占用更多資源,運(yùn)行效率也會(huì)降低,上述工具類是用運(yùn)行效率換開發(fā)效率。開發(fā)中不建議大量使用,還是那句話,技術(shù)只是手段,需要使用的時(shí)候再使用,不要為了使用而使用。
至于反射中的其他方法和姿勢(shì),大家盡可以慢慢去摸索,這里僅僅是拋磚引玉。
至此,本篇講解完畢,歡迎大家繼續(xù)關(guān)注。
以上就是一文搞懂Java中的反射機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Java反射機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決springboot中@DynamicUpdate注解無效的問題
這篇文章主要介紹了解決springboot中@DynamicUpdate注解無效的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Cloud Config 使用本地配置文件方式
這篇文章主要介紹了Spring Cloud Config 使用本地配置文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java實(shí)現(xiàn)讀取文件夾下(包括子目錄)所有文件的文件名
這篇文章主要介紹了Java實(shí)現(xiàn)讀取文件夾下(包括子目錄)所有文件的文件名,本文把代碼組織成了一個(gè)模塊,可以很方便的使用,需要的朋友可以參考下2015-06-06
迅速學(xué)會(huì)@ConfigurationProperties的使用操作
這篇文章主要介紹了迅速學(xué)會(huì)@ConfigurationProperties的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Spring/SpringBoot?@RequestParam注解無法讀取application/json格式數(shù)據(jù)問題
RequestParam用于將指定的請(qǐng)求參數(shù)賦值給方法中的形參,可以接受簡單類型屬性,也可以接受對(duì)象類型,一般用于GET請(qǐng)求,下面這篇文章主要給大家介紹了關(guān)于Spring/SpringBoot?@RequestParam注解無法讀取application/json格式數(shù)據(jù)問題解決的相關(guān)資料,需要的朋友可以參考下2022-10-10
IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過程
這篇文章主要介紹了IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01
Java concurrency之AtomicReference原子類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
AtomicReference是作用是對(duì)"對(duì)象"進(jìn)行原子操作。這篇文章主要介紹了Java concurrency之AtomicReference原子類,需要的朋友可以參考下2017-06-06

