Java 基礎(chǔ) - 反射

Class 類

  • 在 Java 中大部分元素都是對象(但也有例外枉证,如靜態(tài)成員桩匪、基本數(shù)據(jù)類型)月匣;
  • 類是 java.lang.Class 類的實例對象捐川,這個對象稱為該類的類類型;
  • Class 類是私有類逸尖,不能直接用于創(chuàng)建對象古沥,只有 JVM 可以訪問瘸右;
  • 通過 Class 可以訪問系統(tǒng)為所有對象維護運行時的類型標(biāo)識、從而可以通過類的類類型創(chuàng)建類的實例對象岩齿。

對于一個普通的類太颤,可以使用 new 創(chuàng)建其對象

class Foo {
    void print() {
        System.out.println("foo");
    }
}
Foo foo1 = new Foo();

Class 類不能直接訪問;任何一個類都是 Class 類的實例對象盹沈,這個實例對象有三種表達方式

// 第一種表達方式龄章,表示任何一個類都有一個隱含的靜態(tài)成員變量 class
Class c1 = Foo.class;

// 第二種表達方式,已經(jīng)知道該類的對象通過 getClass 方法
Class c2 = foo1.getClass();

// c1乞封、c2 表示了 Foo 類的類類型(class type)
System.out.println(c1 == c2);

// 第三種表達方式
Class c3 = null;
c3 = Class.forName("com.ywh.reflect.Foo");
System.out.println(c2 == c3);

“可以通過類的類類型創(chuàng)建類的實例對象”做裙,指的是在這里可以通過 c1c2肃晚,c3 創(chuàng)建 Foo 的實例

try {
    Foo foo = (Foo) c1.newInstance();    // 需要在類中定義無參數(shù)的構(gòu)造方法锚贱,否則會拋出異常
    foo.print();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

動態(tài)加載與靜態(tài)加載

Class.forName("com.ywh.reflect.Foo")

  • 表示類的類類型,同時代表動態(tài)加載類关串;
  • 編譯時加載類(使用 new 創(chuàng)建對象拧廊,類不存在時編譯無法通過)是靜態(tài)加載類,運行時加載類是動態(tài)加載類晋修。

靜態(tài)加載類

要求所有工具類都寫在 Office 類中吧碾,耦合度高(Excel 不存在,Word 也不可用墓卦,因為無法通過編譯)倦春。

class Office {
    public static void main(String[] args) {
        if ("Word".equals(args[0])) {
            Word w = new Word();
            w.start();
        }
        else if ("Excel".equals(args[0])) {
            Excel e = new Excel();
            e.start();
        }
    }
}

動態(tài)加載類

  • Word 和 Excel 繼承標(biāo)準(zhǔn)接口,在動態(tài)加載時代替必須指定其中的某個類型的強制類型轉(zhuǎn)換趴拧;
  • 當(dāng)需要擴展工具時溅漾,只需要添加符合標(biāo)準(zhǔn)(繼承接口)的工具類,不需要再修改 Office 類著榴;
// Word 和 Excel 的共同標(biāo)準(zhǔn)接口
interface OfficeAble {
    public void start();
}

class Office {
    public static void main(String[] args) {
        try {
            Class c = Class.forName(args[0]);
            OfficeAble oa = (OfficeAble) c.newInstance();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

編譯添履、運行

javac Office.java
java Office Word

獲取類的信息

其他類型(基本數(shù)據(jù)類型、關(guān)鍵字等)都存在類類型

Class c1 = int.class;    // int 的類類型
Class c2 = String.class;    // String 類的類類型脑又,相當(dāng)于 String 類字節(jié)碼
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class;    // package 沒有暮胧,因為不是在類中聲明的

System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());    // 不包含包名的類的名稱
System.out.println(c5.getName());

對于一個類,Method 存放類的方法對象问麸,Field 存放類的成員對象往衷。

獲取類方法的信息

public static void printClassMethodMessage(Object obj) {
    // 首先要獲取類的類類型
    Class c = obj.getClass();    // 傳遞的是哪個子類的對象,c 就是該子類的類類型
    System.out.println("類的名稱是:" + c.getName());    // 獲取類的名稱

    Method[] ms = c.getMethods();    // 獲取所有有 public 方法严卖,包括父類繼承而來的
    // c.getDeclaredMethods() 獲取的是所有該類自己聲明的方法
    for (int i = 0; i < ms.length; i++) {
        Class returnType = ms[i].getReturnType();    // 得到方法的返回值類型的類類型
        System.out.print(returnType.getName() + " ");
        System.out.print(ms[i].getName() + "(");    // 得到方法的名稱
        Class[] paramTypes = ms[i].getParameterTypes();    // 獲取參數(shù)類型席舍,即參數(shù)列表的類型的類類型
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

獲取類成員的信息

  • 成員變量也是對象(java.lang.reflect.Field 的對象);
  • 其中 Field 類封裝了關(guān)于成員變量的操作哮笆。
public static void printFieldMessage(Object obj) {
    Class c = obj.getClass();
    // Field[] fs = c.getFields();    // 獲取所有的public的成員變量的信息
    Field[] fs = c.getDeclaredFields();    // 獲取該類自己聲明的成員變量的信息
    for (Field field : fs) {
        Class fieldType = field.getType();    // 得到成員變量的類型的類類型
        String typeName = fieldType.getName();
        String fieldName = field.getName();    // 得到成員變量的名稱
        System.out.println(typeName + " " + fieldName);
    }
}

獲取類構(gòu)造方法的信息

  • 構(gòu)造方法也是對象(java.lang.Constructor 的對象)
public static void printConMessage(Object obj) {
    Class c = obj.getClass();
    // Constructor[] cs = c.getConstructors();    // 獲取所有的public的構(gòu)造函數(shù)
    Constructor[] cs = c.getDeclaredConstructors();    // 得到所有的構(gòu)造函數(shù)
    for (Constructor constructor : cs) {
        System.out.print(constructor.getName() + "(");
        Class[] paramTypes = constructor.getParameterTypes();    // 獲取構(gòu)造函數(shù)的參數(shù)列表(參數(shù)列表的類類型)
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

反射的基本操作

  • 方法由名稱来颤、參數(shù)列表決定汰扭;
  • 使用方法對象的 invoke(對象, 參數(shù)列表) 方法來實現(xiàn)反射操作。

對于一個普通類

class A {
    public void print() {
        System.out.println("helloworld");
    }

    public void print(int a, int b) {
        System.out.println(a + b);
    }

    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + "," + b.toLowerCase());
    }
}

執(zhí)行反射操作

  • 先獲取類的類類型福铅;
  • 由名稱萝毛、參數(shù)列表決定獲取的方法;
  • 使用方法對象來調(diào)用方法滑黔。
try {
    A a1 = new A();
    Class c = a1.getClass();
    
    // 獲取 a1 中笆包,名稱為 “print”、第一個參數(shù)類型為 “int”略荡、第二個參數(shù)類型為 “int” 的方法并調(diào)用
    Method m0 = c.getMethod("print", int.class, int.class);
    Object o = m0.invoke(a1, 10, 20);    // 等價于a1.print(10, 20)
    
    Method m1 = c.getMethod("print", String.class, String.class);
    o = m1.invoke(a1, "hello", "WORLD");
    
    Method m2 = c.getMethod("print");
    m2.invoke(a1);

} catch (Exception e) {
    e.printStackTrace();
}

反射是常用于系統(tǒng)程序中的技術(shù)庵佣,但由于把程序邏輯插入到運行時、繞過了編譯器的判斷撞芍,從而讓編譯器無法幫助發(fā)現(xiàn)程序中的錯誤秧了,很可能在運行時出現(xiàn)預(yù)期以外的錯誤而難以解決,因此不應(yīng)在應(yīng)用程序中過多使用反射序无。

泛型的本質(zhì)

Java 中的集合常使用泛型來防止錯誤輸入(不能放入類型不兼容的元素):

ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<>();
list2.add("hello");
// list2.add(1);

但在以上兩個集合中验毡,其類類型是相等的

Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2);

由于反射的操作都是編譯后的操作,c1 == c2 返回 true 表明編譯之后集合是去泛型化的(編譯之后沒有泛型)帝嗡,所以泛型只在編譯階段有效晶通,繞過編譯就無效了。

實例:通過方法反射操作繞過編譯

try {
    Method m = c2.getMethod("add", Object.class);
    m.invoke(list1, 20);    // 繞過編譯操作哟玷,給 String 集合添加 int 類型
    System.out.println(list1.size());
    System.out.println(list1);
//  for (String string : list1) {
//      System.out.println(string);
//  }   //現(xiàn)在不能這樣遍歷狮辽,會報類型錯誤
} catch (Exception e) {
    e.printStackTrace();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巢寡,隨后出現(xiàn)的幾起案子喉脖,更是在濱河造成了極大的恐慌,老刑警劉巖抑月,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树叽,死亡現(xiàn)場離奇詭異,居然都是意外死亡谦絮,警方通過查閱死者的電腦和手機题诵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來层皱,“玉大人性锭,你說我怎么就攤上這事〗信郑” “怎么了草冈?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我怎棱,道長方淤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任蹄殃,我火速辦了婚禮,結(jié)果婚禮上你踩,老公的妹妹穿的比我還像新娘诅岩。我一直安慰自己,他們只是感情好带膜,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布吩谦。 她就那樣靜靜地躺著,像睡著了一般膝藕。 火紅的嫁衣襯著肌膚如雪式廷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天芭挽,我揣著相機與錄音滑废,去河邊找鬼。 笑死袜爪,一個胖子當(dāng)著我的面吹牛蠕趁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辛馆,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼俺陋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昙篙?” 一聲冷哼從身側(cè)響起腊状,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苔可,沒想到半個月后缴挖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡硕蛹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年醇疼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片法焰。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡秧荆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出埃仪,到底是詐尸還是另有隱情乙濒,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站颁股,受9級特大地震影響么库,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甘有,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一诉儒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亏掀,春花似錦忱反、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至间影,卻和暖如春注竿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魂贬。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工巩割, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人随橘。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓喂分,卻偏偏與公主長得像,于是被迫代替她去往敵國和親机蔗。 傳聞我的和親對象是個殘疾皇子蒲祈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345