1. 程式人生 > 實用技巧 >Java反射初相識

Java反射初相識

什麼是反射

在瞭解反射之前先來看下面的Demo,首先定義了一個簡單的學生類,其中有兩個成員變數,分別是姓名:name 年齡:age 還有一個方法: void study(String val);然後通過兩種方式進行呼叫.

public class Student {
    public String name;
    private int age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void study(String val){
        System.out.println(name + "喜歡學習" + val);;
    }
}

呼叫方式一

//初始化物件
Student student = new Student();
//為變數賦值
student.setName("小舍");
//為變數賦值
student.study("Java");
//輸出結果為:小舍喜歡學習Java

呼叫方式二

//獲取Student的Class
Class student = Class.forName("com.demo.Student");
//通過Class建立Student物件
Object instance = student.newInstance();
//獲取setName方法給物件屬性賦值
//獲取setName方法,setName有一個字串的引數
Method setName = student.getMethod("setName", String.class);
//執行setName
//相當於instance.setName"小舍"
setName.invoke(instance, "小舍");
Method study = student.getMethod("study", String.class);
study.invoke(instance,"Java");
//輸出結果為:小舍喜歡學習Java

執行後可以發現這兩種呼叫方式的執行結果一致,方式一想必大家都不陌生,開發中經常用到,而方式二便是所謂的反射了.

這兩種方式有什麼區別呢?

在方式一中,已經確定了初始化的物件是什麼,然後可以通過new關鍵字來建立物件,再通過物件呼叫方法;而在方式二中,則是在執行時,通過輸入的字串(com.demo.Student)才確認要執行的類什麼,呼叫方法時,則通過類中的方法名和引數型別來呼叫.

可以說反射就是在執行時,可以建立任意一個物件,並且可以操作其任意一個變數或者方法.

現在看看來源於百度百科的定義,還有什麼疑問嗎?

Java的反射(reflection)機制是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵。

--- 百度百科

如何使用

使用反射大致分為三個步驟

  1. 獲取類的Class物件;
  2. 通過Class物件獲取類的內容(構造方法,成員方法, 成員變數)
  3. 對獲取的內容進行操作

1.獲取Class物件

//1. 通過物件呼叫getClass方法獲取。
Student stu0 = new Student();
Class stu0Class = stu0.getClass();
//2. 通過類的class屬性獲取。
Class stu1Class = Student.class;
//3. 通過Class的靜態方法forName獲取
Class stu2Class = Class.forName("com.demo.Student");

一個類只有一個Class型別的物件,無論怎麼獲取,獲取的都是同一個Class

System.out.println(stu0Class == stu1Class);//true
System.out.println(stu0Class == stu2Class);//true
System.out.println(stu1Class == stu2Class);//true

2.通過Class物件獲取類的內容

/**
 * T newInstance()建立物件
 */
Student student = (Student)stuClass.newInstance();
/**
 * Constructor getConstructor(Class... parameterTypes):獲取空參構造方法
 */
Constructor constructor = stuClass.getConstructor();
/**
 *  Constructor[] getConstructors():獲取所有的構造方法
 */
Constructor[] constructors = stuClass.getConstructors();
/**
 * Method[] getMethods():獲取類中所有成員方法
 */
Method[] methods = stuClass.getMethods();
/**
 *  Method getMethod(String name, Class... parameterTypes):獲取到的類中的指定的成員方法。 第一個引數是方法名, 第二個引數是該方法的引數列表。
 */
Method study = stuClass.getMethod("study", String.class);
/**
 * 反射獲取set/get方法分別完成賦值和取值的操作
 */
Method setName = stuClass.getMethod("setName", String.class);
/**
 *  Object invoke(Object obj, Object... args):執行獲取的方法 第一個引數是呼叫者物件,第二個物件表示呼叫方法時傳遞的實際引數
 */
setName.invoke(student,"小舍");
study.invoke(student,"Java");//小舍喜歡學習Java

/**
 *  Field getField(String name):獲取成員變數, 只能獲取到public許可權的成員變數。
 */
Field nameField = stuClass.getField("name");//public java.lang.String com.demo.Student.name
String name = nameField.getName();//name
Field ageField = stuClass.getField("age");//java.lang.NoSuchFieldException: age(許可權為 private)

3.對獲取的內容進行操作

/**
 *  Object invoke(Object obj, Object... args):執行獲取的方法 第一個引數是呼叫者物件,第二個物件表示呼叫方法時傳遞的實際引數
 */
setName.invoke(student,"小舍");
study.invoke(student,"Java");//小舍喜歡學習Java

存在的意義

通過反射,可以讓程式建立和控制任何類的物件,無需提前硬編碼目標類.例如,如果沒有反射,每當我們構建物件時,都要同過new 關鍵字來建立物件,提前對物件進行編碼,如果要改變物件,那麼必須修改原始碼,並重新編譯.如果使用反射,可以將類(com.demo.Student)寫在配置檔案中,這樣可以通過修改引數,來改變例項化物件.

反射機制極大的提高了程式的靈活性和擴充套件性,降低模組的耦合性,提高自身的適應能力,是構建框架技術的基礎所在.框架就是抽取重複的程式碼,提高編碼效率,如果提前硬編碼目標類,那如何抽取重複程式碼呢?

判斷物件成員變數是否為空

    public static void main(String[] args) throws IllegalAccessException {
        Student student = new Student();
        student.setAge(18);
        student.setName("小舍");
        String[] fieldArray = {"name", "age"};
        List<String> fieldList = Arrays.asList(fieldArray);
        if (checkObjectFiledIsNull(student,fieldList)){
            System.out.println("引數不得為空");
        }
    }

	/**
     * 校驗物件欄位是否為空
     *
     * @param object    物件
     * @param fieldList	需要判空的變數名
     * @return 為空 返回true
     * @throws IllegalAccessException
     */
    public static boolean checkObjectFiledIsNull(Object object, List<String> fieldList) throws IllegalAccessException {
        if (object == null) {
            return true;
        }
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (fieldList.contains(field.getName())) {
                Object obj = field.get(object);
                if (StringUtils.isEmpty(obj)) {
                    return true;
                }
            }
        }
        return false;
    }

(1)student.setAge(18);

(2)student.setName("小舍");

注意:去掉(1)並不會列印"引數不得為空",而去掉(2)則會列印,是因為int變數的預設初始值為0