1. 程式人生 > >理解Java反射

理解Java反射

反射簡介

Java讓我們在執行時識別物件和類的資訊,主要有2種方式:一種是傳統的RTTI,它假定我們在編譯時已經知道了所有的型別資訊;另一種是反射機制,它允許我們在執行時發現和使用類的資訊。
JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
想解剖一個類,必須先要獲取到該類的位元組碼檔案物件。而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼檔案對應的Class型別的物件
反射就是動態的從記憶體載入一個指定的類,把java類中的各種成分對映成一個個的Java物件,獲取該類中的所有的內容。

反射的功能

1.在執行時判斷任意一個物件所屬的類;
2.在執行時構造任意一個類的物件;
3.在執行時判斷任意一個類所具有的成員變數和方法(通過反射甚至可以呼叫private方法);
4.在執行時呼叫任意一個物件的方法
重點:是執行時而不是編譯時
反射的優點:增加程式的靈活性;增強了程式的擴充套件性。
反射的缺點:運用反射會使我們的軟體的效能降低,複雜度增加。

反射的使用

獲取Class物件

1:通過每個物件都具備的方法getClass來獲取。弊端:必須要建立該類物件,才可以呼叫getClass方法。
2:每一個數據型別(基本資料型別和引用資料型別)都有一個靜態的屬性class。弊端:必須要先明確該類。
  前兩種方式不利於程式的擴充套件,因為都需要在程式使用具體的類來完成。
3:使用的Class類中的方法,靜態的forName方法。指定什麼類名,就獲取什麼類位元組碼檔案物件,這種方式的擴充套件性最強,只要將類名的字串傳入即可。
示例程式碼如下:

package com.luis.test;
public class User {
    private long id;
    private String name;
    public int age;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    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;
    }
    
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

public class GetClass {
    public static void main(String[] args) {
        //1、通過物件呼叫 getClass() 方法來獲取,通常應用在:比如你傳過來一個 Object
        //  型別的物件,而我不知道你具體是什麼類,用這種方法
        User user = new User();
        Class c1 = user.getClass();
            
        //2、直接通過 類名.class 的方式得到,該方法最為安全可靠,程式效能更高
        //  這說明任何一個類都有一個隱含的靜態成員變數 class
        Class c2 = User.class;
            
        //3、通過 Class 物件的 forName() 靜態方法來獲取,用的最多,
        //丟擲 ClassNotFoundException 異常
        Class c3;
        try {
            c3 = Class.forName("com.luis.test.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

獲取類中的方法,屬性,建構函式。

public class GetClass {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.luis.test.User");
            Constructor con = clazz.getConstructor(new Class[]{});
            User instance = (User) con.newInstance();
            instance.toString();
            //獲得類完整的名字
            String className = clazz.getName();
            System.out.println("---------完整類名----------");
            System.out.println(className);
                    
            //獲得類的public型別的屬性。
            Field[] fields = clazz.getFields();
            System.out.println("---------public屬性----------");
            for(Field field : fields){
               System.out.println("public類:"+field.getName());
            }
                    
            //獲得類的所有屬性包括私有的
            Field [] allFields = clazz.getDeclaredFields();
            System.out.println("---------所有屬性----------");
            for(Field field : allFields){
                System.out.println(field.getName());
            }
                    
            //獲得類的public型別的方法。這裡包括 Object 類的一些方法
            Method [] methods = clazz.getMethods();
            System.out.println("---------public方法----------");
            for(Method method : methods){
                System.out.println(method.getName());
            }
                    
            //獲得類的所有方法。
            Method [] allMethods = clazz.getDeclaredMethods();
            System.out.println("---------所有方法----------");
            for(Method method : allMethods){
                System.out.println(method.getName());
            }
                    
            //獲得指定的屬性
            Field f1 = clazz.getField("age");
            System.out.println("---------指定屬性----------");
            System.out.println(f1);
            //獲得指定的私有屬性
            System.out.println("---------指定私有屬性----------");
            Field f2 = clazz.getDeclaredField("name");
            //啟用和禁用訪問安全檢查的開關,值為 true,則表示反射的物件在使用時應該取消 java 語言的訪問檢查;反之不取消
            f2.setAccessible(true);
            System.out.println(f2);
                            
            //建立這個類的一個物件
            Object p2 =  clazz.newInstance();
            //將 p2 物件的  f2 屬性賦值為 Bob,f2 屬性即為 私有屬性 name
            f2.set(p2,"luis");
            //使用反射機制可以打破封裝性,導致了java物件的屬性不安全。 
            System.out.println("---------建立物件----------");
            System.out.println(f2.get(p2)); //Bob
                    
            //獲取構造方法
            Constructor [] constructors = clazz.getConstructors();
            System.out.println("---------獲取構造方法----------");
            for(Constructor constructor : constructors){
                System.out.println(constructor.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

---------完整類名----------
com.luis.test.User
---------public屬性----------
public類:age
---------所有屬性----------
id
name
age
---------public方法----------
toString
getName
getId
setName
getAge
setId
setAge
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
---------所有方法----------
toString
getName
getId
setName
getAge
setId
setAge
---------指定屬性----------
public int com.luis.test.User.age
---------指定私有屬性----------
private java.lang.String com.luis.test.User.name
---------建立物件----------
luis
---------獲取構造方法----------
public com.luis.test.User()

反射獲取父類屬性

可以通過反射獲取父類的私有屬性,程式碼如下:

public class Parent {
    public String publicField = "parent_publicField";
    protected String protectField = "parent_protectField";
    String defaultField = "parent_defaultField";
    private String privateField = "parent_privateField";
}

package com.luis.test;
public class Admin extends Parent{
}

public class GetClass {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.luis.test.Admin");
            //獲取父類私有屬性值
            System.out.println(getFieldValue(clazz.newInstance(),"privateField"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Field getDeclaredField(Object obj,String fieldName) {
        Field field = null;
        Class c = obj.getClass();
             for(; c != Object.class ; c = c.getSuperclass()){
                 try {
                     field = c.getDeclaredField(fieldName);
                     field.setAccessible(true);
                     return field;
                 }catch (Exception e){
                     //如果這裡的異常列印或者往外拋,則就不會執行c = c.getSuperclass(),最後就不會進入到父類中了
                 }
             }
        return null;
    }
    public static Object getFieldValue(Object object,String fieldName) throws Exception{
          Field field = getDeclaredField(object,fieldName);
          return field.get(object);
    }
}

執行結果:parent_privateField
要注意:直接通過反射獲取子類的物件是不能得到父類的屬性值的,必須根據反射獲得的子類 Class 物件在呼叫 getSuperclass() 方法獲取父類物件,然後在通過父類物件去獲取父類的屬性值。

動態代理

代理類在程式執行時建立的代理方式被成為動態代理。代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。
介面和實現類:

public interface Interface {
    void doSomething();
    void somethingElse(String arg);
}

public class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething.");
    }

    @Override
    public void somethingElse(String arg) {
         System.out.println("somethingElse " + arg);
    }
}

動態代理物件處理器:

public class DynamicProxyHandler implements InvocationHandler {
    private Object proxyed;

    public DynamicProxyHandler(Object proxyed) {
        this.proxyed = proxyed;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("代理工作了.");
         return method.invoke(proxyed, args);
    }
}

測試類:

public class Main {
    public static void main(String[] args) {
        RealObject real = new RealObject();
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[] {Interface.class},new DynamicProxyHandler(real));
        proxy.doSomething();
        proxy.somethingElse("luis");
    }
}

反射極大地增強了程式碼的靈活性,廣泛應用於各種框架中,因而對於反射的掌握是非常重要的。

本文參考了:
https://www.cnblogs.com/ysocean/p/6516248.html
https://www.cnblogs.com/luoxn28/p/5686794.html
https://blog.csdn.net/ShadowySpirits/article/details/79756259