新聞中心
反射是Java語言中非常重要的一個基礎知識點,它的應用往往在于代碼的封裝上和框架的設計上,對于一般的碼農(nóng)和初級工程師來說,在日常的編碼工作中很少直接使用反射,所以不少Java小伙伴對反射是既熟悉又陌生。

泰來網(wǎng)站建設公司成都創(chuàng)新互聯(lián)公司,泰來網(wǎng)站設計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為泰來1000多家提供企業(yè)網(wǎng)站建設服務。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設要多少錢,請找那個售后服務好的泰來做網(wǎng)站的公司定做!
熟悉是都聽說過,聽說是一個很牛掰的技術,是封裝框架,走向架構的必修課,陌生在于日常開發(fā)很少直接使用,反正不影響哥們施展CV大法。
如果企業(yè)沒有編程規(guī)范要求和來自領導的review,優(yōu)化程序是不可能的,重構,向上封裝?想都不要想。如果想要讓自己的編程功力提高,提升自己的架構思維,那么在日常編碼過程中,一定要多去思考,我的代碼能否再精簡?能否再通用?能否變成一個團隊插件?想要實現(xiàn)這個目的,反射就可能派上用場了。
本文會從以下幾點為小伙伴解密反射,保障看完神清氣爽,直呼哇塞:
- 反射的概念和相關API
- 反射語法,如何獲取和操作反射類,構造器,方法,屬性
- 反射的應用場景,從框架和個人項目經(jīng)驗分享
- 面試中高手都怎么回答反射的優(yōu)缺點拿到高薪
什么是反射
Reflection(反射)是Java程序開發(fā)語言的重要特性之一,通過反射機制允許在程序運行期間,可以根據(jù)任意一個.class【字節(jié)碼文件】文件獲取到這個類的所有信息,包括(成員變量,成員方法,構造器等),并可以通過相應的方式操縱類的字段、方法、構造器
基于反射機制,Java實現(xiàn)在程序運行期間,動態(tài)獲取信息和動態(tài)調(diào)用對象的方法,提高了程序的擴展性
使用反射的前提是必須有字節(jié)碼文件,也就是.class文件,.class文件就是通過.java文件編譯而來的。Java作為面向?qū)ο蟮木幊陶Z言,提供了為反射準備的字節(jié)碼文件對象java.lang.Class
反射相關API
反射機制相關的包都在
java.lang.reflect.*;
一些反射中的常用類:
|
類 |
作用 |
|
java.lang.Class |
代表整個字節(jié)碼。代表一個類型,代表整個類 |
|
java.lang.reflect.Method |
代表字節(jié)碼中的方法字節(jié)碼。代表類中的方法 |
|
java.lang.reflect.Constructor |
代表字節(jié)碼中的構造方法字節(jié)碼。代表類中的構造方法 |
|
java.lang.reflect.Field |
代表字節(jié)碼中的屬性字節(jié)碼。代表類中的成員變量(靜態(tài)變量+實例變量) |
還可以通過字節(jié)碼對象【Class對象】獲取注解以下信息,這些信息不在java.lang.reflect包中,所以單獨列出來
|
類 |
作用 |
|
java.lang.annotation.Annotation |
可以獲取類,字段,方法等地方的注解 |
|
java.lang.Package |
獲取所在包 |
|
java.lang.ClassLoader |
獲取類加載器 |
反射語法應用
反射是將運行中的class文件讀取到并封裝進反射類【Class
獲取反射類
咱們寫的代碼是存儲在后綴名是 .java的文件里的,但是它會被編譯,最終真正去執(zhí)行的是編譯后的 .class文件。Java是面向?qū)ο蟮恼Z言,一切皆對象,所以java認為這些編譯后的 class文件也是一種對象,給抽象成了一種類,這個類就是Class,獲取反射類有三種方式:
- 通過Object類的 getClass方法來獲取,所有的類都直接或者間接繼承Object類,所有的對象都可以使用該方法
- 使用 類名.class 的方式
- 使用Class.forName方法
需求:有以下Student類,我們通過三種方式獲取該類的Class對象,并根據(jù)Class對象的getName方法,輸出類的全路徑
student類:
package com.stt.reflect;
public class Student {
private Long id;
private String name;
private String sex;
private Integer age;
}
獲取反射類:
public class StudentMain {
public static void main(String[] args) {
// 1、通過 getClass 方法獲取 Class類,
// 1) 創(chuàng)建Student類對象
Student student = new Student();
// 2) 所有的類都直接或者間接繼承Object類,每個對象都有getClass方法
Class studentClazz1 = student.getClass();
System.out.println("studentClazz1===>" + studentClazz1.getName());
// 2、通過.class獲取
Class studentClazz2 = Student.class;
System.out.println("studentClazz2===>" + studentClazz2.getName());
// 3、通過Class類的forName方法獲取,傳入類的全路徑
try {
// 此方法拋出 ClassNotFoundException 異常,因為填寫的類全路徑可能錯誤,該類可能不存在
Class studentClazz3 = Class.forName("com.stt.reflect.Student");
System.out.println("studentClazz3===>" + studentClazz3.getName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}輸出結果:
反射獲取構造方法
|
方法 |
作用 |
|
Constructor |
根據(jù)參數(shù)列表,獲取指定數(shù)據(jù)類型的構造方法,拋出 NoSuchMethodException, SecurityException |
|
Constructor>[] getConstructors() |
獲取所有的構造方法,拋出 SecurityException |
改造Student類,增加四個構造方法,默認有一個無參的構造方法
package com.stt.reflect;
public class Student {
private Long id;
private String name;
private String sex;
private Integer age;
// 無參構造
public Student() {
System.out.println("=======無參構造方法=======");
}
// 單參構造
public Student(String name) {
this.name = name;
System.out.println("=======單參構造方法=======name:" + name);
}
// 兩參構造
public Student(Long id, String name) {
this.id = id;
this.name = name;
System.out.println("========兩參構造方法=======id:" + id + ", name: " + name);
}
// 三參構造
public Student(Long id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
System.out.println("========三參構造方法=======id:" + id + ", name: " + name + ", sex: " +sex);
}
// 四參構造
public Student(Long id, String name, String sex, Integer age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
System.out.println("========三參構造方法=======id:" + id + ", name: " + name + ", sex: " + sex + ", age: " +age);
}
}
獲取指定參數(shù)構造方法
- 根據(jù)getConstructor()方法獲取構造方法
- 通過 Constructor 類的 newInstance()方法可以調(diào)用對應的構造方法創(chuàng)建對象
package com.stt.reflect;
import java.lang.reflect.Constructor;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取無參的構造方法
ConstructornoParamConstructor = clazz.getConstructor();
// 通過Constructor創(chuàng)建對象
Student student1 = noParamConstructor.newInstance();
// 3、獲取單燦構造方法,單參是String類型的name,所以參數(shù)寫為String.class
ConstructoroneStringParamConstructor = clazz.getConstructor(String.class);
Student student2 = oneStringParamConstructor.newInstance("添甄");
// 4、獲取兩參構造方法,類型分別為Long和String
ConstructortwoStringParamConstructor = clazz.getConstructor(Long.class,String.class);
Student student3 = twoStringParamConstructor.newInstance(1L,"添甄");
}
}
運行結果:
獲取所有構造方法
將無參的構造方法設置為私有
private Student() {
System.out.println("=======無參構造方法=======");
}獲取所有構造方法:
package com.stt.reflect;
import java.lang.reflect.Constructor;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取所有構造方法
Constructor[] constructors = clazz.getConstructors();
// 3、遍歷構造方法
for (Constructorconstructor : constructors) {
System.out.println(constructor);
}
}
}
輸出結果:
可以獲取所有的公開構造方法,私有構造獲取不到
獲取私有構造
小貼士:通過反射獲取私有的構造函數(shù)時,用普通的getConstructor()會報錯,因為它是私有的,所以提供了專門反射私有構造函數(shù)的方法 getDeclaredConstructor(int.class);
如果要調(diào)用這個方法還需要設置一下暴力反射才可以,即調(diào)用構造器對象的setAccessible(true);方法
獲取所有私有方法:
package com.stt.reflect;
import java.lang.reflect.Constructor;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取所有構造方法,包括私有
Constructor[] constructors = clazz.getDeclaredConstructors();
// 3、遍歷構造方法
for (Constructorconstructor : constructors) {
System.out.println(constructor);
}
}
}
運行結果:
獲取私有構造方法并調(diào)用
僅僅獲取到調(diào)用會出現(xiàn) IllegalAccessException 異常
package com.stt.reflect;
import java.lang.reflect.Constructor;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取無參的構造方法,包括私有
Constructorconstructor = clazz.getDeclaredConstructor();
// 3、此時直接調(diào)用會報錯
Student student = constructor.newInstance();
}
}
報錯:
設置暴力破解即可訪問:也就是調(diào)用一下 setAccessible(true)方法
package com.stt.reflect;
import java.lang.reflect.Constructor;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取無參的構造方法,包括私有
Constructorconstructor = clazz.getDeclaredConstructor();
// 3、暴力破解,允許調(diào)用私有方法
constructor.setAccessible(true);
// 4、此時直接調(diào)用會報錯
Student student = constructor.newInstance();
}
}
運行結果:
反射獲取方法
在Student類中添加以下普通方法
package com.stt.reflect;
public class Student {
// ......
public void speak() {
System.out.println("你好?。? + name);
}
public Integer getAge() {
System.out.println("getAge===》" + this.age);
return this.age;
}
public void setName(String name) {
this.name = name;
System.out.println("設置名字為:" + name);
}
public static void skill() {
System.out.println("======skill靜態(tài)方法======");
}
private void method1() {
System.out.println("======私有方法======");
}
private static void method2(String[] args) {
System.out.println("======私有靜態(tài)方法======" + args);
}
}
獲取所有非私有方法
獲取方法,通過 Method 類型對象封裝
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取所有非私有方法,包括父類中方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}
如下圖:獲取到了所有的非私有方法,包括父類中的方法
獲取所有方法
通過調(diào)用getDeclaredMethods方法獲取所有方法,
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取所有非私有方法,包括父類中方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}
如下,獲取了包括私有方法,但是不包括父類中的方法
獲取指定方法并調(diào)用
我們可以通過 getMethod(String name, Class>... parameterTypes) 方法,根據(jù)方法名和參數(shù)獲取方法,在通過Method對象的 invoke(Object obj, Object... args)方法調(diào)用方法,如果是私有方法,你懂的,需要暴力破解一下再調(diào)用,和構造器一樣
- 調(diào)用四個參數(shù)構造方法創(chuàng)建student對象
- 分別調(diào)用speak,getAge和setName方法,分別對應無返回值和有返回值和有參數(shù)等情況
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、獲取無參的 speak 方法
Method speakMethod = clazz.getMethod("speak");
// 3、創(chuàng)建Student對象
Constructorconstructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
Student student = constructor.newInstance(1L,"添甄","男",26);
// 傳入 student,意為要調(diào)用student對象的speak
Object returnResult = speakMethod.invoke(student);
System.out.println("方法返回值==》" + returnResult);
// 4、調(diào)用getAge方法,沒有參數(shù)有返回值
Method ageMethod = clazz.getMethod("getAge");
Integer returnAge = (Integer)ageMethod.invoke(student);
System.out.println(returnAge);
// 5、調(diào)用有參數(shù)的方法,getMethod參數(shù)2寫為參數(shù)的類型
Method setNameMethod = clazz.getMethod("setName",String.class);
setNameMethod.invoke(student, "王小波");
}
}
調(diào)用私有方法
方式和調(diào)用私有構造器一致,首先通過 getDeclaredMethod 獲取私有方法,其次調(diào)用 setAccessible(true) 設置可訪問為true
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、創(chuàng)建Student對象
Constructorconstructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
Student student = constructor.newInstance(1L,"添甄","男",26);
// 3、通過 getDeclaredMethod 獲取私有方法 private void method1()
Method method1 = clazz.getDeclaredMethod("method1");
// 設置為可訪問 true
method1.setAccessible(true);
// 調(diào)用方法
Object invoke = method1.invoke(student);
}
}
參數(shù)為數(shù)組
參數(shù)維數(shù)組有點特殊,這是因為 jdk1.4和jdk1.5處理invoke方法有區(qū)別
1.5:public Object invoke(Object obj,Object…args):1.5參數(shù)為Object類型的可變參數(shù)
1.4:public Object invoke(Object obj,Object[] args):1.4參數(shù)為一個Object類型數(shù)組
在反射方法時,如果方法的參數(shù)是一個數(shù)組,考慮到向下兼容問題,會按照JDK1.4的語法來對待(JVM會把傳遞的數(shù)組參數(shù)拆開,拆開就會報參數(shù)的個數(shù)不匹配的錯誤) 解決辦法:防止JVM拆開你的數(shù)組 方式一:把數(shù)組看做是一個Object對象 方式二:重新構建一個Object數(shù)組,那個參數(shù)數(shù)組作為唯一的元素存在。
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、創(chuàng)建Student對象
Constructorconstructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
Student student = constructor.newInstance(1L,"添甄","男",26);
// 3、通過 getDeclaredMethod 獲取私有方法 private void method1()
Method method1 = clazz.getDeclaredMethod("method2",String[].class);
// 設置為可訪問 true
method1.setAccessible(true);
// 調(diào)用方式1:將數(shù)組強轉(zhuǎn)為Object對象
method1.invoke(student,(Object)new String[]{"a","b"});
// 調(diào)用方式2:將數(shù)組存儲進Object數(shù)組中
method1.invoke(student,new Object[]{new String[]{"a","b"}});
}
}
錯誤調(diào)用:如果直接傳進數(shù)組,則會出現(xiàn)IllegalArgumentException【非法參數(shù)異常】報錯
Method method1 = clazz.getDeclaredMethod("method2",String[].class);
// 設置為可訪問 true
method1.setAccessible(true);
// 錯誤調(diào)用:直接傳遞數(shù)組
method1.invoke(student,new String[]{"a","b"});
反射獲取屬性
設置name屬性為public
private Long id;
public String name;
private String sex;
private Integer age;
相關方法:
|
方法 |
作用 |
|
Field getField(String name) |
獲取指定非私有屬性 |
|
Field[] getFields() |
獲取所有非私有屬性 |
|
Field getDeclaredField(String name) |
獲取指定私有屬性 |
|
Field[] getDeclaredFields() |
獲取所有私有屬性 |
獲取屬性并通過get方法獲取屬性值
package com.stt.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class StudentMain {
public static void main(String[] args) throws Exception {
// 1、獲取Class類,這里將異常向上拋出
Class clazz = Class.forName("com.stt.reflect.Student");
// 2、創(chuàng)建Student對象
Constructorconstructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
Student student = constructor.newInstance(1L,"添甄","男",26);
// 3、獲取name屬性
Field nameField = clazz.getField("name");
// 4、獲取所有費私有屬性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println("field name====》" + field.getName());
System.out.println("字段值==》" + field.get(student));
}
// 5、獲取指定私有屬性
Field sexField = clazz.getDeclaredField("sex");
sexField.setAccessible(true);
System.out.println(sexField.get(student));
}
}
運行結果:
總結
- 要使用反射首先通過上述三種方式獲取Class對象,該對象有泛型
- 可以根據(jù)相關get方法獲取非私有的構造方法,普通方法和屬性
- 可以根據(jù) 相關getDeclaredXXX系列方法,獲取私有的構造方法,普通方法和屬性,如果是私有需要調(diào)用對應的 setAccessible(true)允許暴力訪問進行操作
- 調(diào)用方法和屬性時,需要指定一個對象,即調(diào)用該對象的指定方法
- Class、Constructor、Method和Field的方法其實都比較簡單,可以自己動手調(diào)用一下,印象會更深刻
- 我們還可以根據(jù)獲取Class、Method、Field、Constructor獲取對應的注解,即 Annotation對象
- 而且私有的方法和屬性都可以被強制調(diào)用,反射破壞了封裝性,帶來一些安全隱患
反射應用場景
Tomcat加載文件
使用Servlet編寫web應用時,往往都有一個web.xml文件,
my3
com.servlet.MyServlet3
my3
網(wǎng)站欄目:原來高手是這么回答和使用反射的,又覺得漲了不少知識
網(wǎng)站鏈接:http://fisionsoft.com.cn/article/ccddhjj.html


咨詢
建站咨詢
