Java 反射入門 | A short introduction to Java reflection

反射在Java是個比較奇怪的概念,背後的思想其實很深刻。一般程序包括程序(code)和數據(data)兩部分。如果一個程序計算1+1,那麼1和1是數據,而加法這個操作是code,code是編譯時就決定的,編譯後就不能再更改,比如這裡的加法。而反射這個概念允許了data也可以是code的概念。比如現在程序變成了f(1, 1),f是一個未知函數,它也是”data”,可以由用戶提供。用戶提供了乘法,那就是乘法,提供了減法,那就是減法。這樣提供一個讓程序在運行時修改自己的機制。這樣能提高程序的表達力,一些Java的框架比如Spring就大量用到反射。Spring裏面很多magical的現象就是這樣實現的,比如我明明只寫了一個class,另一個地方我就自動有了一個這個class的object,而我明明沒有在任何地方初始化這麼一個對象。IDE裏面自動提示一個class裏面的available methods應該也是用到了reflection。在應用中有些壞處也是顯而易見,需要小心。在Java裏面reflection速度慢還是其次,IDE很難檢測到代碼被引用,還有很多compiler可以檢測到的錯誤被漏過了,只能在run time出問題。而且允許外部輸入代碼也帶來安全問題。很多網絡攻擊的原理比如SQL injection就是利用了data也是code的這種特性。關於refelection很多的用法以後我會再寫。

本文通過一個簡單的Java例子來演示一下Java的reflection。Just get a feeling.

Animal.java

package learn.java.reflection;

public interface Animal {
    public void whoami();
}

Tiger.java

package learn.java.reflection;

public class Tiger implements Animal {
    @Override
    public void whoami() {
        System.out.println("I am a tiger");
    }
}

Lion.java

package learn.java.reflection;

public class Lion implements Animal {
    @Override
    public void whoami() {
        System.out.println("I am a lion");
    }
}

Zoo.java

package learn.java.reflection;

import java.util.Scanner;

public class Zoo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Choose [1] for tiger, [2] for lion: ");
        String opt = scanner.nextLine();
        String className = null;
        while (true) {
            if ("1".equals(opt)) {
                className = "learn.java.reflection.Tiger";
                break;
            } else if ("2".equals(opt)) {
                className = "learn.java.reflection.Lion";
                break;
            } else {
                System.out.println("Choose [1] for tiger, [2] for lion: ");
                opt = scanner.nextLine();
            }
        }

        Class cls = Class.forName(className);
        Animal animal = (Animal) cls.newInstance();
        animal.whoami();
    }
}

運行結果

Choose [1] for tiger, [2] for lion: 
1
I am a tiger

Choose [1] for tiger, [2] for lion: 
2
I am a lion

Zoo.java 為主程序。裏面的animal是什麼要看用戶提供的data決定而不是compile time決定的。在IDE裏面,都找不到Lion.java和Tiger.java的引用。因為他們是這樣被用到的:Class cls = Class.forName(className)。className還是個runtime才能知道的String,連人類都不是很容易能發現,IDE能知道就真的實現人工智能了。

雖然我本人並不是Reflection的粉絲,但是因為合作中同事可能會用到,或者公司所選的框架用到,比如Spring之類也是被廣泛使用,還是應該抱有開放的心態學習並駕馭它。

另外,本文提供的小程序程序也包含了Java從鍵盤輸入data的例子。

Leave a Comment

Your email address will not be published.