怎麼當鴨子


你可以使用反射動態載入.class,從中擷取類別資訊,也可以直接利用載入的.class之Class實例,動態生成物件,更可以進一步操作物件的方法。

例如,若有個Student:
package cc.openhome;
public class Student {
    private String name;
    private int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setScore(int score) {
        this.score = score;
    }
    public int getScore() {
        return score;
    }
}

以下的程式可以動態生成Student實例,並透過setName()設定名稱,用getName()取得名稱,一切都是以Object型態操作,不用使用轉型:
Class clz = Class.forName("cc.openhome.Student");
Constructor constructor =
              clz.getConstructor(new Class[] {String.class, Integer.TYPE});

Object obj =
          constructor.newInstance(new Object[] {"caterpillar", new Integer(90)});

       
Method setter = clz.getMethod("setName", new Class[] {String.class});
setter.invoke(obj, new Object[] {"caterpillar"});
       
Method getter = clz.getMethod("getName", null);
System.out.println(getter.invoke(obj, null));

這很有趣,這表示,你可以實現動態語言中經常出現的 鴨子類型 概念:如果它走路像隻鴨子,叫聲像個鴨子,游起來像個鴨子,那它就是鴨子。

聽來很怪?尤其是你對動態語言沒概念的話,那麼,考慮一個情況,你會有個物件,它是什麼類別你一無所知,它會實作哪些介面你也不知道,你只知道它上頭會有個quack()方法,那該怎麼寫程式來呼叫執行這個方法?可以如下:
class Dog {
    public void quack() {
        System.out.println("狗兒呱呱叫");
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        doQuack(new Dog());
    }
   
    public static void doQuack(Object duck) throws Exception {
        Method quack = duck.getClass().getMethod("quack", null);
        quack.invoke(duck, null);
    }
}

doQuack()才不管丟進來的是阿貓還是阿狗,反正它會呱呱叫就好了!就算只是個Object也無所謂:
doQuack(new Object() {
    public void quack() {
        System.out.println("誰在呱呱叫");
    }
});

不過模彷鴨子類型並不是沒有代價,也就是要付出效能作為補償(根據實際的需求,也許你可分析看看,是否有哪些Method等反射物件可以快取,不用每次都動態生成,藉此改進一些效能)。