java基礎——反射

    反射的應用場景

一、概述

反射技術:

        Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類中的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。

二、應用場景

        一個已經可以使用的應用程序,因爲程序已經做好可以運行使用,不能再進行代碼的加入了。而當後期我們新的功能加入程序時,該怎麼做呢?就如我們的電腦一樣,後期我們可能會鼠標、鍵盤等,所以電腦給我們預留了usb接口,只要符合這個接口規則的設備,電腦就可以通過加載驅動等操作來使用。

        那這個程序能用了,如何使用後期出現的功能類呢?

        常用的作法,會提供一個配置文件,來供以後實現此程序的類來擴展功能。對外提供配置文件,讓後期出現的子類直接將類名字配置到配置文件中即可。該應用程序直接讀取配置文件中的內容。並查找和給定名稱相同的類文件。進行如下操作:

        1)加載這個類。

        2)創建該類的對象。

        3)調用該類中的內容。

       應用程序使用的類不確定時,可以通過提供配置文件,讓使用者將具體的子類存儲到配置文件中。然後該程序通過反射技術,對指定的類進行內容的獲取。

        好處:反射技術大大提高了程序的擴展性。

 

二     反射涉及的對象

一、概述

        反射就是把Java類中的各種成分映射成相應的java類。

        例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是FieldMethodContructorPackage等等。

二、反射的基石——Class

1、所有的類文件都有共同屬性,所以可以向上抽取,把這些共性內容封裝成一個類,這個類就叫Class(描述字節碼文件的對象)。

         Class類中就包含屬性有field(字段)、method(方法)、construction(構造函數)。

        而field中有修飾符、類型、變量名等複雜的描述內容,因此也可以將字段封裝稱爲一個對象。用來獲取類中field的內容,這個對象的描述叫Field。同理方法和構造函數也被封裝成對象MethodConstructor。要想對一個類進行內容的獲取,必須要先獲取該字節碼文件的對象。該對象是Class類型。

        Class類描述的信息:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表,方法名稱的列表等。每一個字節碼就是class的實例對象。

2Classclass的區別

        1classJava中的類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性,至於這個屬性的值是什麼,則由此類的實例對象確定,不同的實例對象有不同的屬性值。

        2Class:指的是Java程序中的各個Java類是屬於同一類事物,都是Java程序的類,這些類稱爲Class。例如人對應的是Person類,Java類對應的就是ClassClassJava程序中各個Java類的總稱;它是反射的基石,通過Class類來使用反射。

3、獲取Class對象的三種方式

        加載XX.class文件進內存時就被封裝成了對象,該對象就是字節碼文件對象。如何獲取Class對象呢?

方式一:

        通過對象的getClass方法進行獲取。

        如:Class clazz=new Person().getClass();//Person是一個類名

        麻煩之處:每次都需要具體的類和該類的對象,以及調用getClass方法。

方式二:

        任何數據類型都具備着一個靜態的屬性class,這個屬性直接獲取到該類型的對應Class對象。

        如:Class clazz=Person.class;//Person是一個類名

        比第一種較爲簡單,不用創建對象,不用調用getClass方法,但是還是要使用具體的類,和該類中的一個靜態屬性class完成。

方式三:

        這種方式較爲簡單,只要知道類的名稱即可。不需要使用該類,也不需要去調用具體的屬性和行爲。就可以獲取到Class對象了。

        如:Class clazz=Class.forName("包名.Person");//Person是一個類名

        這種方式僅知道類名就可以獲取到該類字節碼對象的方式,更有利於擴展。

注:

        1、九個預定義的Class

                1)包括八種基本類型(byteshortintlongfloatdoublecharboolean)的字節碼對象和一種返回值爲void類型的void.class

                2Integer.TYPEInteger類的一個常量,它代表此包裝類型包裝的基本類型的字節碼,所以和int.class是相等的。基本數據類型的字節碼都可以用與之對應的包裝類中的TYPE常量表示

        2、只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,可以用Class.isArray()方法判斷是否爲數組類型的。

4Class類中的方法

        static Class forName(String className)

        返回與給定字符串名的類或接口的相關聯的Class對象。

        Class getClass()

        返回的是Object運行時的類,即返回Class對象即字節碼對象

        Constructor getConstructor()

        返回Constructor對象,它反映此Class對象所表示的類的指定公共構造方法。

        Field getField(String name)

        返回一個Field對象,它表示此Class對象所代表的類或接口的指定公共成員字段。

        Field[] getFields()

        返回包含某些Field對象的數組,表示所代表類中的成員字段。

        Method getMethod(String name,Class parameterTypes)

        返回一個Method對象,它表示的是此Class對象所代表的類的指定公共成員方法。

        Method[] getMehtods()

        返回一個包含某些Method對象的數組,是所代表的的類中的公共成員方法。

        String getName()

        以String形式返回此Class對象所表示的實體名稱。

        String getSuperclass()

        返回此Class所表示的類的超類的名稱

        boolean isArray()

        判定此Class對象是否表示一個數組

        boolean isPrimitive()

        判斷指定的Class對象是否是一個基本類型。

        T newInstance()

        創建此Class對象所表示的類的一個新實例。

5、通過Class對象獲取類實例

        通過查看API我們知道,Class類是沒有構造方法的, 因此只能通過方法獲取類實例對象。之前我們用的已知類,創建對象的做法:

        1)查找並加載XX.class文件進內存,並將該文件封裝成Class對象。

        2)再依據Class對象創建該類具體的實例。

        3)調用構造函數對對象進行初始化。

             如:Person p=new Person();

 現在用Class對象來獲取類實例對象的做法:

        1)查找並加載指定名字的字節碼文件進內存,並被封裝成Class對象。

        2)通過Class對象的newInstance方法創建該Class對應的類實例。

        3)調用newInstance()方法會去使用該類的空參數構造函數進行初始化。

三、Constructor

1、概述

        如果指定的類中沒有空參數的構造函數,或者要創建的類對象需要通過指定的構造函數進行初始化。這時怎麼辦呢?這時就不能使用Class類中的newInstance方法了。既然要通過指定的構造函數進行對象的初始化。就必須先獲取這個構造函數——ConstructorConstructor代表某個類的構造方法。

2、獲取構造方法:

        1)得到這個類的所有構造方法:如得到上面示例中Person類的所有構造方法

              Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();

        2)獲取某一個構造方法:

              Constructor con=Person.class.getConstructor(String.class,int.class);

3、創建實例對象:

        1)通常方式:Person p = new Person(“lisi”,30);

         2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);

注:

        1、創建實例時newInstance方法中的參數列表必須與獲取Constructor的方法getConstructor方法中的參數列表一致。

         2newInstance():構造出一個實例對象,每調用一次就構造一個對象。

         3、利用Constructor類來創建類實例的好處是可以指定構造函數,而Class類只能利用無參構造函數創建類實例對象。

四、Field

1Field類代表某個類中一個成員變量

2、方法

       Field getField(String s);//只能獲取公有和父類中公有

        Field getDeclaredField(String s);//獲取該類中任意成員變量,包括私有

        setAccessible(ture);

        //如果是私有字段,要先將該私有字段進行取消權限檢查的能力。也稱暴力訪問。

        set(Object obj, Object value);//將指定對象變量上此Field對象表示的字段設置爲指定的新值。

        Object get(Object obj);//返回指定對象上Field表示的字段的值。

五、Method

1、概述:Method類代表某個類中的一個成員方法。調用某個對象身上的方法,要先得到方法,再針對某個對象調用。

2、專家模式:誰調用這個數據,就是誰在調用它的專家。

3、方法

        Method[] getMethods();//只獲取公共和父類中的方法。

        Method[] getDeclaredMethods();//獲取本類中包含私有。

        Method   getMethod("方法名",參數.class(如果是空參可以寫null);

        Object invoke(Object obj ,參數);//調用方法

        如果方法是靜態,invoke方法中的對象參數可以爲null

如:

獲取某個類中的某個方法:(如String str =abc”)

        1)通常方式:str.charAt(1)

        2)反射方式:

                                  Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

                                  charAtMethod.invoke(str,1);

說明:如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明Method對象對應的是一個靜態方法

六、數組的反射

1、具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。數組字節碼的名字:有[和數組對應類型的縮寫,如int[]數組的名稱爲:[I

2Object[]String[]沒有父子關係,ObjectString有父子關係,所以new Object[]{aaa,bb}不能強制轉換成new String[]{aaa,bb}; Object x =abc”能強制轉換成String x =abc”。

3、如何得到某個數組中的某個元素的類型,

        例:

              int a = new int[3]Object[] obj=new Object[]{ABC,1};

        無法得到某個數組的具體類型,只能得到其中某個元素的類型,

        如:

               Obj[0].getClass().getName()得到的是java.lang.String。

4Array工具類用於完成對數組的反射操作。

        Array.getLength(Object obj);//獲取數組的長度

        Array.get(Object obj,int x);//獲取數組中的元素

5、基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。

七、HashCode的分析

        覆寫hashCode()方法的意義:只有存入的是具有hashCode算法的集合的,覆寫hashCode()方法才有價值。

1、哈希算法的由來:

        若在一個集合中查找是否含有某個對象,通常是一個個的去比較,找到後還要進行equals的比較,對象特別多時,效率很低。有這麼一種HashCode算法,有一個集合,把這個集合分成若干個區域,每個存進來的對象,可以算出一個hashCode值,根據算出來的值,就放到相應的區域中去。當要查找某一個對象,只要算出這個對象的hashCode值,看屬於第幾個區域,然後到相應的區域中去尋找,看是否有與此對象相等的對象。這樣查找的性能就提高了。

2、要想HashCode方法有價值的話,前提是對象存入的是hash算法這種類型的集合當中才有價值。如果不存入是hashCode算法的集合中,則不用複寫此方法。

3、如果沒有複寫hashCode方法,對象的hashCode值是按照內存地址進行計算的。這樣即使兩個對象的內容是想等的,但是存入集合中的內存地址值不同,導致hashCode值也不同,被存入的區域也不同。所以兩個內容相等的對象,就可以存入集合中。

4、當一個對象存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算哈希值的字段了,否則對象修改後的哈希值與最初存儲進HashSet集合中的哈希值就不同了。在這種情況下,調用contains方法或者remove方法來尋找或者刪除這個對象的引用,就會找不到這個對象。從而導致無法從HashSet集合中單獨刪除當前對象,從而造成內存泄露


     反射的作用——>實現框架的功能

一、概述

1、框架:通過反射調用Java類的一種方式。

        框架和工具類的區別:工具類被用戶類調用,而框架是調用用戶提供的類。

2、框架機器要解決的核心問題:

        我們在寫框架的時候,調用的類還未出現,那麼,框架無法知道要被調用的類名,所以在程序中無法直接new其某個類的實例對象,而要用反射來做。

3、簡單框架程序的步驟:

        1)右擊項目File命名一個配置文件如:config.properties,然後寫入配置信息。如鍵值對:className=java.util.ArrayList,等號右邊的配置鍵,右邊是值。

        2)代碼實現,加載此文件:

                ①將文件讀取到讀取流中,要寫出配置文件的絕對路徑。

                    如:InputStream is=new FileInputStream(“配置文件”);

                ②用Properties類的load()方法將流中的數據存入集合。

                ③關閉流:關閉的是讀取流,因爲流中的數據已經加載進內存。

        3)通過getProperty()方法獲取className,即配置的值,也就是某個類名。

        4)用反射的方式,創建對象newInstance()

        5)執行程序主體功能

二、類加載器

1、簡述:類加載器是將.class的文件加載進內存,也可將普通文件中的信息加載進內存。

2、文件的加載問題:

        1eclipse會將源程序中的所有.java文件編譯成.class文件,然後放到classPath指定的目錄中去。並且會將非.java文件原封不動的複製到.class指定的目錄中去。在運行的時候,執行的是.class文件。

        2)將配置文件放到.class文件目錄中一同打包,類加載器就會一同加載。

3、資源文件的加載:是使用類加載器。

        1)由類加載器ClassLoader來加載進內存,即用getClassLoader()方法獲取類加載器,然後用類加載器的getResourceAsStream(String name)方法,將配置文件(資源文件)加載進內存。利用類加載器來加載配置文件,需把配置文件放置的包名一起寫上。這種方式只有讀取功能。

       2Class類也提供getResourceAsStream方法來加載資源文件,其實它內部就是調用了ClassLoader的方法。這時,配置文件是相對類文件的當前目錄的,也就是說用這種方法,配置文件前面可以省略包名。

       如:類名.class.getResourceAsStream(“資源文件名”)

4、配置文件的路徑問題:

        1)用絕對路徑,通過getRealPath()方法運算出來具體的目錄,而不是內部編碼出來的。

        一般先得到用戶自定義的總目錄,在加上自己內部的路徑。可以通過getRealPath()方法獲取文件路徑。對配置文件修改是需要要儲存到配置文件中,那麼就要得到它的絕對路徑才行,因此,配置文件要放到程序的內部。

        2name的路徑問題:

                ①如果配置文件和classPath目錄沒關係,就必須寫上絕對路徑,

                ②如果配置文件和classPath目錄有關係,即在classPath目錄中或在其子目錄中(一般是資源文件夾resource),那麼就得寫相對路徑,因爲它自己瞭解自己屬於哪個包,是相對於當前包而言的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章