黑馬程式員-Java 反射

來源:互聯網
上載者:User

標籤:黑馬程式員   java反射   reflect   class   

——Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! ——

一、概述

Java 反射機制是在運行狀態中,對於程式中的任意一個類,通過反射機制都能夠知道這個類的所有屬性和方法,包括共有、包含、預設和私人。對於任意的一個對象,通過反射機制都可以去調用它的每一個方法,這種機制就稱為Java的反射機制。一般的操作都在java.lang.reflect包中,常用到的類有ConstructorFieldMethod三種。既然是對Java類的反射,當然也有個比不可少的類Class,這也是一個類,作為Java中類檔案的一個類,擷取類的類一般有三種方式:

String s = "abc";// 通過類直接擷取Class<?> c1 = String.class;// 通過變數的getClass()方法擷取Class<?> c2 = s.getClass();// 通過Class的靜態方法forName(String)根據類的全名擷取Class<?> c3 = Class.forName("java.lang.String");

那麼Java的反射機制有什麼用處呢?一般的程式開發時,我們都是對使用的類直接操作,如建立等,但是如果遇到不能直接操作時怎麼辦呢,這裡是反射的一個用處,如可是根據一個類名稱的字串來擷取類的執行個體,這也是一種用法,程式可以根據設定檔內的類名去建立不同的類。還有一些情況如,類中有一些私人方法,我們想要訪問或者修改,正常的做法是行不通的,通過反射則可以達到目的,這也成為暴力反射。

二、 Class

第一次見到這個類可能會有些疑惑,Java中的類有很多,用過的也有很多,但是第一次見到這個描述類的類,多少還是有點好奇的,這也更加驗證了一點,所有事物都可以被描述成對象,既然類這麼常見,那當然也不會例外。首先我們應該對類檔案有一些初步瞭解,我們在編寫代碼之後使用javac進行編譯,便可以看到目錄下由.java檔案產生的.class檔案,那麼這些.class檔案便是類檔案。執行這些檔案時,如java x.class,類檔案便會被載入到記憶體中,以便使用,類檔案中使用到的其他類也會隨之載入,通過下面的例子結果可以說明一個問題,便是類檔案在記憶體中是唯一的。

String a1 = "abc";System.out.println(a1.getClass() == String.class);System.out.println(a1.getClass() == Class.forName("java.lang.String"));// 執行結果為truetrue

對於基礎資料型別 (Elementary Data Type)boolean,char,byte,short,int,long,float,double共8個和一個void都有其對應的類,如int.class,注意int.classInteger.class不是同一個類。對於數組也有其對應的類,如int[].classint[][].class,類型相同,維數相同的數組類才是同一個類,即int[].class != int[][].class,其他類似,數組類的類名有一定的規則,如int[][].class.getName()返回的是[[I,其中[的個數表示維數,int對應的是I,對應關係如下:

類型名 對應的內容
boolean Z
byte B
char C
double D
float F
int I
long J
short S
class or interface Lclassname

數組類也是繼承自Object的,如可以用Object對象接收int[],如Object obj = new int[]{1, 2, 3};,但是注意一點Object[] obj = new int[]{1, 2, 3};這種寫法是不正確的,因為基礎資料型別 (Elementary Data Type)不能轉成Object對象。

Class的常用方法
  • getConstructor(Class<?>... parameterTypes)此方法用於擷取類的構造方法,其中的參數是可變參數,用於指定擷取的構造方法是那個,如擷取String類的String(StringBuilder stringBuilder)構造方法,可以用Constructor cons String.class.getConstructor(StringBuilder.class);方式來擷取。這個方法類似的另一種擷取多構造方法的是getConstructors(),可以擷取所有的構造方法,返回一個Constructor的數組。
  • newInstance()方法用於執行個體化一個對象,即用這個類建立一個對象。範例程式碼如下:
import java.lang.reflect.*;class Main {    public static void main(String[] args) throws Exception {        // 擷取String的一個構造方法        Constructor cons = String.class.getConstructor(StringBuilder.class);        System.out.println(cons);        // 建立一個空的字串        String s = String.class.newInstance();        System.out.println(s);    }}// 執行結果為public java.lang.String(java.lang.StringBuilder)[空串]
  • String getName()用於擷取類名(包含包名),如String.class.getName()的傳回值為"java.lang.String"
  • Package getPackage()用於返回包名。
  • Field getField(String name)擷取類的成員變數。
  • Field[] getFields()擷取類發所有可訪問的成員變數。
  • Field getDeclaredField(String name)根據名稱擷取類聲明的一個成員變數。這裡可以擷取private修飾的成員變數,同樣的還有 Field[] getDeclaredFields()用於擷取所有類聲明的成員變數。
  • Method getMethod(String name, Class<?>... parameterTypes)擷取方法,和擷取構造方法類似,同樣也有擷取所有方法和擷取聲明的方法等等。
三、 Constructor
  • Class<T> getDeclaringClass()返回 Class 對象,該對象表示聲明由此 Constructor 對象表示的構造方法的類。
  • int getModifiers()返回以整數形式返回此 Constructor 對象所表示構造方法的 Java 語言修飾符。
  • T newInstance(Object... initargs)使用此 Constructor 對象表示的構造方法來建立該構造方法的聲明類的新執行個體,並用指定的初始化參數初始化該執行個體。
import java.lang.reflect.*;class Main {    public static void main(String[] args) throws Exception {        // 擷取String的一個構造方法        Constructor cons = String.class.getConstructor(StringBuilder.class);        // 使用擷取的建構函式樣本化一個對象        String s = (String)cons.newInstance(new StringBuilder("abc"));        // 輸出這個對象        System.out.println(s);        // 擷取建構函式對應的類        Class<?> c = cons.getDeclaringClass();        // 輸出類名        System.out.println(c.getName());        // 構造方法的類型        System.out.println(Member.DECLARED == cons.getModifiers());    }}
四、 Field

描述一個類的成員,一般功能有擷取這個成員的值,設定這個成員的值,擷取這個成員的資料類型等,下面通過一個執行個體示範一下Field的相關操作,執行個體內容為將一個自訂對象中的成員(String類型)中出現的疊詞替換為[double],首先是擷取該自訂對象的類對象,然會擷取所有聲明的成員,然會擷取這些成員的值,最後再將替換過的值設定回去。

import java.lang.reflect.*;/** * 將一個自訂類中的成員變數(String類型)中出現的連續兩 * 個相同的字元替換成[double]。如aa變成[double]。 */class Main {    public static void main(String[] args) throws Exception {        Demo demo = new Demo();        // 擷取demo對象中的聲明的成員        Field[] fields = demo.getClass().getDeclaredFields();        for(Field field : fields) {            // 如果不是可直接存取的,則將其修改為可直接存取型            if(!field.isAccessible()) {                field.setAccessible(true);            }            // 擷取原始字串內容            String oldString = (String)field.get(demo);            // 將疊詞替換為[double]            String newString = oldString.replaceAll("(.)\\1", "[double]");            // 最後將新的字串設定回去            field.set(demo, newString);            // 輸出新的變數內容            System.out.println(field);        }        System.out.println(demo.toString());    }}class Demo {    // 修改後應為ac[double]de    public String a = "abccde";    // 修改後應為he[double]oawdcs    private String b = "helloawdcs";    // 修改後應為[double]sdn[double]asdnjasd[double]adas    protected String c = "aasdnjjasdnjasdbbadas";    public String toString() {        return a + "/" + b + "/" + c;    }}
五、 Method

描述一個類的方法,一般常用功能, Class<?> getReturnType() 返回一個 Class 對象,該對象描述了此 Method 對象所表示的方法的正式傳回型別。Object invoke(Object obj, Object... args)對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。 下面通過一個執行個體,內容是通過反射功能擷取一個String對象的第一個位置的字元,即調用charAt()方法。

import java.lang.reflect.*;class Main {    public static void main(String[] args) throws Exception {        String s = "abc";        // 擷取String的類對象        Class<?> c = String.class;        // 擷取String類的charAt方法        Method method = c.getMethod("charAt", int.class);        // 通過方法的反射擷取第1個位置的字元        char ch = (char)method.invoke(s, 1);        System.out.println(ch); // 結果為b    }}
六、數組的反射

使用反射傳遞數組參數時,需要注意其可能會被系統自動拆分為對應的可變參數類型,如main方法的String[]會變成String...,所以傳遞時一般有兩種解決方式,一是將其再次封裝成一個數組,如new Object[]{new String[]{"abc", "cba", "bac"}};二是將其強轉為父類(Object)new String[]{"abc", "cba", "bac"},兩種方式都可。

import java.lang.reflect.*;/** * 通過反射調用另一個類的main方法,傳遞去一個數組參數 */class Main {    public static void main(String[] args) throws Exception {        // 首先擷取對應類的Class對象        Class clazz = Demo.class;        // 擷取Demo的main方法        Method method = clazz.getMethod("main", String[].class);        // 定義待傳遞的參數        String[] strs = new String[]{"Hello", "Hi", "Bye"};        /*調用靜態方法時,不需要傳遞變數         *注意:因為JDK的版本,如果直接傳遞strs會被拆分成         *可變類型String...,不符合main的參數要求,所以         *這裡將其轉為一個整體,Object類型         */        method.invoke(null, (Object)strs);    }}class Demo {    public static void main(String[] args) {        // 將傳遞進來的參數輸出        for(String s : args) {            System.out.println("參數:" + s);        }    }}// 執行結果參數:Hello參數:Hi參數:Bye

前面已經說過數組也是一個類,如int[].class,這種類和其他類有一些區別,Java提供了一個用於運算元組類的類Array,Array 類提供了動態建立和訪問 Java 數組的方法。Array 允許在執行 get 或 set 操作期間進行擴充轉換,但如果發生收縮轉換,則拋出 IllegalArgumentException。範例程式碼如下:

import java.util.*;import java.lang.reflect.*;class Main {    public static void main(String[] args) throws Exception {        // 擷取String[]對應的類        Class<?> clazz = String[].class;        // 定義一個String數組並初始化        String[] strs = new String[]{"Hello", "Hi", "Bye"};        // 如果這個類是數組類        if(clazz.isArray()) {            // 擷取數組的長度            int len = Array.getLength(strs);            // 變數這個數組            for(int i=0; i<len; i++) {                // 反射擷取strs的第i位置的值                String s = (String)Array.get(strs, i);                // 輸出擷取的值                System.out.println(i+":"+s);                // 如果這個值為Hi,則將其改為Good                if(s.equals("Hi")) {                    Array.set(strs, i, "Good");                }            }            // 將整個數組輸出            System.out.println(Arrays.toString(strs));        }    }}// 執行結果為0:Hello1:Hi2:Bye[Hello, Good, Bye]
七、反射的應用

在實際開發中,反射的應用一般是用於開發架構,架構是一些功能的核心抽取,涵蓋了一個系統的整體,但是沒有完成細節的東西。
下面是一個架構的執行個體,程式可以根據使用者配置的檔案,執行中使用不同的類,在目前的目錄建立一個檔案名稱為config.properties在其內寫入className=java.util.ArrayList,這是程式中的集合使用的是ArrayList,最後的結果是集合大小為4,然後將className的值修改為java.util.HashSet,最後的結果則變為了3,如此一來,程式可以根據不同的配置使用不同的類,架構便是如此,在使用的類不確定,或者沒有現成的類可使用時,不防使用反射來實現程式功能。代碼如下:

import java.io.*;import java.util.*;import java.lang.reflect.*;class Main {    public static void main(String[] args) throws Exception {        // 配置        Properties properties = new Properties();        // 通過類載入器擷取輸入資料流        InputStream in = Main.class.getResourceAsStream("config.properties");        // 從流中載入配置        properties.load(in);        // 關閉流        in.close();        // 擷取類名        String className = properties.getProperty("className");        // 根據類名擷取類        Class clazz = Class.forName(className);        // 根據clazz類,建立一個集合對象        Collection collection = (Collection) clazz.newInstance();        // 向集合中添加一些字串        collection.add(new String("Hello"));        collection.add(new String("Hi"));        collection.add(new String("Bye"));        collection.add(new String("Bye"));        // 輸出集合的大小        System.out.println(collection.size());    }}

黑馬程式員-Java 反射

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.