1.反射概述 Java程式在運行時操作類中的屬性和方法的機制,稱為反射機制。 一個關鍵點:運行時 一般我們在開發程式時,都知道自己具體用了什麼類,直接創建使用即可。但當你寫一些通用的功能時沒辦法在編寫時知道具體的類型,並且程式跑起來還會有多種類型的可能,則需要在運行時動態的去調用某個類的屬性和方法, ...
1.反射概述
Java程式在運行時操作類中的屬性和方法的機制,稱為反射機制。
一個關鍵點:運行時
一般我們在開發程式時,都知道自己具體用了什麼類,直接創建使用即可。但當你寫一些通用的功能時沒辦法在編寫時知道具體的類型,並且程式跑起來還會有多種類型的可能,則需要在運行時動態的去調用某個類的屬性和方法,這就必須使用反射來實現。
例子說明:
Father f = new Children();
編譯時變數f 為Father類型,運行時為Children類型;
public void demo(Object obj){
// 不知道調用者傳什麼具體對象
……
}
編譯時demo方法參數類型為Object,一般有兩種做法
第一種做法是知道參數類型有哪幾種情況,可以使用instanceof運算符進行判斷,再利用強制類型轉換將其轉換成其運行時類型的變數即可。
第二種做法是編譯時根本無法預知該對象和類可能屬於哪些類,程式只依靠運行時信息動態的來發現該對象和類的真實信息,這就必須使用反射。
那反射是怎麼做到在運行時獲取類的屬性和方法的呢?
理解類的載入機制的應該知道,當java文件編譯成.class文件,再被載入進入記憶體之後,JVM自動生成一個唯一對應的Class對象,這個Class是一個具體的類,這個Class類就是反射學習的重點。反射的操作對象就是這個Class類,通過Class類來獲取具體類的屬性和方法。
2.Class類
Class 類是用於保存類或介面屬性和方法信息的類,就是保存類信息的類,它類名稱就叫 Class。
2.1.理解Class類
Class類和構造方法源碼
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private final ClassLoader classLoader;
private Class(ClassLoader loader) {
classLoader = loader;
}
……
}
簡單分析下Class類
Class類和String類都是被final關鍵字修飾的類,是不可以被繼承的類; Class類支持泛型T,也就是說在編寫程式時可以做到:反射 + 泛型; Class類實現了序列化標記介面Serializable,既是Class類可以被序列化和反序列化; Class類不能被繼承,同時唯一的一個構造器還是私有的,因為設計之初就是讓JVM在類載入後傳入ClassLoader對象來創建Class對象(每個類或介面對應一個JVM自動生成Class對象),開發人員只是調用Class對象,並沒有直接實例化Class的能力。
Class對象的創建是在載入類時由 Java 虛擬機以及通過調用類載入器中的defineClass
方法自動構造的,關於類的載入可以通過繼承ClassLoader來實現自定義的類載入器,本文著重講反射,在此不展開講類載入相關知識。
2.2.獲取Class對象的三種方式
方式一:常用方式,Class.forName("包名.類名")
public static void main(String[] args) {
// 方式一:全限定類名字元串
Class<?> childrenClass = null;
try {
childrenClass = Class.forName("com.yty.fs.Children"); // 包名.類名
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 獲取類名稱
System.out.println("全限定類名="+childrenClass.getName());
}
執行結果:全限定類名=com.yty.fs.Children
方式二:每個類下的靜態屬性 class,類名.class
public static void main(String[] args) {
// 方式二:每個類下的靜態屬性 class
Class<Children> childrenClass2 = Children.class;
System.out.println("類名稱="+childrenClass.getSimpleName());
}
執行結果:類名稱=Children
方式三:每個類最終都繼承了Object,Object類下的getClass()
public static void main(String[] args) {
// 方式三:Object類下的getClass()
Children children = new Children();
Class<?> childrenClass3 = children.getClass();
System.out.println("類所在包="+childrenClass3.getPackage());
}
執行結果:類所在包=package com.yty.fs
三種方式簡單對比:
方式一通過全限定類名字元串既可以獲取,其他兩種方式都要導入類Children才可以; 方式二獲取的Class不需要強轉即可獲得指定類型Class ,其他兩種方式獲得的都是未知類型Class<?>; 方式三通過實例化對象的Object中的方法獲取,其他兩種都不需要實例化對象。
怎麼選:
只有全限定類名字元串,沒有具體的類可以導入的只能選方式一; 有具體類導入沒有實例化對象的使用方式二; 作為形參使用的使用方式三,通過形參引用來獲取Class。
Class類中有非常多的方法,通過案例掌握常用的方法即可。
2.3.案例一:構造方法、成員變數和成員方法的獲取和使用
1.構造方法操作
1.1.所有構造方法
1.2.所有public構造方法
1.3.無參構造方法
1.4.單個私有構造方法
2.欄位操作(成員變數)
2.1.獲取所有成員變數
2.2.獲取所有公共成員變數
2.3.獲取單個公共成員變數
2.4.獲取單個私有成員變數
3.方法操作(成員方法)
3.1.獲取所有方法--不會獲取父類的方法
3.2.獲取所有公共方法--會獲取父類的方法
3.3.獲取單個公共方法
3.3.1.獲取單個公共方法--無參方法
3.3.2.獲取單個公共方法--有參方法
3.4.獲取單個私有方法
具體看代碼
測試類:Children類
public class Children {
public String testString; //測試用
private int id;
private String name;
// 無參構造方法
public Children() {
System.out.println("====無參構成方法被調用");
}
// 多個參數構造方法
public Children(int id, String name) {
this.id = id;
this.name = name;
}
// default構造方法--測試
Children(String name, int id){
this.id = id;
this.name = name;
}
// 受保護構造方法--測試
protected Children(int id) {
this.id = id;
}
// 私有構造方法--測試
private Children(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Children{ id=" + id + ", name=" + name + "}";
}
public void printName(){
System.out.println("====printName--"+this.name);
}
public void printName(String name){
this.name = name;
System.out.println("====printName--"+this.name);
}
private void demoTest(){
System.out.println("====demoTest--執行了");
}
}
Class類的具體操作
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 案例一:構造方法、成員變數和成員方法的獲取和使用
*/public class Demo1 {
public static void main(String[] args) throws Exception {
Class<?> chilrenClass = Class.forName("com.yty.fs.Children");
// 1.構造方法操作
// 1.1.獲取所有構造方法
System.out.println("1.構造方法操作\n1.1.所有構造方法");
Constructor<?>[] declaredConstructors = chilrenClass.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors){
System.out.println(constructor.toString()); // Constructor類的toString已重寫
}
// 1.2.獲取所有public構造方法
System.out.println("1.2.所有public構造方法");
Constructor<?>[] constructors = chilrenClass.getConstructors();
for(Constructor constructor : constructors){
System.out.println(constructor.toString());
}
// 1.3.獲取無參構造方法
Constructor<?> onParamConstructor = chilrenClass.getConstructor();//參數類型為null,表示無參
System.out.println("1.3.無參構造方法:\n"+onParamConstructor.toString());
// 實例化對象
Object o = onParamConstructor.newInstance();
if(o instanceof Children){
Children children = (Children)o;
children.setId(111);
children.setName("myName");
System.out.println(o.toString());// Children類重寫了toString
}
// 1.4.獲取單個私有構造方法
// 指定了私有構造方法的參數類型,所以只會獲取到一個構造方法
Constructor<?> privateConstructor = chilrenClass.getDeclaredConstructor(String.class);
System.out.println("1.4.單個私有構造方法:\n"+privateConstructor.toString());
//私有構造方法需要取消訪問許可權檢查,否則報異常:IllegalAccessExceptionw
privateConstructor.setAccessible(true);
Object obj = privateConstructor.newInstance("myName");
System.out.println(o.toString());
// 2.欄位操作(成員變數)
// 2.1.獲取所有成員變數
System.out.println("2.欄位操作(成員變數)\n2.1.獲取所有成員變數");
Field[] declaredFields = chilrenClass.getDeclaredFields();
for (Field declaredField : declaredFields){
// 獲取fieldName
System.out.println(declaredField.getName());
}
// 2.2.獲取所有公共成員變數
System.out.println("2.2.獲取所有公共成員變數");
Field[] fields = chilrenClass.getFields();
for (Field field : fields){
// 獲取fieldName
System.out.println(field.getName());
}
// 2.3.獲取單個公共成員變數
System.out.println("2.3.獲取單個公共成員變數");
Field field = chilrenClass.getField("testString");
Object o1 = chilrenClass.getConstructor().newInstance();
field.set(o1,"yty");
Object o1_1 = field.get(o1);
// 獲取fieldName
System.out.println("成員變數名-值:"+field.getName()+"="+o1_1.toString());
// 2.4.獲取單個私有成員變數
System.out.println("2.4.獲取單個私有成員變數");
Field field2 = chilrenClass.getDeclaredField("name");
//私有成員變數需要取消訪問許可權檢查,否則報異常:IllegalAccessExceptionw
field2.setAccessible(true);
Object o2 = chilrenClass.getConstructor().newInstance();
field2.set(o2,"myName");
Object o2_2 = field2.get(o2);
// 獲取fieldName
System.out.println("成員變數名-值:"+field2.getName()+"="+o2_2.toString());
// 3.方法操作(成員方法)
// 3.1.獲取所有方法(成員方法)
System.out.println("3.方法操作(成員方法)\n3.1.獲取所有方法--不會獲取父類的方法");
Method[] declaredMethods = chilrenClass.getDeclaredMethods();
for (Method method : declaredMethods){
// 獲取方法名
System.out.println(method.getName());
}
// 3.2.獲取所有公共方法
System.out.println("3.2.獲取所有公共方法--會獲取父類的方法");
Method[] methods = chilrenClass.getMethods();
for (Method method : methods){
// 獲取方法名
System.out.println(method.getName());
}
// 3.3.獲取單個公共方法
System.out.println("3.3.獲取單個公共方法\n3.3.1.獲取單個公共方法--無參方法");
Method printName = chilrenClass.getMethod("printName"); //方法名稱
System.out.println(printName);
System.out.println("3.3.2.獲取單個公共方法--有參方法");
Method printName2 = chilrenClass.getMethod("printName",String.class); //方法名稱,參數類型
System.out.println("參數個數:"+printName2.getParameterCount());
// 遍歷所有參數信息
Parameter[] parameters = printName2.getParameters();
for (int i=0;i<printName2.getParameterCount();i++){
Parameter param = parameters[i];
if(param.isNamePresent()){
System.out.println("第"+ (i+1) +"個參數信息");
System.out.println("參數類型="+param.getType());
System.out.println("參數名稱="+param.getName());
}
}
// 使用有參方法
Object o3 = chilrenClass.getConstructor().newInstance();
printName2.invoke(o3,"myName");//傳入參數值、執行方法
// 3.4.獲取單個私有方法
System.out.println("3.4.獲取單個私有方法");
Method demoTest = chilrenClass.getDeclaredMethod("demoTest");
// 使用私有無參方法
Object o4 = chilrenClass.getConstructor().newInstance();
demoTest.setAccessible(true);
demoTest.invoke(o4);
}
}
執行結果:拷貝過去執行就知道了……
2.4.案例二:註解的相關操作
自定義一個測試用的註解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)
public @interface PersonAnnotation {
String name() default "myName";
}
註解使用類和測試用的main方法
import java.lang.reflect.Field;
public class PersonAnnotationDemo {
@PersonAnnotation(name = "張三")
private String name;
private int age;
@Override
public String toString() {
return "PersonAnnotationDemo{" + "name=" + name + ", age=" + age + "}";
}
public static void main(String[] args) throws Exception {
Class<?> annotateClass = Class.forName("com.yty.fs.PersonAnnotationDemo");
Object o = annotateClass.newInstance();
System.out.println("PersonAnnotationDemo是否是註解類:"+annotateClass.isAnnotation());
Field[] declaredFields = annotateClass.getDeclaredFields();
for(Field field : declaredFields){
// PersonAnnotationDemo類中的成員變數是否有 PersonAnnotation註解
if (field.isAnnotationPresent(PersonAnnotation.class)){
// 獲取成員變數中 單個PersonAnnotation註解
PersonAnnotation annotation = field.getAnnotation(PersonAnnotation.class);
// 獲取 多個PersonAnnotation註解
// PersonAnnotation[] annotationsByType = field.getAnnotationsByType(PersonAnnotation.class);
/**
* 相類似的獲取註解方法:getDeclaredAnnotation、getDeclaredAnnotationsByType、getAnnotations、getDeclaredAnnotations
*/
// 輸出註解中的值
System.out.println("輸出註解中的值:"+field.getName()+"="+annotation.name());
// 將註解的值 賦值 到PersonAnnotationDemo對象對應欄位
field.setAccessible(true);//私有欄位需要忽略修飾符
field.set(o,annotation.name());
}
}
// 輸出:註解的值 賦值給 對象
System.out.println(o.toString());
}
}
執行結果:
PersonAnnotationDemo是否是註解類:false
輸出註解中的值:name=張三
PersonAnnotationDemo{name=張三, age=0}
3.反射的應用
常用於框架底層開發
3.1.實戰一:通過配置文件解耦類和反射
Spring 框架通過將成員變數值以及依賴對象等信息都放在配置文件中進行管理的,類發生改變時只需要更新配置文件,對於反射模塊則無需更改,從而實現了較好的解耦。
測試類
public class BigBanana {
public void printBigBanana(String color){
System.out.println("Do you like "+color+" BigBanana?");
}
}
配置文件信息
bigbanana.class.name=com.yty.fs.BigBanana
bigbanana.class.method=printBigBanana
bigbanana.class.method.param=java.lang.String
反射測試類
public class TestBigBanana {
private static Properties properties;
// 通過Key 獲取配置文件value 值
public static String getProperty(String key) throws IOException {
if (properties == null){
properties = new Properties();
FileReader fileReader = new FileReader(new File("properties.properties"));
properties.load(fileReader);
}
return properties.getProperty(key);
}
// 測試
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName(getProperty("bigbanana.class.name"));
Object o = aClass.getConstructor().newInstance();
Class<?> paramClass = Class.forName(getProperty("bigbanana.class.method.param"));
Method method = aClass.getMethod(getProperty("bigbanana.class.method"),paramClass);
method.invoke(o,"yellow");
}
}
執行結果:Do you like yellow BigBanana?
3.2.實戰二:代理賣手機--JDK動態代理
3.2.1.簡單理解代理模式
通過代理的方式(增加中間層)對想要訪問的類做一些控制,使代碼職責清晰、通用化、智能化、易擴展。
代理模式三個要點:一個公共介面、一個具體類(被代理類)、一個代理類
代理模式分為:靜態代理和動態代理
靜態代理:代理類在編譯時已創建好具體類的對象,簡言之是幫你提前new好了對象; 動態代理:代理類在程式運行時才創建具體類的對象,根據程式的運行不同可能調用的具體類不同。
JDK動態代理本質是通過反射來實現,涉及InvocationHandler介面和Proxy類。
Proxy類:創建動態代理實例;
InvocationHandler對象:當執行被代理對象里的方法時,實際上會替換成調用InvocationHandler對象的invoke方法,動態的代理到介面下的實現類。
本次案例的類圖關係:
綠色名為JDK動態代理關鍵介面和類 紅色名為本案例要編寫的關鍵介面和類
3.2.2.手機介面
public interface Phone {
void sellPhone();
}
3.2.3.三款手機類
華為
public class Huawei implements Phone {
// 型號
private String phoneModelName;
public Huawei(String phoneModelName){
this.phoneModelName=phoneModelName;
}
@Override
public void sellPhone() {
System.out.println("賣Huawei "+this.phoneModelName+" 的手機");
}
}
小米
public class Xiaomi implements Phone{
// 型號
private String phoneModelName;
public Xiaomi(String phoneModelName){
this.phoneModelName=phoneModelName;
}
public Xiaomi(){
}
@Override
public void sellPhone() {
System.out.println("賣Xiaomi "+this.phoneModelName+" 的手機");
}
}
愛瘋
public class IPhone implements Phone {
// 型號
private String phoneModelName;
public IPhone(String phoneModelName){
this.phoneModelName=phoneModelName;
}
@Override
public void sellPhone() {
System.out.println("賣IPhone "+this.phoneModelName+" 的手機");
}
}
3.2.4.Invocation 實現類
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 代理前可以考慮做些跟有趣的事
*/
System.out.println("代理方法--start--代理前可以考慮做些跟有趣的事");
Object invoke = method.invoke(target, args);
/**
* 代理後可能你有更想要做的事
*/
System.out.println("代理方法--end--代理後可能你有更想要做的事\n");
return invoke;
}
}
3.2.5.代理商類
public class MyProxy {
public static Object getProxy(Object target){
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(target);
Object proxyInstance = Proxy.newProxyInstance(Phone.class.getClassLoader(), new Class[]{Phone.class}, myInvocationHandler);
return proxyInstance;
}
}
3.2.6.測試類
public class Test {
public static void main(String[] args) {
// 不管你想要買什麼手機,只要通過同一個代理商就可以買到
Phone huawei = (Phone) MyProxy.getProxy(new Huawei("Huawei 16 pro"));
Phone xiaomi = (Phone) MyProxy.getProxy(new Xiaomi("MI 13 pro"));
Phone iphone = (Phone) MyProxy.getProxy(new IPhone("IPhone 13 pro"));
huawei.sellPhone();
xiaomi.sellPhone();
iphone.sellPhone();
}
}
測試結果:
代理方法--start--代理前可以考慮做些跟有趣的事
賣Huawei Huawei 16 pro 的手機
代理方法--end--代理後可能你有更想要做的事
代理方法--start--代理前可以考慮做些跟有趣的事
賣Xiaomi MI 13 pro 的手機
代理方法--end--代理後可能你有更想要做的事
代理方法--start--代理前可以考慮做些跟有趣的事
賣IPhone IPhone 13 pro 的手機
代理方法--end--代理後可能你有更想要做的事
4.反射與泛型的簡單實戰
通過實戰進一步理解泛型和反射。
4.1.實戰一:泛型方法和反射的結合
PrintResult類:註意成員方法私有
public class PrintResult {
private void printSuccessInfo(){
System.out.println("printSuccessInfo--執行成功");
}
private void printAdd(int[] ints){
int n=0;
for (int i :ints){
n=n+i;
}
System.out.println("求和結果="+n);
}
}
測試類
public class Demo {
// 執行指定類型的方法
public <T> void demo1(T t,String methodName,int... args) throws Exception {
Class<?> tClass = t.getClass();
Object o = tClass.getConstructor().newInstance();
if (args == null){
Method method = tClass.getDeclaredMethod(methodName);
method.setAccessible(true);
System.out.println("執行的方法="+method.getName());
method.invoke(o);
}else {
Method method = tClass.getDeclaredMethod(methodName,int[].class);
method.setAccessible(true);
System.out.println("執行的方法="+method.getName());
method.invoke(o,args);
}
}
public static void main(String[] args) throws Exception {
Demo demo = new Demo();
PrintResult printResult = new PrintResult();
demo.demo1(printResult,"printSuccessInfo",null);//執行printResult 無參方法
demo.demo1(printResult,"printAdd",1888,2222,333);
}
}
執行結果:
執行的方法=printSuccessInfo
printSuccessInfo--執行成功
執行的方法=printAdd
求和結果=4443
在此可以看到泛型T 的對象t,是可以像普通的對象一樣使用反射。
4.2.實戰二:通過反射越過泛型檢查
泛型在編譯期通過類型抹除機制來完成;
反射在運行期完成執行,可以理解為反射是在運行期將編譯好的list集合再新增元素進去。
public class Demo2 {
public static void main(String[] args) throws Exception{
List<String> list = new ArrayList<>();
list.add("qwert");
list.add("1234Z");
// list.add(222); //指定了泛型類型為String後,無法add 非String的值
Class listClass = list.getClass();
//獲取和調用 list中的add()方法
Method m = listClass.getMethod("add", Object.class);
m.invoke(list, 100);
//輸出List集合 -- toString已經被AbstractCollection重寫的了
System.out.println("集合中的內容:"+list.toString());
}
}
執行結果:集合中的內容:[qwert, 1234Z, 100]
原創不易,三聯支持:點贊、分享