反射的基石Class類
Java程序中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class。
對比提問:衆多的人用一個什麼類表示?衆多的Java類用一個什麼類表示?
人àPerson
Java類àClass
對比提問: Person類代表人,它的實例對象就是張三,李四這樣一個個具體的人,Class類代表Java類,它的各個實例對象又分別對應什麼呢?
對應各個類在內存中的字節碼,例如,Person類的字節碼,ArrayList類的字節碼,等等。
一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,不同的類的字節碼是不同的,所以它們在內存中的內容是不同的,這一個個的空間可分別用一個個的對象來表示,這些對象顯然具有相同的類型,這個類型是什麼呢?
如何得到各個字節碼對應的實例對象( Class類型)
類名.class,例如,System.class
對象.getClass(),例如,new Date().getClass()
Class.forName("類名"),例如,Class.forName("java.util.Date");可以寫變量在運行的時候在明確類名,然後加載。
九個預定義Class實例對象:
參看Class.isPrimitive方法的幫助
基本的 Java 類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也表示爲 Class 對象。 如void.class
數組類型的Class實例對象
class.isArray()
總之,只要是在源程序中出現的類型,都有各自的Class實例對象,例如,int[],void…
isPrimitive
public boolean isPrimitive()
判定指定的 Class 對象是否表示一個基本類型。
有九種預定義的 Class 對象,表示八個基本類型和 void。這些類對象由 Java 虛擬機創建,與其表示的基本類型同名,即 boolean、byte、char、short、int、long、float 和 double。
這些對象僅能通過下列聲明爲 public static final 的變量訪問,也是使此方法返回 true 的僅有的幾個 Class 對象。
返回:
當且僅當該類表示一個基本類型時,才返回 true
從以下版本開始:
JDK1.1
另請參見:
Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE
如int.class == Integer.TYPE 返回ture
反射
反射就是把Java類中的各種成分映射成相應的java類。例如,一個Java類用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示,就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等等。
一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象後,得到這些實例對象後有什麼用呢?怎麼用呢?這正是學習和應用反射的要點。
Constructor類代表某個類中的一個構造方法
得到某個類所有的構造方法:
例子:Constructor [] constructors=
Class.forName("java.lang.String").getConstructors();
得到某一個構造方法:
例子:Constructor constructor =
Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
獲得方法時要用到類型
創建實例對象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
調用獲得的方法時要用到上面獲得方法時要用到的相同類型的實例對象
newInstance(Object... initargs)
使用此 Constructor 對象表示的構造方法來創建該構造方法的聲明類的新實例,並用指定的初始化參數初始化該實例。
Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。
該方法內部的具體代碼是怎樣寫的呢?用到了緩存機制來保存默認構造方法的實例對象。
Field類
Field類代表某個類中的一個成員變量
演示用eclipse自動生成Java類的構造方法
問題:得到的Field對象是對應到類上面的成員變量,還是對應到對象上的成員變量?類只有一個,而該類的實例對象有多個,如果是與對象關聯,哪關聯的是哪個對象呢?所以字段fieldX 代表的是x的定義,而不是具體的x變量。
示例代碼:
ReflectPoint point = new ReflectPoint(1,7);
Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
System.out.println(y.get(point));
//私有不能訪問要先暴力訪問才能獲得數據Field x =
Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
Field x =
Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
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類
Method類代表某個類中的一個成員方法
得到類中的某一個方法:
例子: Method charAt =
Class.forName("java.lang.String").getMethod("charAt", int.class);
調用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果傳遞給Method對象的invoke()方法的第一個參數爲null,這有着什麼樣的意義呢?說明該Method對象對應的是一個靜態方法!
jdk1.4和jdk1.5的invoke方法的區別:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的語法,需要將一個數組作爲參數傳遞給invoke方法時,數組中的每個元素分別對應被調用方法中的一個參數,所以,調用charAt方法的代碼也可以用Jdk1.4改寫爲 charAt.invoke(“str”, new Object[]{1})形式。
用反射方式執行某個類中的main方法
目標:
寫一個程序,這個程序能夠根據用戶提供的類名,去執行該類中的main方法。用普通方式調完後,大家要明白爲什麼要用反射方式去調啊?因爲傳進去args[0]是字符串,不能用一般的類名調用。所以要用反射方式去調用。
問題:
啓動Java程序的main方法的參數是一個字符串數組,即public static void main(String[] args),通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac會到底按照哪種語法進行處理呢?jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進行處理,即把數組打散成爲若干個單獨的參數。所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”}),javac只把它當作jdk1.4的語法進行理解,而不把它當作jdk1.5的語法解釋,因此會出現參數類型不對的問題。
解決辦法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,編譯器會作特殊處理,編譯時不把參數當作數組看待,也就不會把數組打散成若干個參數了
數組的反射
具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。
代表數組的Class實例對象的getSuperClass()方法返回的父類都爲Object類對應的Class實力對象。(九個預定義Class實例對象沒有父類,因爲不屬於Object,因此不能建立對象。)
基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。
Arrays.asList()方法處理int[]和String[]時的差異。
思考題:怎麼得到數組中的元素類型?
非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。這是可以用Arrays.asList()變成集合,然後打印。
Array工具類用於完成對數組的反射操作。
private static void printObject(Object obj){
Class clazz=obj.getClass();
if(clazz.isArray()){
int len=Array.getLength(obj);
for(int i=0;i<len;i++){
System.out.println(Array.get(obj,i));
}
}else{
System.out.println(obj);
}
}
反射的作用à實現框架功能
應該先直接用ArrayList和HashSet,然後才引入從配置文件讀,這樣便於學員學習。
類裏面如果沒有定義hashCode方法和equal方法,建立對象後就會默認按新建地址給對象的的哈希值賦值,就是在hashset集合中,如果兩個對象equal相等,但是因爲新建的時候地址不一樣,哈希值不一定相等,所以也是不相等的。
如果在類中有hashCode方法和equal方法,對象加入hashset集合後,不要輕易改變對象中與哈希值有關的變量,否則會出現內存泄漏,就是當程序要去刪除這個集合中的這個對象的引用時,這個對象因爲哈希值已經改變,但是集合中這個對象的引用的哈希值並沒有改變,用已經改變的對象哈希值去尋找集合中未改變哈希值前對象的引用,因爲前後哈希值不同,所以用改變哈希值的對象的哈希值在集合中找對象的引用是找不到,所以就無法刪除這個集合對對象的引用,就會造成內存泄漏。
框架與框架要解決的核心問題
我做房子賣給用戶住,由用戶自己安裝門窗和空調,我做的房子就是框架,用戶需要使用我的框架,把門窗插入進我提供的框架中。框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。
框架要解決的核心問題
我在寫框架(房子)時,你這個用戶可能還在上小學,還不會寫程序呢?我寫的框架程序怎樣能調用到你以後寫的類(門窗)呢?
因爲在寫才程序時無法知道要被調用的類名,所以,在程序中無法直接new 某個類的實例對象了,而要用反射方式來做。
綜合案例
先直接用new 語句創建ArrayList和HashSet的實例對象,演示用eclipse自動生成 ReflectPoint類的equals和hashcode方法,比較兩個集合的運行結果差異。
然後改爲採用配置文件加反射的方式創建ArrayList和HashSet的實例對象,比較觀察運行結果差異。
引入了Eclipse對資源文件的管理方式的講解。
getRealPath;金山詞霸/內部
一定要記住用完整的路徑,但完整的路徑不是硬編碼,而是運算出來的。
InputStream ips=new FileInputStream("config.properties");
相對於bin從 bin開始找(相對目錄)
InputStream ips=ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
相對於本包從本包開始找(相對目錄)
InputStream ips=ReflectTest2.class.getResourceAsStream("resource/config.properties");
跳出包外,所以要從根bin目錄開始找(絕對目錄)
InputStream ips=ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resource/config.properties");
Properties props=new Properties();
props.load(ips);
ips.close();不關的話,會內存泄漏,因爲系統資源被佔用;
本對象是被java垃圾回收幹掉的,在被垃圾回收之前,首先要把
佔用的系統資源釋放,系統資源釋放垃圾回收做不了,必須要自己手工釋放。
String className=props.getProperty("className");
Collection collections
=(Collection)Class.forName(className).newInstance();
內省à瞭解JavaBean
JavaBean是一種特殊的Java類,主要用於傳遞數據信息,這種java類中的方法主要用於訪問私有的字段,且方法名符合某種命名規則。
javaBean中,這種JavaBean的實例對象通常稱之爲值對象(Value Object,簡稱VO)。這些信息在類中用私有字段來存儲,如果讀取或設置這些字段的值,則需要通過一些相應的方法來訪問,大家覺得這些方法的名稱叫什麼好呢?JavaBean的屬性是根據其中的setter和getter方法來確定的,而不是根據其中的成員變量。如果方法名爲setId,中文意思即爲設置id,至於你把它存到哪個變量上,用管嗎?如果方法名爲getId,中文意思即爲獲取id,至於你從哪個變量上取,用管嗎?去掉set前綴,剩餘部分就是屬性名,如果剩餘部分的第二個字母是小寫的,則把剩餘部分的首字母改成小的。
setId()的屬性名àid
isLast()的屬性名àlast
setCPU的屬性名是什麼?àCPU
getUPS的屬性名是什麼?àUPS
總之,一個類被當作javaBean使用時,JavaBean的屬性是根據方法名推斷出來的,它根本看不到java類內部的成員變量。
一個符合JavaBean特點的類可以當作普通類一樣進行使用,但把它當JavaBean用肯定需要帶來一些額外的好處,我們纔會去了解和應用JavaBean!好處如下:
在Java EE開發中,經常要使用到JavaBean。很多環境就要求按JavaBean方式進行操作,別人都這麼用和要求這麼做,那你就沒什麼挑選的餘地!
JDK中提供了對JavaBean進行操作的一些API,這套API就稱爲內省。如果要你自己去通過getX方法來訪問私有的x,怎麼做,有一定難度吧?用內省這套api操作JavaBean比用普通類的方式更方便。內省對應的英文單詞爲IntroSpector,它主要用於對JavaBean進行操作,JavaBean是一種特殊的Java類,其中的某些方法符合某種命名規則,如果一個Java類中的一些方法符合某種命名規則,則可以把它當作JavaBean來使用。
內省綜合案例
演示用eclipse自動生成 ReflectPoint類的setter和getter方法。
直接new一個PropertyDescriptor對象的方式來讓大家瞭解JavaBean API的價值,先用一段代碼讀取JavaBean的屬性,然後再用一段代碼設置JavaBean的屬性。
演示用eclipse將讀取屬性和設置屬性的流水帳代碼分別抽取成方法:
只要調用這個方法,並給這個方法傳遞了一個對象、屬性名和設置值,它就能完成屬性修改的功能。得到BeanInfo最好採用“obj.getClass()”方式,而不要採用“類名.class”方式,這樣程序更通用。採用遍歷BeanInfo的所有屬性方式來查找和設置某個RefectPoint對象的x屬性。在程序中把一個類當作JavaBean來看,就是調用IntroSpector.getBeanInfo方法, 得到的BeanInfo對象封裝了把這個類當作JavaBean看的結果信息。
Beanutils工具包
演示用eclipse如何加入jar包,先只是引入beanutils包,等程序運行出錯後再引入logging包。在前面內省例子的基礎上,用BeanUtils類先get原來設置好的屬性,再將其set爲一個新值。BeanUtils類get屬性時返回的結果爲字符串,set屬性時可以接受任意類型的對象,通常使用字符串。用PropertyUtils類先get原來設置好的屬性,再將其set爲一個新值。get屬性時返回的結果爲該屬性本來的類型,set屬性時只接受該屬性本來的類型。但是不管BeanUtils類還是PropertyUtils類只能操作對象(value)。對於基本類型會自動拆箱和裝箱。如果要改變屬性用BeanUtils類,不改變屬性用PropertyUtils類。演示去掉JavaBean(ReflectPoint)的public修飾符時,BeanUtils工具包訪問javabean屬性時出現的問題。
package cn.itcast.day1;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorTest {
public static void main(String[] args) throws Exception {
ReflectPoint pt1=new ReflectPoint(3,5);
String propertyName="x";
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
Object value=7;
setProperties(pt1, propertyName, value);
System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName());
BeanUtils.setProperty(pt1, "x", "9");
System.out.println(pt1.getX());
/*java7的新特性
Map map={name:"zxx",age:18};
BeanUtils.setProperty(map,"name","1=lhm");
*/
BeanUtils.setProperty(pt1,"birthday.time","111");
BeanUtils.getProperty(pt1,"birthday.time");
PropertyUtils.setProperty(pt1, "x", 9);
System.out.println(PropertyUtils.getProperty(pt1, "x").getClass());
}
private static void setProperties(Object pt1, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd2=new PropertyDescriptor(propertyName, pt1.getClass());
Method methodSetX=pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
private static Object getProperty(Object pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
/*PropertyDescriptor pd=
new PropertyDescriptor(propertyName, pt1.getClass());
Method methodGetX=pd.getReadMethod();
Object retVal=methodGetX.invoke(pt1);*/
BeanInfo beanInfo=Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
Object retVal=null;
for(PropertyDescriptor pd:pds){
if(pd.getName().equals(propertyName)){
Method methodGetX=pd.getReadMethod();
retVal=methodGetX.invoke(pt1);
break;
}
}
return retVal;
}
}