天天看點

Java反射--反射與類操作1. 反射擷取類結構2. 反射調用構造方法3. 反射調用方法4. 反射調用成員

内容學習于:edu.aliyun.com

1. 反射擷取類結構

  使用Class實作了對象執行個體化,并且通過Class類反射構造了類的執行個體化對象,但是這并不意味着這些全部都屬于反射機制的功能,如果認真去分析的話,實際上反射可以完整的實作Java允許規定的類的操作形式。

  如果在日後的開發之中你突然發現需要對二進制檔案做更深入的一層分析的時候,那麼此時你有兩個選擇:

  • 選擇一:通過Oracle的官方标準進行二進制位元組流資料的讀取分析:
  • 選擇二:使用一些第三方工具包(Java Relection), 這個包可以實作“*.class"檔案的分析。

  Class作為所有反射操作的源頭,于是在Class類裡面就可以擷取一些結構 上的資訊,例如:類所在的包、類所繼承的父類以及類所實作的相關的接口,這些的操作方法如下:

  • 擷取程式所在的包名稱:public String getPackageName()
  • 擷取所繼承的父類:public Class<? super T> getSuperclass()
  • 擷取所有的父接口:public Class<?>[] getInterfaces()

  結構如下圖所示:

Java反射--反射與類操作1. 反射擷取類結構2. 反射調用構造方法3. 反射調用方法4. 反射調用成員

擷取父結構資訊:

interface IMessage {
}

interface IChannel {
}

abstract class AbstaractChannelMessage implements IMessage, IChannel {
}

class CloudMessage extends AbstaractChannelMessage implements IMessage, IChannel {
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = CloudMessage.class;
        System.out.println("【擷取包的名稱】:" + clazz.getPackage().getName());
        System.out.println("【擷取繼承父類】:" + clazz.getSuperclass().getName());
        System.out.println("【擷取實作接口】:" + Arrays.toString(clazz.getInterfaces()));
    }
}
           

結果:

【擷取包的名稱】:com.xzzz.demo

【擷取繼承父類】:com.xzzz.demo.AbstaractChannelMessage

【擷取實作接口】:[interface com.xzzz.demo.IMessage, interface com.xzzz.demo.IChannel]

  之是以通過反射可以擷取這些資訊,主要是由于Class類擁有了“*.class" 二進制資料的分析能力,它實際上是根據二進制的結構檔案動态擷取的内容。

2. 反射調用構造方法

  在一個類之中會提供有大量的構造方法出現,那麼所有的構造方法都可以利用反射來進行擷取,在Class類裡面定義有如下的構造方法擷取的操作:

No. 方法名稱 類型 描述
01 public Constructor getConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException 方法 根據指定的參數類型擷取指定的構造方法(public)
02 public Constructor<?>[] getConstructors() throws SecurityException 方法 擷取類所有的構造方法(public)
03 public Constructor getDeclaredConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException 方法 根據指定的參數類型擷取指定的構造方法(任意類型)
04 public Constructor<?>[] getDeclaredConstructors() throws SecurityException 方法 擷取類所有的構造方法(任意類型)

  結構圖如下圖所示:

Java反射--反射與類操作1. 反射擷取類結構2. 反射調用構造方法3. 反射調用方法4. 反射調用成員

擷取構造資訊:

class CloudMessage {
    protected CloudMessage(String str){}
    private CloudMessage(){}
    CloudMessage(int a){}
    public CloudMessage(String str,int a){}
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = CloudMessage.class;
        System.out.println("【擷取部分構造】"+Arrays.toString(clazz.getConstructors()));
        System.out.println("【擷取全部構造】"+Arrays.toString(clazz.getDeclaredConstructors()));
    }
}
           

結果:

【擷取部分構造】[public com.xzzz.demo.CloudMessage(java.lang.String,int)]

【擷取全部構造】[public com.xzzz.demo.CloudMessage(java.lang.String,int), com.xzzz.demo.CloudMessage(int), private com.xzzz.demo.CloudMessage(), protected com.xzzz.demo.CloudMessage(java.lang.String)]

  此時可以直接擷取本類的全部構造方法,如果要想擷取父類構造隻需要編寫如下代碼即可:clazz.getSuperclass().getConstructors()

  在以後編寫反射的過程裡面,構造方法不一定隻使用public來定義,  是以擷取的方法應該采用“getDeclaredConstructors()”。

  但是擷取構造并不意味着要進行簡單的輸出,這種擷取資訊的操作隻在開發工具中比較常見,但是擷取Constructor最大的意義實際上在于其可以進行反射構造調用,提供有如下的方法:

  • public T newInstance() throws InstantiationException, IllegalAccessException

  這個類中提供有一個newlnstance()方法,這個方法的主要功能是可以調用指定的構造進行對象執行個體化處理。

調用構造:

class Ball {
    private String name;
    private double price;

    public Ball(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Ball{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Ball.class;//擷取反射對象
        Constructor<?> con = clazz.getDeclaredConstructor(String.class,double.class);
        Ball ball = (Ball) con.newInstance("aaaa",1.2);
        System.out.println(ball);
    }
}
           

結果:

Ball{name=‘aaaa’, price=1.2}

  以上的操作是在JDK 1.9 之後官方推薦的做法,無參構造為Class保留。在JDK 1.8 及以前的版本裡面,關于反射對象的執行個體化操作實際上提供了兩個不同的操作方法:

  • Class類: public T newInstance() throws InstantiationException, IllegalAccessException;預設調用無參構造,如果類中沒有無參構造則将抛出異常,JDK 1.9後就直接修改為Constructor類調用;
  • Constructor 類: public T newInstance(Oject… initargs) throws InstantiationException, llgalAccessException,

    IllegalArgumentException, InvocationTargetException;

3. 反射調用方法

  類中構造方法是在對象執行個體化的時候調用一-次,但是有了對象之後就可以進行普通方法的多次調用,那麼在Class類裡面同樣定義了可以擷取類中方法執行個體的操作。

  • 擷取本類全部的方法:public Method[] getDeclaredMethods() throws SecurityException
  • 擷取本類一個指定類型的Method執行個體:public Method getDeclaredMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException
  • 擷取所有的public方法(包括父類):public Method[] getMethods() throws SecurityException
  • 擷取一個指定類型的方法(包括父類):public Method getMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException

  當擷取了一個方法之中将以Method類的執行個體進行該方法的描述,而此類的定義如下:

  • public final class Method extends Executable

  這個類的定義與之前的Construtor類是完全一樣的, 此類的繼承結構如下。

Java反射--反射與類操作1. 反射擷取類結構2. 反射調用構造方法3. 反射調用方法4. 反射調用成員

擷取方法資訊:

class Ball {
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Ball.class;//擷取反射對象
        Method methods [] = clazz.getMethods();
        for (int i = 0;i<methods.length;i++){
            System.out.print(Modifier.toString(methods[i].getModifiers()));
            System.out.print(" "+methods[i].getReturnType().getSimpleName());
            System.out.print(" "+methods[i].getName() + " (");
            Class<?> params [] = methods[i].getParameterTypes();//擷取所有參數
            for (int j = 0;j<params.length;j++){
                System.out.print(params[j].getSimpleName() + " arg-" + j);
                if (j<params.length-1){
                    System.out.print(" , ");
                }
            }
            System.out.print(")");
            Class<?> [] exps = methods[i].getExceptionTypes();//擷取異常資訊
            if (exps.length>0){
                System.out.print(" thorws ");
            }
            for (int j = 0;j<exps.length;j++){
                System.out.print(exps[j].getSimpleName());
                if (j<params.length-1){
                    System.out.print(" , ");
                }
            }
            System.out.println();
        }
    }
}
           

結果:

public final native void wait (long arg-0) thorws InterruptedException

public final void wait (long arg-0 , int arg-1) thorws InterruptedException ,

public final void wait () thorws InterruptedException

public boolean equals (Object arg-0)

public String toString ()

public native int hashCode ()

public final native Class getClass ()

public final native void notify ()

public final native void notifyAll ()

  此時的确已經擷取了類中的所有的public方法,但是這些方法是直接通過Method類中的toString0方法得到的。

  在以後如果配置了任何的第三方工具包,各個開發工具之是以可以進行代碼的檢測以及方法的擷取,就是由于反射機制的支援(二進制資料流的解析處理),但是對我們開發人員來講,真正有意義的代碼在于Method類中提供的反射調用方法:

  • public Object invoke(Object obj, Object… args) throws llegalAccessException, IlegalArgumentException, Invocation TargetException

  invoke()可以直接依據Object類的執行個體化對象(不一定是具體類型)實作反射方法調用。

用反射調用setter、getter方法:

定義一個類:

package com.xzzz.vo;

public class Ball {
    private String brand;

    public String getBrand() {
        return this.brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}
           

定義一個工具類:

package com.xzzz.demo;

public class StringUtil {
    private StringUtil() {
    }

    public static String initcap(String str) {
        if (str == null || "".equals(str)) {
            return str;
        }
        if (str.length() == 1) {
            return str.toUpperCase();
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}
           

反射調用方法:

package com.xzzz.demo;

import java.lang.reflect.Method;

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        String fieldName = "brand";//描述要操作的屬性
        String fieldValue = "我是一個品牌";//屬性的内容
        Class<?> clazz = Class.forName("com.xzzz.vo.Ball");//擷取類對象
        //不管使用反射還是具體的類型,一定要有執行個體化對象
        Object obj = clazz.getDeclaredConstructor().newInstance();
        //此時對于setter方法的參數類型無法動态擷取。先固定一個String.class
        Method setMethod = clazz.getDeclaredMethod("set" + StringUtil.initcap(fieldName), String.class);
        setMethod.invoke(obj, fieldValue);//等價于執行個體化對象.setBrand(fielValue)
        Method getMethod = clazz.getDeclaredMethod("get" + StringUtil.initcap(fieldName));
        Object returnObject = getMethod.invoke(obj);
        System.out.println(returnObject);
    }
}
           

結果:

我是一個品牌

  此時的程式實作了方法的反射調用,同時也就解釋了為什麼簡單Java類中的命名要有setter、getter 規則了。

4. 反射調用成員

  類結構裡面包含的主要是三個組成:構造、方法、成員屬性,那麼對于所有的成員屬性也是可以通過反射來實作調用的,在Class類裡面和Field裡面提供有如下的與成員有關的操作方法:

No. 方法名稱 類型 描述
01 public Field[] getFields() throws SecurityException 方法 擷取所有繼承來的public成員
02 public Field getField(String name) throws NoSuchFieldException, SecurityException 方法 擷取一個指定類型的成員
03 public Field[] getDeclaredFields() throws SecurityException 方法 根據指定的參數類型擷取本類定義的全部的成員
04 public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException 方法 擷取一個本類定義成員
05 public Class<?> getType() 方法 擷取屬性類型
06 public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException 方法 擷取屬性内容
07 public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException 方法 設定屬性内容
08 public void setAccessible(boolean flag) 方法 取消封裝

  類結構如下圖所示:

Java反射--反射與類操作1. 反射擷取類結構2. 反射調用構造方法3. 反射調用方法4. 反射調用成員

調用類中的屬性:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = ball.class;
        Object obj = clazz.getDeclaredConstructor().newInstance();
        Field brandField = clazz.getDeclaredField("brand");//擷取屬性
        brandField.setAccessible(true);//取消封裝
        System.out.println("【成員類型】"+brandField.getType().getName());
        brandField.set(obj,"我是一個品牌");//等價于對象.brand = "我是一個品牌"
        System.out.println(brandField.get(obj));//等價于 System.out.println(對象.brand)
    }
}
           

結果:

【成員類型】java.lang.String

我是一個品牌

5. UnSafe工具類

  為了進一步進行反射操作的支援擴充,在Java裡面有一個sun.misc.Unsafe類,這個類主要功能是可以通過反射來擷取對象,并且打破JVM固定的對象使用流程(因為其底層直接利用C++進行資料的讀取),這個類可以在沒有執行個體化對象的時候進行類中方法的調用。

  • 構造方法: private Unsafe() {}
  • 常量: private static final Unsafe theUnsafe = new Unsafe();

  那麼此時應該通過類内部定義的“theUnsafe"常量對象來進行Unsafe類的調用。

調用類中方法:

class Singleton{
    private Singleton(){
        System.out.println("Singleton【構造調用】");
    }

    public void print(){
        System.out.println("www.mldn.cn");
    }
}
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);//取消内封裝
        Unsafe unsafe =(Unsafe) unsafeField.get(null);
        Singleton instance = (Singleton) unsafe.allocateInstance(Singleton.class);
        instance.print();
    }
}
           

結果:

www.mldn.cn

  現在可以發現利用Unsafe類可以打破已有的JVM中關于對象的使用的模型,可以在沒有執行個體化對象的時候直接調用類中所提供的普通方法,但是這樣的操作表示所有的處理全部由程式自已完成,而所有的代碼不受到JVM的控制,也就意味着所有的垃圾回收機制就将失效

  講解意義:UnSafe隻是提供了一種底層的直接進行對象操作的支援,與實際的開發意義不大。如果在以後筆試的過程裡面要求你編寫單例設計模式的時候,請注意如下幾點:要使用懶漢式單例設計、要針對于資料的同步做出處理、補充上利用Unsafe可以繞過對象的執行個體化機制,直接調用類中的方法。