定義
在Java 的運行時環境中,對于任意的一個類,都能夠知道這個類的所有屬性和方法,對于任意一個對象都能夠調用任意的方法和屬性,這種動態的獲取類的信息以及動態的調用對象方法的功能,稱之為反射(注意是運行狀態,非編譯)。
Java反射提供的主要功能:
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時調用任意一個對象的方法
Java反射機制實現類:
- Field類:代表類的成員變量
- Method類:代表類的方法
- Constructor類:代表類的構造方法
- Array類:提供動態的創建數組
- 以上四個類都位于java.lang.reflect 包中
- Class類:代表一個類 , 位于java.lang 包中
反射的使用
在JAVA中,無論一個類生成多少個對象,這些對象都會對應同一個Class對象 。要想使用反射,首先需要獲得待處理的類或者對象所對應的Class對象。該Class對象是在類被加載之后,由系統自動生成的。獲取該Class對象有三種方式:
- 使用Class類的靜態方法forName: Class.forName("java.lang.String") ,參數必須添加完整的路徑,包括包名。
- 使用類的.class 語法: String.class
- 使用對象的getClass()方法: String s = "reflect" ;Class<?> clazz = s.getClass()
以上便是獲取Class對象的三種方式,要使用反射,必須先獲取Class對象,一旦獲取了Class對象之后,就可以調用Class對象的方法來獲得對象和該類的真實信息。
反射相關API ,使用反射生成并操作對象
Class對象可以獲得帶操作類里的方法(Method),構造器(constructor),Field 。要操作方法需要獲得Mehtod對象,要操作構造器需要獲得Constructor對象,要操作屬性需要獲得Field對象。
下面將會結合具體的實例,來說明生成Class對象的三種方式是如何使用的:
反射初體驗,通過第一種方式,獲取Class對象,并打印java.lang.String的所有聲明方法:
Class<?> aClass = Class.forName("java.lang.String"); //獲取String類的Class對象
Method[] methdos = aClass.getDeclaredMethods(); //獲取所有的聲明方法
for(Method m : methdos) //循環遍歷Method數組
System.out.println(m); //打印方法名稱,包含了private 方法
運行結果:
通過反射調用某個類的方法
public class InvokeTest {
public int add(int a , int b){
return a +b ;
}
public String echo(String message){
return "Hello :" +message ;
}
public static void main(String[] args) throws Exception {
//通過new的方式調用
//InvokeTest invokeTest = new InvokeTest();
//System.out.println(invokeTest.add(1 , 2));
//System.out.println(invokeTest.echo("Tom"));
//通過反射調用方法
//通過內置語法,獲取Class對象
Class<InvokeTest> invokeTestClass = InvokeTest.class;
//創建Class對象表示的類的實例 ,調用Class對象的newInstance()方法
InvokeTest invokeTest = invokeTestClass.newInstance();
//獲取待操作類的Method對象。傳入方法名字,和方法的參數類型
Method add = invokeTestClass.getDeclaredMethod("add", new Class[]{int.class, int.class});
//使用這種方式也是可以得,因為getDeclaredMethod()第二個參數是一個可變參數
//Method add = InvokeTestClass.getDeclaredMethod("add" , int.class , int.class)
Method echo = invokeTestClass.getDeclaredMethod("echo", new Class[]{String.class});
//調用Method對象的invoke方法,表示含義為,調用invokeTest對象的add/echo方法,并傳出參數值
Object addResult = add.invoke(invokeTest, new Object[]{1, 2});
Object echoResult = echo.invoke(invokeTest, new Object[]{"Tome"});
//打印輸出的結果
System.out.println((Integer) addResult);
System.out.println((String) echoResult);
//運行結果
//3
//Hello :Tome
}
}
通過反射調用某個類的構造方法 ,創建對象
public class ConstructorTest {
public static void main(String[] args) throws Exception {
User user = new User() ;
//使用第三種獲取Class對象的方式。使用getClass()方法
Class<?> userClass = user.getClass();
//調用無參構造方法,創建對象實例,反射無法得知返回的類型,需要強制類型轉換
Constructor<?> constructor = userClass.getConstructor();
User user1 = (User)constructor.newInstance();
//上面兩行代碼可以替換為下面一行代碼
//User u = (User)userClass.newInstance();
user1.setAge(18);
user1.setName("zhangsan");
System.out.println(user1.getName() +"," + user1.getAge()); //zhangsan,18
System.out.println("--------------------------");
//調用有參構造方法,下面兩行方法中的參數要一一對應
Constructor<?> constructor1 = userClass.getConstructor(new Class[]{String.class, int.class});
User user2 = (User)constructor1.newInstance(new Object[]{"lisi", 30});
System.out.println(user2.getName() +"," +user2.getAge());
}
}
class User{
private String name ;
private int age ;
public User(){}
public User(String name , int age ){
this.name = name ;
this.age = age ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
通過反射生成對象有兩種方式:
1.使用Class對象的newInstance()方法,這種方法要求對象的對應類有無參構造方法
2.先使用Class對象獲得Constructor()對象,在調用Constructor對象的newInstance()方法。這種方式可以調用無參的構造方法,也可以調用有參的構造方法。但是Constructor()和newInstance()中的參數要一一對應。可參考上例。
通過反射操作類的屬性
public class FieldTest {
public static void main(String[] args) throws Exception {
//獲取Class對象,并生成實例對象
Class<?> personClass = Person.class;
Object p = personClass.newInstance();
//通過Class對象的getDeclaredField()方法,獲取類的field
Field name = personClass.getDeclaredField("name");
Field age = personClass.getDeclaredField("age");
//name字段為private 修飾,使用setAccessible()方法,取消訪問權限檢查
name.setAccessible(true);
//調用set()方法,設置p對象的值
name.set(p , "zhangsan");
age.set(p , 18);
//打印值
System.out.println(p); //name: zhangsan, age: 18
}
}
class Person{
private String name ;
public int age ;
@Override
public String toString() {
return "name: "+name +", age: "+age ;
}
}
通過setAccessible()方法,可以通過反射訪問類中的私有屬性和私有方法,通常不建議這樣使用,因為它打破了封裝的特性。但是在一些特定情況中,還是需要使用這種方式的。
通過反射操作數組
public class ArrayTest {
public static void main(String[] args) {
//創建一個類型為String, 長度為10 的數組
Object o = Array.newInstance(String.class, 10);
//為數組賦值
Array.set(o , 2 ,"hello");
Array.set(o , 5 ,"world");
//獲取數組的值,并打印
System.out.println(Array.get(o , 2)); //hello
System.out.println(Array.get(o , 5)); //world
}
}
上面簡單介紹了幾個常用的方法,旨在說明反射對方法,構造方法,字段,數組中的調用方式,除了上面的這些,還可以獲取父類,接口,包 ,全部的方法聲明,全部的field聲明等與類相關的全部信息。想要了解更多內容,請自行查閱API。
通過反射生成動態代理
先看代碼,稍后再解釋動態代理的實現過程,這個地方可能有點難理解,多看幾遍就可以了。
/*定義接口*/
public interface Subject {
public void sayHello(String name);
}
/*定義接口的實現類,也就是真實的代理調用對象*/
public class RealSubject implements Subject{
public void sayHello(String name ) {
System.out.println("hello : " + name );
}
}
/*定義動態代理類*/
public class DynamicProxy implements InvocationHandler{
private Object obj = null ; //此處定義了Object類型,表示可以代理任何對象
public DynamicProxy(Object obj){
this.obj = obj ;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//調用obj對象的method方法,并傳入參數args,本例中相當于調用obj對象的sayHello()方法,并傳入Tom 參數
method.invoke(obj , args) ;
after();
return null ; //Subject接口中方法沒有返回值,返回Null
}
//此方法表示代理的額外操作。
private void before(){
System.out.println("before proxy");
}
//此方法表示代理的額外操作
private void after(){
System.out.println("after proxy");
}
}
客戶端調用:
public class Client {
public static void main(String[] args) {
//需要代理的真實對象
RealSubject realSubject = new RealSubject() ;
//創建代理類,并將真實的代理對象,傳入構造方法中
InvocationHandler handler = new DynamicProxy(realSubject);
//運行時動態生成的代理實例,實現了RelSubject類所實現的接口,所以可以強制轉換為Subject類型,第一個參數是類加載器,第二個參數是代理類實現的接口,第三個參數是處理實際工作的handler實例
Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
//調用接口中聲明的方法 ,流程跳轉到handler的invoke()方法中執行。
subject.sayHello("Tom");
}
}
總結:
Java的動態代理類位于java.lang.reflect包下,涉及到兩個類:
interface InvocationHandler: 該接口中只定義了一個方法-invoke(),使用動態代理類時,必須實現這個接口。
Porxy:該類即為動態代理類,常用newProxyInstance()來生成代理實例,它不會做什么實質性的工作,實質性的工作是由與之關聯的InvocationHandle來處理的。也就是說生成的代理對象都有一個與之關聯的InvocationHandler對象。
動態代理的使用步驟:
- 創建被代理類的接口和實現類,因為動態代理是基于接口的。這也是動態代理的一個小小缺憾。
- 創建實現InvocationHandler接口的類,并實現invoke()方法
- 通過Proxy的靜態方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 創建一個代理實例。
- 通過上面創建的代理實例,調用方法
至此,End!
少年聽雨歌樓上,紅燭昏羅帳。
壯年聽雨客舟中,江闊云低,斷雁叫西風。
感謝支持!
---起個名忒難