黑馬程式設計師 java高新技術 反射
---------- android培訓、java培訓、期待與您交流! ----------
一、Class類
Class是Java程式中各個Java類的總稱;它是反射的基石,通過Class類來使用反射。
物件的建立和使用:
建立例項物件:不可用new Class()的方式,因為Class沒有這樣的構造方法。而是將位元組碼物件賦值給Class變數。如Class c1 =Person.class。
如Person類,它的位元組碼:首先要將Person的java檔案編譯為class檔案放於硬碟上,即為二進位制程式碼,再將這些程式碼載入到記憶體中,接著用它建立一個個物件。就是把類的位元組碼加 載進記憶體中,再用此位元組碼建立一個個物件。當有如Person、Math、Date等等的類,那麼這些位元組碼就是分別的一個Class物件。即Class c2 =Date.class;。
2、獲得類的位元組碼物件:如Class.forName(”java.lang.String”)即獲得String.class。得到這個位元組碼物件有兩種情況:
1)此類已經載入進記憶體:若要得到此類位元組碼,不需要再載入。
2)此類還未載入進記憶體:類載入器載入此類後,將位元組碼快取起來,forName()方法返回載入進來的位元組碼。
3、得到各位元組碼對應的例項物件(Class型別)的方式:
類名.class:如System.class,String.class等等
物件.class:如new Date().getClass()或者d.getClass()。(Date d = new Date())
Class.forName(“類名”):如Class.forName(”java.lang.String”)
當獲取類名的時候,是不知道此類的名稱的,forName(字串引數)方法中傳入字串型的變數作為對外訪問的入口,
即傳入什麼類名就獲得什麼類名,從而得知相應的類名。
注:forName()是靜態方法,是反射中使用的一種方式獲取位元組碼的例項物件。
每個類的位元組碼物件只有唯一的一個,如任何字串物件,對應唯一的String.clas位元組碼。
九個預定義的Class:
1)包括八種基本型別(byte、short、int、long、float、double、char、boolean)
2)Integer.TYPE是Integer類的一個常量,它代表此包裝型別包裝的基本型別的位元組碼,所以和int.class是相等的。
基本資料型別的位元組碼都可以用與之對應的包裝類中的TYPE常量表示
陣列型別的Class例項物件,可以用Class.isArray()方法判斷是否為陣列型別的。
總結:只要是在源程式中出現的型別都有各自的Class例項物件,如int[].class、void.class等。
一些方法
1、static Class forName(String className)返回與給定字串名的類或介面的相關聯的Class物件。
2、Class getClass() 返回的是Object執行時的類,即返回Class物件即位元組碼物件
3、Constructor getConstructor() 返回Constructor物件,它反映此Class物件所表示的類的指定公共構造方法。
4、Field getField(String name) 返回一個Field物件,它表示此Class物件所代表的類或介面的指定公共成員欄位。
5、Field[] getFields()返回包含某些Field物件的陣列,表示所代表類中的成員欄位。
6、Method getMethod(String name,Class… parameterTypes) 返回一個Method物件,它表示的是此Class物件所代表的類的指定公共成員方法。
7、Method[] getMehtods() 返回一個包含某些Method物件的陣列,是所代表的的類中的公共成員方法。
8、String getName()以String形式返回此Class物件所表示的實體名稱。
9、String getSuperclass()返回此Class所表示的類的超類的名稱
10、boolean isArray() 判定此Class物件是否表示一個數組
11、boolean isPrimitive()判斷指定的Class物件是否是一個基本型別。
12、T newInstance()建立此Class物件所表示的類的一個新例項。
public static void fuction()throws Exception{
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true
//判斷是否為基本型別:isPrimitive()
System.out.println(cls1.isPrimitive());//false
System.out.println(int.class == Integer.class);//false
//Integer.TYPE代表包裝類對應的基本資料型別的位元組碼
System.out.println(int.class == Integer.TYPE);//true
System.out.println(int[].class.isPrimitive());//false
//判斷是否為陣列型別的
System.out.println(int[].class.isArray());//true
}
二、反射
概述:把Java類中的各種成分對映成相應的Java類。
如Class中的每一個方法返回的都是一種類(型),即Method對所有方法抽取成了這個類Method,它的每一個物件(如變數methodObj1)代表了一個方法。
一個類中的組成成分:
成員變數、方法、建構函式、包等資訊,也用一個個java類來表示(如汽車是一個類,其中的發動機,變速箱等也是對應的一個個類)
表示Java類的Class類顯然要提供一系列的方法 來獲取其中的變數、方法、建構函式、修飾符、包等資訊,這些資訊就是用相應的類的例項物件來表示,
他們是Field、Method、Contructor、Package等。
一個類中的每個成員都可用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可得到這些例項物件。
反射中的各類
Constructor類
Constructor代表某個類的構造方法
獲取構造方法:
1)如何得到摸個類的所有構造方法:如得到String類的所有構造方法
Constructor[] cons = Class.forName(“java.lang.String”).getConstructors();
2)獲取某一個構造方法:
Constructor con =String.class.getConstructor(StringBuffer.class);
建立例項物件:
1)通常方式:String str = new String(new StringBuffer (”abc”));
2)反射方式:String str = (String)con.newInstance(new StringBuffer(“abc”));
呼叫獲得的方法時要用到上面相同型別的例項物件,即兩個StringBuffer()要對應相等。
NewInstance():構造出一個例項物件,每呼叫一次就構造一個物件。
注意:上面的兩個地方①②都要用到StringBuffer,這必須是一致的。
第①個是指定要帶StringBuffer引數型別的構造方法,即所需使用的是含StringBuffer型別的構造方法。
第②個是用這個構造方法建立物件,要傳入的引數型別是StringBuffer。
Class.newInstance():建立一個物件,不帶引數的構造方法。
//new String(new StringBuffer("abc"));
Constructor constructor1 =
String.class.getConstructor(StringBuffer.class);
String str2 =
(String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str2);
//Class.newInstrance建立不帶引數的構造方法
String str3 =
(String)Class.forName("java.lang.String").newInstance();
System.out.println("str3:"+str3);
Field類
Field類代表成員變數(欄位)的反射。
public class ReflectPoint {
private int x;
public int y;
public String toString(){
return str1+";" + str2 + ";" + str3;
}
}
public class FieldTest(){
ReflectPoint pt1 = new ReflectPoint(3,5);
//fieldX和fieldY並不是物件身上的變數,而是類上的
//要用它去取某個物件上的對應的值,傳入什麼物件,就取相應物件的值。
Field fieldY = pt1.getClass().getField("y");
System.out.println(fieldY.get(pt1));
//獲取私有的成員變數
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));
}
獲取成員變數:
如上例子所示:
1、獲取公有的成員變數:
getField(String name)和get(變數)
2、獲取私有的成員變數:暴力反射
getDeclared(String name)
setAccessible(boolean b),將b設為true即可
get(變數)
//替換字元
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field : fields){
//此處需要用==比較,因為是同一份位元組碼物件
if(field.getType() == String.class){
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b','a');
field.set(obj, newValue);
}
}
}
Method類
1、概述:Method類代表某個類中的一個成員方法。
呼叫某個物件身上的方法,要先得到方法,再針對某個物件呼叫。
2、專家模式:誰呼叫這個資料,就是誰在呼叫它的專家。
如人關門:
呼叫者:是門呼叫管的動作,物件是門,因為門知道如何執行關的動作,通過門軸之類的細節實現。
指揮者:是人在指揮門做關的動作,只是給門發出了關的訊號,讓門執行。
總結:變數使用方法,是方法本身知道如何實現執行的過程,也就是“方法物件”呼叫方法,才執行了方法的每個細節的。
3、獲取某個類中的某個方法:(如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物件對應的是一個靜態方法
4、用反射方式執行某個main方法:
首先要明確為何要用反射:在寫源程式時,並不知道使用者傳入的類名是什麼,但是雖然傳入的類名不知道,
而知道的是這個類中的方法有main這個方法,所以可以通過反射的方式,通過使用者傳入的類名(
可定義字串型變數作為傳入類名的入口,通過這個變數代表類名),內部通過傳入的類名獲取其main方法,然後執行相應的內容。
//Method類演示
private static void methodTest(String [] args) throws Exception {
String str1 = "abc";
//一般方法:
System.out.println(str1.charAt(1));
//反射方法 :
Method methodCharAt =
Class.forName("java.lang.String").getMethod("charAt",int.class);
System.out.println(methodCharAt.invoke(str1,1));
//用反射方式執行某個main方法
//一般方式:
Test.main(new String[]{"111","222","333"});
System.out.println("-------");
//反射方式:
String startingClassName = args[0];
Method methodMain =
Class.forName(startingClassName).getMethod("main",String[].class);
//方案一:強制轉換為超類Object,不用拆包
methodMain.invoke(null,(Object)new String[]{"111","222","333"});
//方案二:將陣列打包,編譯器拆包後就是一個String[]型別的整體
methodMain.invoke(null,new Object[]{new String[]{"111","222","333"}});
}
//定義一個測試類
class Test{
public static void main(String [] args){
for(String arg : args){
System.out.println(arg);
}
}
}
陣列的反射
1、陣列位元組碼的名字:有[和陣列對應型別的縮寫,如int[]陣列的名稱為:[I
2、基本資料型別的一維陣列不能轉換為Object陣列,如:
int[] a = new int[3];Object[] obj= a;這樣是不成立的。
3、如何得到某個陣列中的某個元素的型別:
例:int a = newint[3];Object[] obj= new Object[]{”ABC”,1};
無法得到某個陣列的具體型別,只能得到其中某個元素的型別,如
Obj[0].getClass().getName()得到的是java.lang.String
若通過b.getClass().getName(),結果是:[Ljava.lang.Object;
public static void arrayTest()throws Exception {
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
Integer[] ai = new Integer[3];
String[] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());
System.out.println((Object)a1.getClass() == (Object)a3.getClass());
System.out.println(a3[0].getClass() == a1.getClass());
System.out.println(a3[0].getClass().getSuperclass().getName());
System.out.println(a1.getClass().equals(a3.getClass()));
System.out.println(a1.getClass().equals(a4.getClass()));
System.out.println(a1.getClass().getName());
System.out.println("----:" + a1.getClass());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a2.getClass().getSuperclass().getName());
Object obj1 = a1;
Object obj2 = a2;
//int基本資料型別不是Object的
//Object[] obj3 = a1;
Object[] obj4 = a3;
Object[] obj5 = a4;
System.out.println(a1);
System.out.println(a4);
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
System.out.println("-------");
int[] a = new int[3];
Object[] obj = new Object[]{"abc",new Integer(1)};
System.out.println(a.getClass().getName());
System.out.println(obj.getClass().getName());
System.out.println(obj[0].getClass().getName());
}
HashSet和與hashCode的分析
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class ReflectTest2 {
public static void main(String [] args){
Collection cons = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
cons.add(pt1);
cons.add(pt2);
cons.add(pt3);
cons.add(pt1);
cons.remove(pt1);
System.out.println(cons.size());
}
}
public class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itcast";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
//System.out.println("demo...");//測試
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
public String toString(){
return str1+";" + str2 + ";" + str3;
}
}
覆寫hashCode()方法的意義:只有存入的是具有hashCode演算法的集合的,覆寫hashCode()方法才有價值。
1、雜湊演算法的由來:
若在一個集合中查詢是否含有某個物件,通常是一個個的去比較,找到後還要進行equals的比較,物件特別多時,效率很低,
通過雜湊演算法,將集合分為若干個區域,每個物件算出一個雜湊值,可將雜湊值分組(一般模32為一組),每組對應某個儲存區域,
依一個物件的雜湊碼即可確定此物件對應區域,從而減少每個物件的比較,只需在指定區域查詢即可,從而提高從集合中查詢元素的效率。
2、如果不存入是hashCode演算法的集合中,那麼則不用複寫此方法。
3、只有類的例項物件要被採用雜湊演算法進行存入和檢索時,這個類才需要按要求複寫hashCode()方法,
即使程式可能暫時不會用到當前類的hashCode()方法,但是為提供一個hashCode()方法也不會有什麼不好,
沒準以後什麼時候就會用到這個方法,所以通常要求hashCode()和equals()兩者一併被覆蓋。
4、提示:
1)若同類兩物件用equals()方法比較的結果相同時,他們的雜湊碼也必須是相等的,
但反過來就不成立了,如”BB”和”Aa”兩字串用equals()比較式不相等的,但是他們的雜湊值是相等的。
2)當一個物件被儲存進HashSet集合中,就不能再修改參與計算雜湊值的欄位,
否則物件被修改後的雜湊值與最初被存入的HashSet集合中的雜湊值就不同了。在這種情況下,
即使contains()方法是用物件的當前引用作為引數去HashSet集合中檢索物件,也將返回找不到物件的結果,
這也導致無法從HashSet集合中單獨刪除當前物件,從而造成記憶體洩露。
簡單說,之前存入的物件和修改後的物件,是具有不同的雜湊值,被認為是不同的兩個物件
這樣的物件不再使用,又不移除,而越來越多,就會導致記憶體洩露。
補充:
記憶體洩露:某些物件不再使用了,佔用著記憶體空間,並未被釋放,就會導致記憶體洩露;
也就是說當程式不斷增加物件,修改物件,刪除物件,日積月累,記憶體就會用光了,就導致記憶體溢位。
3)物件在呼叫方法時,物件會先進行一次自身hashCode()方法的呼叫,再進行操作方法。
反射作用
概述
1、框架:通過反射呼叫位置Java類的一種方式。
如房地產商造房子使用者住,門窗和空調等等內部都是由使用者自己安裝,房子就是框架,使用者需使用此框架,安好門窗等放入到房地產商提供的框架中。
框架和工具類的區別:工具類被使用者類呼叫,而框架是呼叫使用者提供的類。
2、框架機器要解決的核心問題:
我們在寫框架(造房子的過程)的時候,呼叫的類(安裝的門窗等)還未出現,那麼,框架無法知道要被呼叫的類名,
所以在程式中無法直接new其某個類的例項物件,而要用反射來做。
類載入器:
1、簡述:類載入器是將.class的檔案載入經記憶體,也可將普通檔案中的資訊載入進記憶體。
2、檔案的載入問題:
a、eclipse會將源程式中的所有.java檔案載入成.class檔案,以確保編譯,然後放到classPath指定的目錄中去。
並且會將非.java檔案原封不動的複製到.class指定的目錄中去。在真正編譯的時候,使用classPath目錄中的檔案,即放置.class檔案的目錄。
b、寫完程式是要講配置檔案放到.class檔案目錄中一同打包,這些都是類載入器載入的,資原始檔(配置檔案)也同樣載入了配置檔案。
c、框架中的配置檔案都要放到classPath指定的資料夾中,原因是它的內部就是用類載入器載入的檔案。
3、資原始檔的載入:是使用類載入器。
a、由類載入器ClassLoader的一個物件載入經記憶體,即用getClassLoader()方法載入。
若要載入普通檔案,可用getResourseAsStream(String name)在classPath的檔案中逐一查詢要載入的檔案。
b、在.class身上也提供了方法來載入資原始檔,其實它內部就是先呼叫了Loader方法,再載入的資原始檔。
如:Reflect.class.getResourseAsStream(String name)
4、配置檔案的路徑問題:
第一、用絕對路徑,通過getRealPath()方法運算出來具體的目錄,而不是內部編碼出來的。
一般先得到使用者自定義的總目錄,在加上自己內部的路徑。可以通過getRealPath()方法獲取檔案路徑。
對配置檔案修改是需要要儲存到配置檔案中,那麼就要得到它的絕對路徑才行,因此,配置檔案要放到程式的內部。
第二、name的路徑問題:
①如果配置檔案和classPath目錄沒關係,就必須寫上絕對路徑,
②如果配置檔案和classPath目錄有關係,即在classPath目錄中或在其子目錄中(一般是資原始檔夾resource)
那麼就得寫相對路徑,因為它自己瞭解自己屬於哪個包,是相對於當前包而言的。
示例:
配置檔案內容:
className=java.util.ArrayList
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
public static void main(String [] args)throws Exception{
//讀取系統檔案到讀取流中
//方式一:
//InputStream ips = new FileInputStream("config.propert");
/*getRealPath()--得到完整的路徑
* 一定要用完整的路徑,但完整的路徑不是硬編碼出來的,而是運算出來的。*/
//方式二:
//InputStream ips =
// ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/text1/config.propert");
//方式三:
//第一種:配置檔案(資原始檔)在當前包中
InputStream ips = ReflectTest2.class.getResourceAsStream("resourse/config.propert");
//第二種:配置檔案(資原始檔)不在當前包中,和此包沒太大關係
//InputStream ips =
// ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/test2/resourse/config.properties");
//載入檔案中的鍵值對
Properties props = new Properties();
props.load(ips);
//關閉資源,即ips呼叫的那個系統資源
//注意:關閉的是ips操作的流,載入進記憶體後,就不再需要流資源了,需要關閉
ips.close();
//定義變數,將檔案中的類名賦值給變數
String className = props.getProperty("className");
//通過變數,建立給定類的物件
Collection cons =
(Collection)Class.forName(className).newInstance();
//將元素新增到集合中
/*Collection cons = new HashSet();*/
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
cons.add(pt1);
cons.add(pt2);
cons.add(pt3);
cons.add(pt1);
//移除元素
cons.remove(pt1);
System.out.println(cons.size());
}
}