JavaSE 反射機制(下)
四、獲取類的方法
-
獲取方法函數:
-
public Method[] getDeclaredMethods()
返回此Class對象所表示的類或接口的全部方法。
-
public Method[] getMethods()
返回此Class對象所表示的類或接口的public的方法。
-
-
常用方法:(在獲取方法過程中)
方法名 功能 Method[] getMethods() 獲取類的所有公有方法 Method[] getDeclaredMethods() 獲取類的所有方法(公有+私有) getName() 獲取方法名稱 getReturnType() 獲取返回值類型 getModifiers() 獲取修飾符(public–>1; private–>2) getParameterTypes() 獲取方法的參數類型,返回Class[], 數組元素個數爲參數個數 -
上述方法案例:
package com.reflection; //父類 public class Person { public String name; int age; }
package com.reflection; //子類 public class Student extends Person{ String school; private String privateField; //無參構造 public Student(){ System.out.println("調用的是public Student()"); } //有參構造 public Student(String school){ System.out.println("調用的是public Student(String school)"); this.school = school; } //私有有參構造 private Student(String name, int age){ System.out.println("調用的是private Student(String name, int age)"); this.name = name; this.age = age; } public void moveType() { System.out.println("騎自行車上學"); } public void studyInfo() { System.out.println("學習中學知識"); } public String getSchool(){ return this.school; } private void test(String name){ } }
package com.reflection; import java.lang.reflect.Method; public class Test3 { public static void main(String[] args) { try { Class clazz3 = Class.forName("com.reflection.Student"); //獲取到類的所有公有方法 Method[] ms = clazz3.getMethods(); for (Method m : ms) { System.out.println("方法名: "+ m.getName()); System.out.println("返回值類型: "+ m.getReturnType()); System.out.println("修飾符: "+ m.getModifiers()); Class[] pcs = m.getParameterTypes();//獲取方法的參數類型,返回Class數組,方法參數個數等於數組元素個數 if (pcs.length>0){ for (Class pc : pcs) { System.out.println("參數類型: " + pc.getName()); } } System.out.println("-----------------------------------------------"); } System.out.println("\n========分==================割===================線===========\n"); //獲取類的所有方法,包含公有私有 Method[] ams = clazz3.getDeclaredMethods(); for (Method m : ams) { System.out.println("方法名: "+ m.getName()); System.out.println("返回值類型: "+ m.getReturnType()); System.out.println("修飾符: "+ m.getModifiers()); Class[] pcs = m.getParameterTypes();//獲取方法的參數類型,返回Class數組,方法參數個數等於數組元素個數 if (pcs.length>0){ for (Class pc : pcs) { System.out.println("參數類型: " + pc.getName()); } } System.out.println("-----------------------------------------------"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /*運行結果: 方法名: moveType 返回值類型: void 修飾符: 1 ----------------------------------------------- 方法名: studyInfo 返回值類型: void 修飾符: 1 ----------------------------------------------- 方法名: getSchool 返回值類型: class java.lang.String 修飾符: 1 ----------------------------------------------- ========分==================割===================線=========== 方法名: test 返回值類型: void 修飾符: 2 參數類型: java.lang.String ----------------------------------------------- 方法名: moveType 返回值類型: void 修飾符: 1 ----------------------------------------------- 方法名: studyInfo 返回值類型: void 修飾符: 1 ----------------------------------------------- 方法名: getSchool 返回值類型: class java.lang.String 修飾符: 1 ----------------------------------------------- */
五、獲取類的屬性和包
-
獲取屬性函數:
-
public Field[] getDeclaredFields()
返回此Class對象所表示的類或接口的全部屬性。
-
public Field[] getFields()
返回此Class對象所表示的類或接口的public的屬性。
注意:1. getDeclaredFields()僅獲取本類中所有屬性,不包含父類。
2. getFields()獲取本類及其父類中的public修飾屬性。
-
-
獲取包函數:
-
Package getPackage()
返回此Class對象所在的包名稱。
-
-
常用方法:(在獲取屬性和包中)
方法名 功能 getFields() 獲取類(本類及父類)的公有屬性 getDeclaredFields() 獲取本類中所有的屬性(公有+私有) getModifiers() 獲取屬性修飾符 getType() 獲取屬性的類型 getName() 獲取屬性名稱 Package getPackage() 獲取該類所屬包名 -
以上方法案例展示:
注:父類Person()、子類Student()同(四)中案例。
package com.reflection; import java.lang.reflect.Field; public class Test5 { public static void main(String[] args) { try { Class clazz4 = Class.forName("com.reflection.Student"); Field[] fs = clazz4.getFields(); //獲取類中所有公有屬性,同時包含父類的屬性 for (Field f : fs) { System.out.println("修飾符: " + f.getModifiers()); System.out.println("屬性類型: " + f.getType()); System.out.println("屬性名稱: " + f.getName()); System.out.println("-----------------------------------"); } System.out.println("\n==========分===========割===========線===========\n"); Field[] afs = clazz4.getDeclaredFields(); //獲取類中所有屬性(私有+公有),不包含父類的屬性。 for (Field af : afs) { System.out.println("修飾符: " + af.getModifiers()); System.out.println("屬性類型: "+af.getType()); System.out.println("屬性名稱: " + af.getName()); System.out.println("-----------------------------------"); } System.out.println("\n==========分===========割===========線===========\n"); Package p = clazz4.getPackage(); System.out.println(p.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /*運行結果: 修飾符: 1 屬性類型: class java.lang.String 屬性名稱: school ----------------------------------- 修飾符: 1 屬性類型: class java.lang.String 屬性名稱: name ----------------------------------- ==========分===========割===========線=========== 修飾符: 1 屬性類型: class java.lang.String 屬性名稱: school ----------------------------------- 修飾符: 2 屬性類型: class java.lang.String 屬性名稱: privateField ----------------------------------- ==========分===========割===========線=========== com.reflection */
六、調用類中指定方法、指定屬性
– 調用指定方法
注:方法調用前需要通過構造器創建obj對象,爲之後的方法調用做準備!
實際調用分以下四種情況討論:
-
調用公有方法
Method m = clazz.getMethod("調用方法名","參數.class"...);
m.invoke(obj,"args"...);
//此處的invoke英文意思有“喚起”的意思,此處表示向對象obj中注入參數。 -
調用私有方法
需要額外註明
m.setAccessible(true)
,該語句表示解除私有封裝,強行調用。其他語句同上。
-
調用重載方法
只需注意填入參數的不同即可。
-
調用有返回值的方法
需要注意對返回值進行接收,接受過程中可能需要對對象進行強轉。
以上情況案例展示:
注:該案例引入Teacher類,其父類Person同上。
package com.reflection;
//Teacher類
public class Teacher extends Person{
public String school;
private String privateField;
//私有方法
private void test(String name){
this.name = name;
System.out.println("這裏調用private void test(String name)方法");
}
//公有方法
public void setInfo(String name, String school){
this.name = name;
this.school = school;
System.out.println("這裏調用public void setInfo(String name, String school)方法");
}
//重載方法
public void SetInfo(String school){
this.school = school;
System.out.println("這裏調用public void setInfo(String school)方法");
}
//有返回值方法
public String getSchool(){
System.out.println("這裏調用public String getSchool()方法");
return school;
}
}
package com.reflection;
//調用指定方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test6 {
public static void main(String[] args) {
try {
/*
以下不論是反射調用setInfo還是test方法
都調用的obj對象的方法,obj對象實際上就是Teacher對象
*/
Class clazz5 = Class.forName("com.reflection.Teacher");
Constructor con = clazz5.getConstructor(); //獲取無參構造
Object obj = con.newInstance(); //使用無參構造創建對象
//調用公有方法
Method m = clazz5.getMethod("setInfo", String.class, String.class); //得到名稱爲setInfo,參數是(String,String)的方法
m.invoke(obj,"卡莉","第一中學");//參數1爲需要實例化的對象,參數2爲調用當前方法的實際參數
//調用私有方法(需要解封)
Method m1 = clazz5.getDeclaredMethod("test", String.class);
m1.setAccessible(true); //解除私有封裝,則可調用私有方法
m1.invoke(obj,"蘇瞻");
//調用重載方法,只需注意填入參數不同即可
Method m2 = clazz5.getMethod("SetInfo", String.class);
m2.invoke(obj,"第二中學");
//調用有返回值的方法,需要對返回值進行接收
Method m3 = clazz5.getMethod("getSchool");
String school = (String)m3.invoke(obj);//調用有返回值但是沒有參數的方法
System.out.println(school);
} catch (Exception e) {
e.printStackTrace();
}
}
}
– 調用指定屬性
注:方法調用前需要通過構造器創建obj對象,此處案例強轉爲Teacher對象,爲之後調用屬性準備!
實際調用分兩種情況討論:
-
調用公有屬性
Field f = clazz.getField("屬性名稱");
f.set(對象名,“傳入參數”);
//設置對應屬性值f.get(對象名);
//獲取對象的對應屬性值 -
調用私有屬性
Field f = clazz.getDeclaredField("私有屬性名稱");
f.setAccessible(true)
//非常重要!!!進行解除封裝f.set(對象名,“傳入參數”)
//設置對應屬性值f.get(對象名);
//獲取對象的對應屬性值
以上情況案例展示:
注:Teacher類同上
package com.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test7 {
public static void main(String[] args) {
try {
Class clazz6 = Class.forName("com.reflection.Teacher");
//反射創建一個對象
Constructor con = clazz6.getConstructor();
Teacher tea = (Teacher)con.newInstance();
//調用公有屬性
Field f = clazz6.getField("school");
f.set(tea,"第三中學");//對tea對象的school屬性設置值 "第三中學"
String school = (String)f.get(tea); //獲取tea對象的school屬性值
System.out.println(school);
System.out.println("----------------");
//調用私有屬性:使用getDeclareField()方法,並解除封裝
Field f1 = clazz6.getDeclaredField("privateField");
f1.setAccessible(true);
f1.set(tea,"測試私有屬性");
String field = (String)f1.get(tea);
System.out.println(field);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*運行結果:
第三中學
----------------
測試私有屬性
*/
七、動態代理
什麼是動態代理?
例:一個Java項目,其中有100個java類,每個java有10個方法,總共1000個方法。現在有一個需求,需要在每個java方法上加上2句話,在方法執行前輸出“這個方法開始執行”,在方法執行後輸出“這個方法已經完成”。此時就需要使用到動態代理!
Proxy :專門完成代理的操作類,是所有動態代理類的父類。通過此類爲一個或多個接口動態地生成實現類。
動態代理步驟:
-
創建需要被代理的類以及接口。
-
創建一個實現接口
InvocationHandler
的類,它必須實現invoke方法,以完成代理的具體操作。(ProxyDemo爲實現接口的類,test爲被代理對象),則使用如下語句生成代理對象handler。InvocationHandler handler = new ProxyDemo(test);
-
通過Proxy的靜態方法,返回一個Subject接口代理。
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
-
通過Subject代理調用RealSubject實現類的方法。
注意:如果一個對象想要通過Proxy.newProxyInstance方法被代理,那麼這個對象的類一定要有相應的接口。
案例展示:
package com.proxy;
//被代理的接口
public interface ITestDemo {
void test1();
void test2();
}
package com.proxy;
//實現被代理接口的類
public class TestDemoImpl implements ITestDemo{
@Override
public void test1() {
System.out.println("執行test1()方法");
}
@Override
public void test2() {
System.out.println("執行test2()方法");
}
}
package com.proxy;
//實現了InvocationHandler接口的動態代理類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 動態代理類
*/
public class ProxyDemo implements InvocationHandler {
Object obj; //被代理的對象
//有參構造器
public ProxyDemo(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法開始執行");
Object result = method.invoke(this.obj,args); //執行的是指定代理對象的指定的方法
System.out.println(method.getName()+"方法執行完成");
return result; //返回Object對象
}
}
注意:其中invoke()方法有三個參數:
-
動態代理類的引用,通常情況下不需要它。但可以使用getClass()方法,得到proxy的Class類從而取得實例的類信息,如方法列表,annotation等。
-
方法對象的引用,代表被動態代理類調用的方法。從中可得到方法名,參數類型,返回類型等等
-
args對象數組,代表被調用方法的參數。注意基本類型(int,long)會被裝箱成對象類型(Interger, Long)
package com.proxy;
//測試類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test1 {
public static void main(String[] args) {
//如果一個對象想要通過Proxy.newProxyInstance方法被代理
//那麼這個對象的類一定要有相應的接口
//就像本類中ITestDemo接口和實現類TestDemoImpl
ITestDemo test = new TestDemoImpl();
test.test1();
test.test2();
System.out.println("====================================");
/**
* 需求:
* 在執行test1和test2方法時,需要加入一些東西
* 在執行方法前打印test1/test2開始執行
* 在執行方法後打印test1/test2執行完成
* 打印的方法名要和當時調用的方法保存一致
*/
InvocationHandler handler = new ProxyDemo(test); //handler爲代理對象,代理test
//參數1爲代理對象的類加載器
//參數2爲被代理的對象的接口
//參數3爲代理對象
//返回的值是成功被代理後的對象,返回Object類型,需要根據當時的情況去轉換類型
ITestDemo t = (ITestDemo) Proxy.newProxyInstance(handler.getClass().getClassLoader(),test.getClass().getInterfaces(),handler);
t.test1();
System.out.println("====================================");
t.test2();
}
}
/*測試結果:
執行test1()方法
執行test2()方法
====================================
test1方法開始執行
執行test1()方法
test1方法執行完成
====================================
test2方法開始執行
執行test2()方法
test2方法執行完成
*/
注意:Proxy.newProxyInstance()方法有三個參數:
-
代理對象類加載器(Class Loader)
-
被代理對象的接口
-
InvocationHandler接口實例對象(也稱代理對象)。所有動態代理類的方法調用,都會交由InvocationHandler接口實現類裏的invoke()方法去處理。這是動態代理的關鍵所在。
寫在最後
我以後會經常抱你。
To Dottie!