Java(十二)——反射


反射

一、反射

通過Class實例獲取class信息的方法稱為反射(Reflection)

  • JVM為每個加載的classinterface創建了對應的Class實例來保存classinterface的所有信息

  • 獲取一個class對應的Class實例后,就可以獲取該class的所有信息;

二、Class類及其實例

1、Class類

Java程序在運行時,Java運行時系統一直對所有的對象進行所謂的運行時類型標識,即所謂的RTTI。這項信息紀錄了每個對象所屬的類。虛擬機通常使用運行時類型信息選准正確方法去執行,用來保存這些類型信息的類是Class類。Class類封裝一個對象和接口運行時的狀態,當裝載類時,Class類型的對象自動創建。
簡單解釋上面一段話:
1、Class類也是類的一種,只是名字和class關鍵字高度相似。Java是大小寫敏感的語言。
2、Class類的對象內容是你創建的類的類型信息,比如你創建一個shapes類,那么,Java會生成一個內容是shapes的Class類的對象。
3、Class類的對象不能像普通類一樣,以 new shapes() 的方式創建,它的對象只能由JVM創建,因為這個類沒有public構造函數。
4、Class類的作用是運行時提供或獲得某個對象的類型信息。

2、獲取Class實例

1、Class類的靜態方法forName

// 如果知道一個class的完整類名,可以通過靜態方法Class.forName()獲取
Class cls = Class.forName( String className);

Class cls = Class.forName("java.lang.String");

2、對象的getClass()方法

// 如果我們有一個實例變量,可以通過該實例變量提供的getClass()方法獲取 
Student student = new Student();
 Class<? extends Student> aClass1 = student.getClass();

3、使用類名加.class

// 直接通過一個class的靜態變量class獲取
Class classes = Student.class;

3、獲取基本信息

public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
        System.out.println("---------------------------------------------");
    }
}

三、Class實例操作class的成員變量

public class Student{
    private String name;
    private int age;
    
    public Student(){}
    
    public Student(String name) {
        this.name = name;
    }
}


public class Main{
    public static void main(String[] args){
        ...
    }
}

1、獲取類的變量

注意:

getField(name)getDeclaredField(name)中 name 可能不存在,源碼中用了throws來拋出可能出現的異常。所以在使用時,必須用try...catch...來捕獲異常

Field getField(name)	   // 根據字段名獲取包括父類字段在內的某個public的field()
Field getDeclaredField(name)		// 根據字段名獲取當前類的某個field(不包括父類)
Field[] getFields()			// 獲取包括父類在內的所有public的field,返回值為數組
Field[] getDeclaredFields()			// 獲取當前類的所有field(不包括父類),返回值為數組
Class cls = Student.class;
try{
    Field f = cls.getDeclaredField("name");
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}


2、獲取變量的屬性

getName()       //返回字段名稱
getType()			//返回字段類型,也是一個Class實例
getModifiers()		// 返回字段的修飾符,它是一個int,不同的bit表示不同的含義。
Class cls = Student.class;
try{
    Field f = cls.getDeclaredField("name");
    f.getName();    // name
    f.getType();		// String
    f.getModifiers();
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}

3、獲取、修改變量的值

get(obj);   // 獲取obj對象xx變量值
set(obj, value);   // 設置obj對象的xx變量的值為value
// 獲取s對象name的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
try{
    Field f = c.getDeclaredField("name");
    f.setAccessible(true);     // 由於name為私有變量,外部無法訪問,通過setAccessible(true)可以允許訪問任何權限變量
    Object value = f.get(s);    // 獲取值
    System.out.println(value); // "Xiao Ming"
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}

 
// 設置s對象的age變量的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
Field f = c.getDeclaredField("age");
f.setAccessible(true);
f.set(s, 18)    // 設置值

四、Class實例操作class的方法

public class Student{
    private String name;
    private int age;
    
    public Student(){}
    
    public Student(String name) {
        this.name = name;
    }
    
    public void setName(String name){
        this.name = name
    }
    
    public static void add(int a){
        a += 1;
    }
    
    private void check(){
        ...
    }
    
}


public class Main{
    public static void main(String[] args){
        ...
    }
}

1、獲取類的方法

注意:

getMethod(name)getDeclaredMethod(name)中 name 可能不存在,源碼中用了throws來拋出可能出現的異常,所以在使用時,必須用try...catch...來捕獲異常

Method getMethod(name, Class...)		// 獲取包括父類方法在內的某個public的Method
Method getDeclaredMethod(name, Class...)		// 獲取當前類的某個Method(不包括父類)
Method[] getMethods()		// 獲取包括父類方法在內的所有public的Method,返回一個數組
Method[] getDeclaredMethods()		// 獲取當前類的所有Method(不包括父類),返回一個數組
Class cls = Student.class;
try {
    
    Method method = cls1.getDeclaredMethod("setName", String.class);
}catch (Exception e){
    ...
}

2、獲取方法的屬性

getName()		// 返回方法名稱
getReturnType()     // 返回方法返回值類型,也是一個Class實例
getParameterTypes()     // 返回方法的參數類型,是一個Class數組
getModifiers()		// 返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
Class cls = Student.class;
try {
    // 獲取setName方法,參數為String
    Method method = cls1.getDeclaredMethod("setName", String.class);
    System.out.println(method.getName());    //  setName
}catch (Exception e){
    ...
}

3、調用方法

invoke()     // 調用執行方法

(1)調用public非靜態方法

invoke(obj, 參數)   // 對Method實例調用invoke就相當於調用該方法, 
    // invoke的第一個參數是對象實例,即在哪個實例上調用該方法,后面的可變參數要與方法參數一致,否則將報錯。
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 獲取setName方法,參數為String
    Method method = cls1.getDeclaredMethod("setName", String.class);
    method.invoke(s, "john")
}catch (Exception e){
    ...
}

(2)調用public靜態方法

調用靜態方法時,由於無需指定實例對象,所以invoke方法傳入的第一個參數永遠為null

Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 獲取setName方法,參數為String
    Method method = cls1.getDeclaredMethod("add", int.class);
    method.invoke(null, 5)
}catch (Exception e){
    ...
}

(3)調用 非public 方法

對於非public方法,我們雖然可以通過Class.getDeclaredMethod()獲取該方法實例,但直接對其調用將得到一個IllegalAccessException。為了調用非public方法,我們通過Method.setAccessible(true)允許其調用。

注意:

  • setAccessible(true)可能會失敗。如果JVM運行期存在SecurityManager,那么它會根據規則進行檢查,有可能阻止setAccessible(true)。例如,某個SecurityManager可能不允許對javajavax開頭的package的類調用setAccessible(true),這樣可以保證JVM核心庫的安全
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 獲取setName方法,參數為String
    Method method = cls1.getDeclaredMethod("check");
    Method.setAccessible(true)
    method.invoke(s)
}catch (Exception e){
    ...
}

4、多態

使用反射調用方法時,仍然遵循多態原則:即總是調用實際類型的覆寫方法(如果存在)。

即先從子類中找有無方法,沒有再去父類

五、調用構造方法

注意:

getConstructor(name)getDeclaredConstructor(name)中 name 可能不存在,源碼中用了throws來拋出可能出現的異常,所以在使用時,必須用try...catch...來捕獲異常

getConstructor(Class...)		// 獲取某個public的Constructor
getDeclaredConstructor(Class...)		// 獲取某個Constructor
getConstructors()			// 獲取所有public的Constructor
getDeclaredConstructors()			// 獲取所有Constructor
public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取構造方法Integer(int)
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 調用構造方法,實例化產生實例
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 獲取構造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

六、獲取繼承關系

Class i = Integer.class;
// 獲取class的父類
Class n = i.getSuperclass();

// 獲取class的所有接口
Class[] is = s.getInterfaces();

// 通過Class對象的isAssignableFrom()方法可以判斷一個向上轉型是否可以實現。
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因為Number不能賦值給Integer


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM