Java反射總結


 

能夠分析類能力的程序稱為反射。對於給定的Java類名,可以通過反射獲取類的信息、將類的各成分映射出相應的Java類。

Class類

    在程序運行期間,Java運行時系統始終對所有的對象維護一個被稱為運行時的類型標識。這個信息跟蹤着每個對象所屬的類。虛擬機利用運行時類型信息選擇相應的方法執行。可以通過專門的Java類訪問這些信息。保存這些信息的類被稱為Class

創建Class類對象的三種方法:

1. 通過getClass方法

    Object中的getClass方法將返回一個Class類型的實例。

class Person {
   //...
}

Person p = new Person();
Class c = p.getClass();

2. forName方法

    可以通過靜態方法forName獲得類名對應的Class對象。

String className = "java.util.Date";
Class c = Class.forName(className);

    如果需要在運行過程中需要改變類名,就可以使用這種方法。當然,字符串className必須是一種類名或接口(包括包名),否則會出現異常。

3. T.class

Class c1 = Date.class;
Class c2 = int.class;
Class c3 = Double[].class;

    T表示任意的Java類型。通過T.class獲取匹配的類對象。

注意:一個Class對象實際表示一種類型,而這種類型未必是一種類。

虛擬機為每個類型管理一個Class對象。因此可以用==運算符來實現兩個類對象的比較

if (p.getClass() == Person.class) {...}

newInstance方法

    通過newInstance方法可以創建一個類的實例。

Person person = p.getClass().newInstance();

    創建了一個與p具有相同類類型的實例。newInstance調用默認的構造器初始化創建的對象。如果這個類沒有默認的構造器,就會拋出異常。

利用反射解析類結構

獲取包名+類名(包括父類名)

package com.xiaoxiaoyihan.reflection;

class Person {
    //...
}

class Student extends Person{

}

public class ReflectionDemo1 {
    public static void main(String[] args) {
        Student s = new Student();
        Class cl = s.getClass();
        Class superCl = cl.getSuperclass();
        System.out.println("獲取類名:" + cl.getName());
        System.out.println("獲取父類名:" + superCl.getName());
    }
}

【運行結果】:
獲取類名:com.xiaoxiaoyihan.reflection.Student
獲取父類名:com.xiaoxiaoyihan.reflection.Person

說明:如果類在一個包中,包的名字也作為類名的一部分。

一點擴展:

首先看一個例子:

class Person {
    private String name = "蕭蕭弈寒";
    // 省略setter和getter
}
class Animal {
    private String name = "paqi";
    // 省略setter和getter
}

Person p;
Animal a;
Class cPerson = p.getClass();
Class cAnimal = a.getClass();
// cPerson.getName()獲取的是類名、p.getName()是Person實例的name屬性值
System.out.println(cPerson.getName() + "<--->" + p.getName());
System.out.println(cAnimal.getName() + "<--->" + p.getName());

【運行結果】:
com.xxyh.reflec.Person<--->蕭蕭弈寒
com.xxyh.reflec.Animal<--->paqi

由此說明,一個Person對象p表示一個特定人的屬性,一個Animal對象a表示一個特定動物的屬性,一個Class對象表示一個特定類(Person或Animal)的屬性。從這點看,Class的實例是一種對象

解析構造函數(Constructor)

Class類中的getConstructors方法將返回公共構造器數組。Class類中的getDeclaredConstructors將返回類中聲明的構造器數組。

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Constructor;
class Person {
    private String name;
    private int age;

    private Person() {}

    protected Person(String name) {
        this.name = name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class ReflectionDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class cl = Class.forName("com.xiaoxiaoyihan.reflection.Person");  // 直接拋出異常以簡化代碼 

        Constructor[] constructors = cl.getDeclaredConstructors();
        //Constructor[] constructors = cl.getConstructors();
        for (Constructor c :constructors) {
            System.out.println(c);
        }
    }
}

【運行結果】:

private com.xiaoxiaoyihan.reflection.Person()
protected com.xiaoxiaoyihan.reflection.Person(java.lang.String)
public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)

// public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int)// 本部分為運行getConstructors方法輸出結果

對結果加以分析,會發現通過System.out.println(c);直接打印的構造函數是由幾部分組成的,其中包括了修飾符(public/protected/private)、類名以及構造函數的參數,那么,這些部分是如何獲取的呢?

Modifier類中包含靜態方法getModifiers方法,它返回一個整數i,用不同的數值表示public、static、final這樣的修飾符。Modifier中的靜態方法toString(i)返回對應的修飾符。Constructor類中包含由靜態方法getParameterTypes,它返回一個描述參數類型的Class對象數組

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

class Person {
    private String name;
    private int age;

    private Person() {}

    protected Person(String name) {
        this.name = name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class ReflectionDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class cl = Person.class;

        Constructor[] constructors = cl.getDeclaredConstructors();
        for (Constructor c :constructors) {
            // 獲取包名和類名
            String name = c.getName();

            // 獲取修飾符
            String modifiers = Modifier.toString(c.getModifiers());
            if (modifiers.length() > 0) {   //如果有修飾符
                System.out.print(modifiers + " ");
            }

            System.out.print(name + "(");

            // 獲取構造函數的參數類型
            Class[] paramTypes = c.getParameterTypes();

            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {    // 如果不止一個參數,使用","將參數類型分割
                    System.out.print(",");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }

    }
}

【運行結果】:

private com.xiaoxiaoyihan.reflection.Person();
protected com.xiaoxiaoyihan.reflection.Person(java.lang.String);
public com.xiaoxiaoyihan.reflection.Person(java.lang.String,int);

解析方法(Method)

Class類中的getMethods將返回方法的數組,其中包括本類的所有公共方法、從接口實現的方法、父類的公共(public)方法、父類的父類的公共方法……一直延伸到Object的公共方法getDeclaredMethods方法將返回本類聲明的方法、從接口實現的方法

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;


class Person/* extends Object */ {
    private void privateMethodPerson() {
        //...
    }

    protected void protectedMethodPerson() {
        // ...
    }

    public void publicMethodPerson() {
        //...
    }
}

interface Smoke {
    void smoking();
}

class Student extends Person implements Smoke{
    @Override
    public void smoking() {
        // ...
    }

    private void privateMethodStudent() {
        //...
    }

    protected void protectedMethodStudent() {
        // ...
    }

    public void publicMethodStudent() {
        //...
    }
}


public class ReflectionDemo1 {
    public static void main(String[] args) {
        Student s = new Student();
        Class cl = s.getClass();
        Method[] methods = cl.getDeclaredMethods();
//        Method[] methods = cl.getMethods();

        for (Method m : methods) {
            System.out.println(m);
        }
    }
}

【運行結果】:

public void com.xiaoxiaoyihan.reflection.Student.smoking()
protected void com.xiaoxiaoyihan.reflection.Student.protectedMethodStudent()
private void com.xiaoxiaoyihan.reflection.Student.privateMethodStudent()
public void com.xiaoxiaoyihan.reflection.Student.publicMethodStudent()

上面的例子故意給出一種繼承結構,是為了查看getMethods的結果,在此就不給出結果了。注意Person默認繼承了Object類。順便說一句,筆者(非科班自學菜鳥)曾在面試的時候被問到Object中有哪些方法,估計他是想問有哪些常用方法吧,我沒答全~2333,不知道讀者您能一眼看出結果嗎??。

類似地,我們看到上面的System.out.println(m);打印出方法由修飾符返回值類名方法名以及參數組成。那么這些部分是如果獲取的呢? 與Construction類相似,Method類中也提供了靜態方法getParamTypes,該方法返回描述參數類型的Class對象數組。此外,Method還提供了getReturnType方法,用於獲取返回類型的Class對象。

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

class Person {

    private String name = "蕭蕭弈寒";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void speak() {
        // ...
    }

    public final void eat() {
        // ...
    }
}


public class ReflectionDemo1 {
    public static void main(String[] args) {

        Class cl = Person.class;

        Method[] methods = cl.getDeclaredMethods();
        for (Method m : methods) {
            // 返回類型的Class對象
            Class retType = m.getReturnType();
            //
            String retTypeName = retType.getName();

            // 獲取方法名
            String name = m.getName();

            String modifiers = Modifier.toString(m.getModifiers());
            if (modifiers.length() > 0) // 如果有修飾符
                System.out.print(modifiers + " ");

            // 返回值名
            System.out.print(retType.getName() + " ");

            System.out.print(name + "(");

            Class[] paramTypes = m.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) { // 如果不止一個參數,用","分割
                    System.out.print(paramTypes[i].getName());
                }
            }
            System.out.println(");");
        }

    }
}

【運行結果】:
public java.lang.String getName();
public void setName();
public static void speak();
public final void eat();

解析域(Field)

Class類中的getDeclaredFields方法返回類中聲明的域數組,getFields方法返回類中的公有域、接口中的域所組成的Field對象數組。

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Field;
import java.util.Date;

class Person {
    private String name;
    protected int age;
    public Date birthday;
}

class Student extends Person implements Smoke{
    private float score;
}

interface Smoke {
    String brand = "大中華";
}

public class ReflectionDemo1 {
    public static void main(String[] args) {
        Class cl = Student.class;

        Field[] fields = cl.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
    }
}

【運行結果】:
public static final java.lang.String com.xiaoxiaoyihan.reflection.Smoke.brand
public java.util.Date com.xiaoxiaoyihan.reflection.Person.birthday

    結果顯示了字段由修飾符類型類名字段名構成。這里需要引入Field類中的getType方法,它返回字段聲明類型的Class對象。下面同樣作出解析:

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Date;

class Person {
    private String name;
    protected int age;
    public Date birthday;
}

public class ReflectionDemo1 {
    public static void main(String[] args) {
        Class cl = Person.class;

        Field[] fields = cl.getDeclaredFields();
        for (Field f : fields) {
            // 屬性類型的Class對象
            Class type = f.getType();

            // 屬性類型名
            String name = type.getName();
            System.out.print(" ");

            // 修飾符
            String modifiers = Modifier.toString(f.getModifiers());

            if (modifiers.length() > 0) {   // 如果有修飾符
                System.out.print(modifiers + " ");
            }

            System.out.println(type.getName() + " " + name + ";");
        }
    }
}

【運行結果】:

private java.lang.String java.lang.String;
protected int int;
public java.util.Date java.util.Date;

反射的應用

操作對象屬性(域)的值

    前面已經知道如何查看任意對象的數據域名稱和類型。在編寫程序時,如果想要查看域名和類型是很簡單的事情,而反射機制可以查看在編譯時還不清楚的對象域。

利用get方法獲取域(屬性)值

    查看對象域的關鍵方法是Field類的get方法。如果f是一個Field類型的對象,obj是某個包含f域的類的對象,f.get(obj)將返回一個對象,類型為Object,其值為obj域的當前值。示例:

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Field;

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
public class ReflectionDemo2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Person p = new Person("蕭蕭弈寒");

        Class clz = p.getClass();
        Field f = clz.getDeclaredField("name");
        //f.setAccessible(true);
        Object name = f.get(p);

        System.out.println(name);
    }
}

【運行結果】:

Exception in thread "main" java.lang.IllegalAccessException: Class com.xiaoxiaoyihan.reflection.ReflectionDemo2 can not access a member of class com.xiaoxiaoyihan.reflection.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:109) ……

說明:因為反射機制的默認行為受限於Java的訪問控制。如果一個Java程序沒有受限於安全管理器的控制,就可以覆蓋訪問控制。Field,Method或Constructor對象提供了setAccessible方法用於覆蓋訪問控制。

    get方法還需要解決另一個問題,get方法的返回值是Object類型的,上面的name是String類型的,將String類型值,賦給Object對象沒有問題,但是如果出現double類型的域呢?Java中的數值類型不是對象。可以通過Field類的getDouble方法,也可以調用get方法然后進行強制類型轉換。反射機制會自動地將這個域值打包到相應的對象包裝器中,對於double類型,將打包成Double。

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Field;

class Person {
    private double salary;

    public Person(double salary) {
        this.salary = salary;
    }
}
public class ReflectionDemo2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Person p = new Person(100);

        Class clz = p.getClass();

        Field f = clz.getDeclaredField("salary");
        f.setAccessible(true);
//        double salary = (double) f.get(p);
        double salary = f.getDouble(p);
        System.out.println("薪水:" + salary);
    }
}

【運行結果】: 薪水:100.0

利用set方法設置域(屬性)值

   當然也可以使用set方法,對obj對象的f域設置新值。set方法的簽名是:

void set(obj, value)    // obj:操作的對象;value:新值

示例:

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.Field;

class Person {
    private String name;
    private double salary;

    public Person(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public String getName() {
        return name;
    }
}
public class ReflectionDemo2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Person p = new Person("張三", 100);

        System.out.println(p.getName() + "-----" + p.getSalary());

        Class clz = Person.class;

        Field f = clz.getDeclaredField("name");
        f.setAccessible(true);
        f.set(p, "蕭蕭弈寒");

        f = clz.getDeclaredField("salary");
        f.setAccessible(true);
        f.set(p, 200);

        System.out.println(p.getName() + "-----" + p.getSalary());

    }
}

 

調用任意方法

invoke方法

    與Field類的get方法查看對象的域相似,Method類提供了一個invoke方法,它允許調用包裝在當前Method對象中的方法。invoke方法的簽名是:

Object invoke(Object obj, Object...args)

第一個參數是隱式參數,其余的對象提供了顯示參數。

例如,m1代表Person的getName方法,那么通過invoke方法獲取name的形式為:

String n = m1.invoke(p);

如果返回的是基本類型,invoke方法會返回其包裝器類型。例如,m2表示getSalary方法,那么返回的對象實際是一個Double,必須響應的完整類型轉換。

double s = m2.invoke(p);

根據前面的介紹,可以通過Class類的靜態方法getDeclaredMethods獲取Method對象數組,然后對返回的數組進行遍歷,找到指定的方法。與getField方法類似,getField方法通過表示域名的字符串,返回一個Field對象。然而,由於方法存在重載的情況,有可能獲得若干個相同名字的方法。因此,還必須提供方法的參數類型。getMethod方法的簽名:

Method getMethod(String name, Class...paramTypes)

示例:

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Person {
    private String name;
    private double salary;

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public void raiseSalary(double raise) {
        salary += raise;
    }

    public Person(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

}

public class ReflectionDemo2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Person p = new Person("蕭蕭弈寒", 100);

        // 獲取getName方法對象
        Method m1 = p.getClass().getMethod("getName");
        // 調用getName方法
        String name = (String)m1.invoke(p);
        System.out.println("我的名字是:" + name);

        // 獲取raiseSalary方法對象,需要傳double類型的參數
        Method m2 = p.getClass().getMethod("raiseSalary", double.class);
        // 調用raiseSalary方法,並傳入參數值
        m2.invoke(p, 200);
        System.out.println("加薪后:salary=" + p.getSalary());
    }
}

【運行結果】:
我的名字是:蕭蕭弈寒
加薪后:salary=300.0

對於靜態方法,invoke的第一個參數可以被忽略,既可以將它設置為null。

package com.xiaoxiaoyihan.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Person {
    public static void speak(String name) {
        System.out.println("木頭!" + name + ",你倒是說話啊!");
    }

}

public class ReflectionDemo2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method m = Person.class.getMethod("speak", String.class);
        m.invoke(null, "蕭蕭弈寒");
    }
}

【運行結果】: 木頭!蕭蕭弈寒,你倒是說話啊!

注意:invoke的參數和返回值必須是Object類型的。這就意味着必須進行多次的類型轉換。這樣做將會是編譯器錯過檢查代碼的機會。此外,通過反射獲得方法指針的代碼比直接調用方法效率低。

 

 


免責聲明!

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



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