一 反射的應用場景
一、概述
反射技術:
Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類中的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
二、應用場景
一個已經可以使用的應用程序,因爲程序已經做好可以運行使用,不能再進行代碼的加入了。而當後期我們新的功能加入程序時,該怎麼做呢?就如我們的電腦一樣,後期我們可能會鼠標、鍵盤等,所以電腦給我們預留了usb接口,只要符合這個接口規則的設備,電腦就可以通過加載驅動等操作來使用。
那這個程序能用了,如何使用後期出現的功能類呢?
常用的作法,會提供一個配置文件,來供以後實現此程序的類來擴展功能。對外提供配置文件,讓後期出現的子類直接將類名字配置到配置文件中即可。該應用程序直接讀取配置文件中的內容。並查找和給定名稱相同的類文件。進行如下操作:
1)加載這個類。
2)創建該類的對象。
3)調用該類中的內容。
應用程序使用的類不確定時,可以通過提供配置文件,讓使用者將具體的子類存儲到配置文件中。然後該程序通過反射技術,對指定的類進行內容的獲取。
好處:反射技術大大提高了程序的擴展性。
二 反射涉及的對象
一、概述
反射就是把Java類中的各種成分映射成相應的java類。
例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等等。
二、反射的基石——Class類
1、所有的類文件都有共同屬性,所以可以向上抽取,把這些共性內容封裝成一個類,這個類就叫Class(描述字節碼文件的對象)。
Class類中就包含屬性有field(字段)、method(方法)、construction(構造函數)。
而field中有修飾符、類型、變量名等複雜的描述內容,因此也可以將字段封裝稱爲一個對象。用來獲取類中field的內容,這個對象的描述叫Field。同理方法和構造函數也被封裝成對象Method、Constructor。要想對一個類進行內容的獲取,必須要先獲取該字節碼文件的對象。該對象是Class類型。
Class類描述的信息:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表,方法名稱的列表等。每一個字節碼就是class的實例對象。
2、Class和class的區別
1)class:Java中的類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性,至於這個屬性的值是什麼,則由此類的實例對象確定,不同的實例對象有不同的屬性值。
2)Class:指的是Java程序中的各個Java類是屬於同一類事物,都是Java程序的類,這些類稱爲Class。例如人對應的是Person類,Java類對應的就是Class。Class是Java程序中各個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)包括八種基本類型(byte、short、int、long、float、double、char、boolean)的字節碼對象和一種返回值爲void類型的void.class。
2)Integer.TYPE是Integer類的一個常量,它代表此包裝類型包裝的基本類型的字節碼,所以和int.class是相等的。基本數據類型的字節碼都可以用與之對應的包裝類中的TYPE常量表示
2、只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,可以用Class.isArray()方法判斷是否爲數組類型的。
4、Class類中的方法
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方法了。既然要通過指定的構造函數進行對象的初始化。就必須先獲取這個構造函數——Constructor。Constructor代表某個類的構造方法。
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方法中的參數列表一致。
2、newInstance():構造出一個實例對象,每調用一次就構造一個對象。
3、利用Constructor類來創建類實例的好處是可以指定構造函數,而Class類只能利用無參構造函數創建類實例對象。
四、Field類
1、Field類代表某個類中一個成員變量
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
2、Object[]與String[]沒有父子關係,Object與String有父子關係,所以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。
4、Array工具類用於完成對數組的反射操作。
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、文件的加載問題:
1)eclipse會將源程序中的所有.java文件編譯成.class文件,然後放到classPath指定的目錄中去。並且會將非.java文件原封不動的複製到.class指定的目錄中去。在運行的時候,執行的是.class文件。
2)將配置文件放到.class文件目錄中一同打包,類加載器就會一同加載。
3、資源文件的加載:是使用類加載器。
1)由類加載器ClassLoader來加載進內存,即用getClassLoader()方法獲取類加載器,然後用類加載器的getResourceAsStream(String name)方法,將配置文件(資源文件)加載進內存。利用類加載器來加載配置文件,需把配置文件放置的包名一起寫上。這種方式只有讀取功能。
2)Class類也提供getResourceAsStream方法來加載資源文件,其實它內部就是調用了ClassLoader的方法。這時,配置文件是相對類文件的當前目錄的,也就是說用這種方法,配置文件前面可以省略包名。
如:類名.class.getResourceAsStream(“資源文件名”)
4、配置文件的路徑問題:
1)用絕對路徑,通過getRealPath()方法運算出來具體的目錄,而不是內部編碼出來的。
一般先得到用戶自定義的總目錄,在加上自己內部的路徑。可以通過getRealPath()方法獲取文件路徑。對配置文件修改是需要要儲存到配置文件中,那麼就要得到它的絕對路徑才行,因此,配置文件要放到程序的內部。
2)name的路徑問題:
①如果配置文件和classPath目錄沒關係,就必須寫上絕對路徑,
②如果配置文件和classPath目錄有關係,即在classPath目錄中或在其子目錄中(一般是資源文件夾resource),那麼就得寫相對路徑,因爲它自己瞭解自己屬於哪個包,是相對於當前包而言的。