JavaSE 基礎(chǔ)學(xué)習(xí)之六 —— Java 的反射操作

1.java.lang.Class 類

參考地址:
《Java源碼解析(2) —— Class(1)》
《Class類詳解》


萬(wàn)事萬(wàn)物都是對(duì)象胚泌。我們平常接觸到的類绣夺,本身也是一種對(duì)象,它的類型是 Class薛耻,也可以說(shuō) Class 是類的類型输钩,即類類型 (Class Type)豺型;任何一個(gè)類,都是 java.lang.Class 的一個(gè)實(shí)例對(duì)象买乃。

Class 是 Java 的基本類之一姻氨,也是反射機(jī)制的基礎(chǔ),它的意義是類的抽象剪验,即對(duì)“類”進(jìn)行描述肴焊。比如獲得類的屬性的方法 getField,有獲得該類的所有方法功戚、所有公有方法的方法 getMethods, getDeclaredMethods娶眷。同時(shí),Class 也是 Java 類型中最重要的一種啸臀,表示原始類型(引用類型)及基本類型茂浮。

(1) 如何表示這個(gè)實(shí)例對(duì)象?

  • 第一種:類名.Class
  • 第二種:對(duì)象.getClass()
  • 第三種:Class.forName("類的全稱");

編譯時(shí)刻加載類壳咕,稱為靜態(tài)加載類席揽,比如通過(guò) new 關(guān)鍵字加載的類。
運(yùn)行時(shí)刻加載類谓厘,稱為動(dòng)態(tài)加載類幌羞,Class.forName() 方法就是 Java 語(yǔ)言中唯一一種動(dòng)態(tài)加載的方法。動(dòng)態(tài)加載類在編譯不會(huì)報(bào)錯(cuò)竟稳,在運(yùn)行時(shí)才會(huì)加載属桦,使用接口標(biāo)準(zhǔn)能更方便動(dòng)態(tài)加載類的實(shí)現(xiàn)。所以功能性的類盡量使用動(dòng)態(tài)加載他爸,而不用靜態(tài)加載聂宾。

有了類的類類型,就可以獲取類的所有信息诊笤;具體如下例所示:

:寫一個(gè)方法系谐,接受一個(gè)對(duì)象,然后打印該對(duì)象所屬類的所有信息讨跟;

package homework4_27;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class homework1 {
    public static void main(String[] args) {
        Object o = new Object();
        printClassMsg("hello");
    }
    
    // 寫一個(gè)方法纪他,接受一個(gè)對(duì)象,然后打印該對(duì)象所屬類的所有信息晾匠; (Reflect.Demo2.java)
    // 打印所有方法的返回值類型茶袒、方法名,參數(shù)類型列表      
    // 得到所有的聲明的成員變量凉馆,打印成員變量的類型薪寓,名稱
    public static void printClassMsg(Object obj){
        // 獲取類類型
        Class c = obj.getClass();
        System.out.println("類類型名稱"+c.getName());
        
        // 獲取方法列表,包括方法名澜共,返回值類型向叉,參數(shù)列表
        System.out.println("======================");
        // getMethods(): 獲取所有公有的方法;
        Method[] ms = c.getMethods();
        System.out.println(c.getName() + " 的類方法:");
        for(Method m : ms) {
            System.out.println("------------");
            System.out.println("  類方法名:" + c.getName() + "." + m.getName());
            System.out.println("  返回值類型:" + m.getReturnType().getName());
            System.out.println("  參數(shù)類型:");
            Class[] c_params = m.getParameterTypes();
            for(Class param : c_params) {
                System.out.println("    " + param.getName());
            }
        }
        
        // 獲取類中已經(jīng)聲明的成員變量
        System.out.println("======================");
        // 獲取所有聲明的成員變量
        // c.getFields();
        
        // 獲取該類的所有成員變量
        Field[] f = c.getDeclaredFields();
        System.out.println(c.getName() + " 的成員變量:");
        for(Field fie : f){
            System.out.println("------------");
            System.out.println("  變量類型:" + fie.getType().getName());
            System.out.println("  變量名稱:" + fie.getName());
        }
    }
}

(2) 框架的原理

Java 框架就是一些類和接口的集合咳胃,通過(guò)這些類和接口協(xié)調(diào)來(lái)完成一系列的程序?qū)崿F(xiàn)植康。框架又叫做開發(fā)中的半成品展懈,它通過(guò)了編譯销睁,打成了 jar 包,但不能提供整個(gè) WEB 應(yīng)用程序的所有東西存崖。但是有了框架冻记,我們就可以集中精力進(jìn)行業(yè)務(wù)邏輯的開發(fā),同時(shí)框架會(huì)創(chuàng)建我們寫的類的實(shí)例對(duì)象来惧,并調(diào)用我們寫的方法冗栗;這樣我們就不用去關(guān)心它的技術(shù)實(shí)現(xiàn)以及一些輔助的業(yè)務(wù)邏輯。
說(shuō)白了 Java 框架就是封裝好方便程序員操作的類,在運(yùn)行時(shí)動(dòng)態(tài)加載我們的類(通過(guò) Class.forName(類名) 方法)隅居,并創(chuàng)建對(duì)象調(diào)用方法钠至。這樣可以使項(xiàng)目的開發(fā)更簡(jiǎn)單,維護(hù)起來(lái)也更容易胎源。

2. Java 的反射機(jī)制

參考網(wǎng)址:
《Java基礎(chǔ)之—反射(非常重要)》
《Java源碼解析(2) —— Class(1)》


前面提到框架是開發(fā)中的半成品棉钧,它可以在運(yùn)行過(guò)程中加載實(shí)例,并填充業(yè)務(wù)涕蚤。在框架中宪卿,反射是框架設(shè)計(jì)的靈魂。

Java 反射機(jī)制是在運(yùn)行狀態(tài)中進(jìn)行的万栅,它把 Java 類中的各種成分映射成一個(gè)個(gè)的 Java 對(duì)象佑钾。使用反射的前提條件,是首先必須得到字節(jié)碼的 Class烦粒,它用于表示 .class 文件休溶。通過(guò)動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為 Java 語(yǔ)言的反射機(jī)制。具體對(duì)于類和對(duì)象:

  • 任意一個(gè)撒遣,都能夠知道這個(gè)類的所有屬性和方法邮偎;
  • 任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性义黎;

要想解剖一個(gè)類禾进,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是 Class 類中的方法廉涕。所以先要獲取到每一個(gè)字節(jié)碼文件 *.class 對(duì)應(yīng)的 Class 類型的對(duì)象泻云。在 《Java基礎(chǔ)之—反射(非常重要)》 一文中繪制了反射加載的過(guò)程:

反射加載流程

用好反射,關(guān)鍵在于能夠調(diào)用任意類或?qū)ο蟮姆椒ê蛯傩院伞N覀兛梢酝ㄟ^(guò) java.lang.reflect.Method 調(diào)用任意的方法宠纯,通過(guò) java.lang.reflect.Field 調(diào)用任意的屬性。

(1) java.lang.reflect.Method 方法的反射

  • 獲取一個(gè)方法對(duì)象
    • 方法名稱
    • 參數(shù)列表(的類型的類類型)
  • 如何用方法的反射操作方法层释?
    • method.invoke(target, 參數(shù)列表)

方法本身也可以用對(duì)象的形式表現(xiàn)出來(lái)婆瓜,它被封裝在 java.lang.reflect.Method 中。調(diào)用一個(gè)方法對(duì)象,關(guān)鍵在于兩部分:

  • 方法簽名
  • 參數(shù)列表的類型的類類型

舉例說(shuō)明:假設(shè)已經(jīng)定義了方法 methodFunction(int a, double b, boolean c),對(duì)于這個(gè)方法进统,methodFunction 是它的方法簽名,(int.class, double.class, boolean.class) 是它的參數(shù)列表的類型的類類型猴蹂。

簡(jiǎn)要介紹幾種不同的獲取成員方法的方式:

  • 批量獲取成員方法:
    • public Method[] getMethods(): 獲取所有"公有方法";該方法包含了父類的方法楣嘁,也包含 Object 類磅轻;
    • public Method[] getDeclaredMethods(): 獲取所有的成員方法珍逸,包括私有的,但不包括繼承的聋溜;
  • 獲取單個(gè)成員方法:
    • public Method getMethod(String name,Class<?>... parameterTypes):
      • 參數(shù):
        • String name: 方法名谆膳;
        • Class<?>...parameterTypes: 形參的Class類型對(duì)象
    • public Method getDeclaredMethod(String name,Class<?>... parameterTypes):

方法的調(diào)用是通過(guò) Method 類的 invoke 方法實(shí)現(xiàn)的。它的定義為:

public Object invoke(Object obj,Object... args) {...}

參數(shù)說(shuō)明:

  • Object obj: 要調(diào)用方法的對(duì)象勤婚;
  • Object... args: 調(diào)用方式時(shí)所傳遞的實(shí)參摹量;

關(guān)于方法的反射,例程如下:

:寫三個(gè)參數(shù)列表不同的 f 方法馒胆,獲得其方法的反射:

package reflect;

import java.lang.reflect.Method;

class A{
    public void f(){
        System.out.println("helloworld");
    }
    public int f(int a ,int b){
        return a+b;
    }
    public String f(String a,String b,int c){
        return a+","+b+","+c;
    }
}

public class Demo3 {
    public static void main(String[] args) {
        A a1 = new A();
        Class c = a1.getClass();
        try {
            // 獲取名為 f 的方法,參數(shù)列表的類型分別為 (int, int)
            Method method1 = c.getMethod("f",int.class,int.class);
            System.out.println(a1.f(10, 10));
            // 傳入?yún)?shù) (10, 10)凝果,輸出 f(int, int) 的計(jì)算結(jié)果
            int n = (Integer)method1.invoke(a1, 10,10);
            System.out.println(n);
            
            // 獲取無(wú)參數(shù)的 f 方法
            System.out.println("================");
            Method method2 = c.getMethod("f");
            a1.f();
            method2.invoke(a1);
            
            // 獲取參數(shù)列表為 (String, String, int) 類型的 f 方法
            System.out.println("================");
            Method method3 = c.getMethod("f", String.class,String.class,int.class);
            System.out.println(a1.f("hello", "world",100));
            String ss = (String)method3.invoke(a1, "hello","world",100);
            System.out.println(ss);     
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

該例程中首先定義了一個(gè)類 A祝迂,其中定義了幾個(gè)名稱為 f 的方法,都有不同的參數(shù)列表和返回值器净。在 getMethod 方法中傳入了不同的參數(shù)列表型雳,就可以準(zhǔn)確的獲取我們想要的具體的 f 方法。

(2) java.lang.reflect.Field 成員變量的反射

Class 類中重要的方法:

  • Field[] getDeclaredFields(): 返回 Field 對(duì)象的一個(gè)數(shù)組山害,這些對(duì)象反映此 Class 對(duì)象所表示的類或接口所聲明的所有字段纠俭。
  • Field getDeclaredField(String name): 返回一個(gè) Field 對(duì)象,該對(duì)象反映此 Class 對(duì)象所表示的類或接口的指定已聲明字段浪慌。

Field 類中重要的方法:

  • Class<?> getType(): 返回一個(gè) Class 對(duì)象冤荆,它標(biāo)識(shí)了此 Field 對(duì)象所表示字段的聲明類型。
  • void setAccessible(boolean flag): 將此對(duì)象的 accessible 標(biāo)志設(shè)置為指示的布爾值权纤。

下面用例程說(shuō)明:

  • 獲取一個(gè)成員變量對(duì)象
  • 如何進(jìn)行成員變量的反射操作
    • field.get(target);
    • field.set(target, newValue);


現(xiàn)在有類 User.java 如下:

package reflect;

public class User {
    private String name;
    private int age;
    public User() {}
    public User(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}

寫一個(gè)方法钓简,接受一個(gè)對(duì)象:

  • 如果該對(duì)象有字符串屬性,把其值改成大寫汹想;
  • 如果該對(duì)象有 int 屬性外邓,把值都加 100;
package reflect;

import java.lang.reflect.Field;

public class Demo6 {
    public static void main(String[] args) {
        User user = new User("zhangsan", 20);
        try {
            changeValue(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(user);
    }
    public static void changeValue(Object obj) throws Exception{
        Class c = obj.getClass();
        // 獲取成員變量數(shù)組
        Field[] fs = c.getDeclaredFields();
        for (Field field : fs) {
            // 當(dāng)前類型為 String
            if(field.getType() == String.class){
                // java 中古掏,在類的外面獲取此類的私有成員變量的值時(shí)损话,需要先用 setAccessible(true) 對(duì)變量進(jìn)行權(quán)限更改設(shè)定;
                field.setAccessible(true);
                // 獲取 String field 的值槽唾,并將其轉(zhuǎn)為大寫丧枪;
                String oldValue = (String)field.get(obj);
                field.set(obj, oldValue.toUpperCase()); 
            }
            // 當(dāng)前類型為 int
            if(field.getType()==int.class){
                field.setAccessible(true);
                // 獲取 int 類型 field 的值,并將其加 100
                int oldValue = field.getInt(obj);
                field.set(obj, oldValue+100);
            }
        }
    }
}

(3) java.lang.reflect.Constructor 構(gòu)造函數(shù)的反射

  • constructor.getConstructor:獲取某個(gè)構(gòu)造函數(shù)
  • constructor.newInstance(參數(shù)):構(gòu)建函數(shù)傳入?yún)?shù)的反射操作

Class 類關(guān)于構(gòu)造函數(shù)的重要方法:

  • Constructor<?> getConstructor(Class<?>... parameterTypes): 返回一個(gè) Constructor 對(duì)象夏漱,它反映此 Class 對(duì)象所表示的類的指定公共構(gòu)造方法豪诲;
  • Constuctor<?>[] getDeclaredConstructors(): 返回 Constructor 對(duì)象的一個(gè)數(shù)組,這些對(duì)象反映此 Class 對(duì)象表示的類聲明的所有構(gòu)造方法挂绰;
  • T newInstance(): 創(chuàng)建此 Class 對(duì)象所表示的類的一個(gè)新實(shí)例屎篱。

下面依舊用上面的中的 User.java 舉例說(shuō)明:

package reflect;

import java.lang.reflect.Constructor;

public class Demo7 {
    public static void main(String[] args) {
        Class c = User.class;
        try {
            // 獲取 User 類的無(wú)參構(gòu)造函數(shù)
            Constructor<User> cons = c.getConstructor();
            // 構(gòu)造無(wú)參的 User 實(shí)例對(duì)象
            User u = cons.newInstance();
            System.out.println(u);
            
            // 獲取 User(String, int) 的 User 構(gòu)造函數(shù)
            Constructor<User> cons2 = c.getConstructor(String.class,int.class);
            // 構(gòu)造 User(String, int) 的實(shí)例對(duì)象
            User u2 = cons2.newInstance("lisi",20);
            System.out.println(u2);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 數(shù)組的反射

前面說(shuō)過(guò) Java 中萬(wàn)事萬(wàn)物都是對(duì)象服赎,其中也包含數(shù)組。數(shù)組也是實(shí)例對(duì)象交播,而且數(shù)組的類類型只和類型和維數(shù)相關(guān)重虑。數(shù)組在反射機(jī)制中的類是 java.lang.reflect.Array。

依舊有上例中的 User.java秦士,下面用例程說(shuō)明:

public class Demo8 {
    public static void main(String[] args) {
        int[] a = {1,2,3,4,5};
        int[] b = {5,6,7,8};
        int[][] c = {
                {1,2,3},
                {4,5,6,7}
        };
        String[][] str = {
                {"aaa","bbb"},
                {"ccc","ddd","eee"}
        };
        
        Class c1 = a.getClass();
        Class c2 = b.getClass();
        Class c3 = c.getClass();
        System.out.println(c1==c2); // true
        System.out.println(c1==int[].class); // true
        System.out.println(c1.getName()+","+c2.getName()); // [I,[I
        System.out.println(str.getClass().getName()); // [[Ljava.lang.String;
        System.out.println(str.getClass()==String[][].class); // true
    }
}

輸出結(jié)果為:

true
true
[I,[I
[[Ljava.lang.String;
true

當(dāng)然通過(guò)反射的方法來(lái)創(chuàng)建數(shù)組的用法是很少見的缺厉,其實(shí)也是多余的。為什么不直接通過(guò) new 來(lái)創(chuàng)建數(shù)組呢隧土?反射創(chuàng)建數(shù)組不僅速度沒有 new 快提针,而且寫的程序也不易讀,還不如 new 來(lái)的直接曹傀。事實(shí)上通過(guò)反射創(chuàng)建數(shù)組確實(shí)很少見辐脖。


練習(xí):
寫一個(gè)函數(shù),簽名與參數(shù)列表為:public String getSql(Object obj);

  • 如果傳遞的是 User 對(duì)象
    • User 有 (name, age, sex) 屬性
    • 寫一個(gè)數(shù)據(jù)庫(kù)操作的命令行:返回 insert into User(name, age, sex) values (?, ?, ?)
public class Demo11 {
    public static void main(String[] args) {
        String res = getSql(new User());
        System.out.println(res);
    }
    public static String getSql(Object obj) {
        StringBuffer s = new StringBuffer();
        s.append("insert into ");
        Class c = obj.getClass();
        // 不包含包名的類名稱
        String className = c.getSimpleName();
        s.append(className).append("(");
        
        //==============
        // 獲取類的成員變量皆愉,并用逗號(hào)與括號(hào)分隔存儲(chǔ)
        // 如:(name, age, sex)
        //==============
        Field[] fs = c.getDeclaredFields();
        // 類的所有成員變量用逗號(hào)','隔開
        for(int i = 0; i < fs.length; i++) {
            s = i == 0 ? s.append(fs[i].getName()) : s.append(", ").append(fs[i].getName());
        }
        // 接上相同個(gè)數(shù)的 '?' 列表
        s.append(") values ").append(getString(fs.length));
        return s.toString();
    }
    public static String getString(int length) {
        StringBuilder s = new StringBuilder();
        s.append("(");
        for(int i = 0; i < length; i++)
            s = i == 0 ? s.append("?") : s.append(", ?");
            return s.append(")").toString();
    }
}

輸出為:

insert into User(name, age, sex) values (?, ?, ?)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗜价,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子幕庐,更是在濱河造成了極大的恐慌久锥,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件异剥,死亡現(xiàn)場(chǎng)離奇詭異瑟由,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)届吁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門错妖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人疚沐,你說(shuō)我怎么就攤上這事暂氯。” “怎么了亮蛔?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵痴施,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我究流,道長(zhǎng)辣吃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任芬探,我火速辦了婚禮神得,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偷仿。我一直安慰自己哩簿,他們只是感情好宵蕉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著节榜,像睡著了一般羡玛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宗苍,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天稼稿,我揣著相機(jī)與錄音,去河邊找鬼讳窟。 笑死让歼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挪钓。 我是一名探鬼主播是越,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碌上!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起浦徊,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馏予,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盔性,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霞丧,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年冕香,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛹尝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悉尾,死狀恐怖突那,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情构眯,我是刑警寧澤愕难,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站惫霸,受9級(jí)特大地震影響猫缭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壹店,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一猜丹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硅卢,春花似錦射窒、人聲如沸藏杖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)制市。三九已至,卻和暖如春弊予,著一層夾襖步出監(jiān)牢的瞬間祥楣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工汉柒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留误褪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓碾褂,卻偏偏與公主長(zhǎng)得像兽间,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子正塌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容