1. 程式人生 > >Java深入(高新技術)(四):反射、Class、Constructor、Method、Field、陣列的反射、JavaBean

Java深入(高新技術)(四):反射、Class、Constructor、Method、Field、陣列的反射、JavaBean

反射的基石àClass

對比提問:Person類代表人,它的例項物件就是張三,李四這樣一個個具體的人,Java程式中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class。對比提問:眾多的人用一個什麼類表示?眾多的Java類用一個什麼類表示?

Ø  àPerson

Ø  JavaàClass

Class類代表Java類,它的各個例項物件又分別對應什麼呢?

Ø  對應各個類在記憶體中的位元組碼,例如,Person類的位元組碼,ArrayList類的位元組碼,等等。

Ø  一個類被類載入器載入到記憶體中,佔用一片儲存空間,這個空間裡面的內容就是類的位元組碼,不同的類的位元組碼是不同的,所以它們在記憶體中的內容是不同的,這一個個的空間可分別用一個個的物件來表示,這些物件顯然具有相同的型別,這個型別是什麼呢?

如何得到各個位元組碼對應的例項物件(Class型別)

Ø  類名.class,例如,System.class

Ø  物件.getClass(),例如,newDate().getClass()

Ø  Class.forName("類名"),例如,Class.forName("java.util.Date");

九個預定義Class例項物件:

bytechar shory int float double long boolean void

Ø  參看Class.isPrimitive方法的幫助

Ø  Int.class == Integer.TYPE

陣列型別的Class例項物件

Ø  Class.isArray()

總之,只要是在源程式中出現的型別,都有各自的Class例項物件,例如,int[]void…

反射就是把Java類中的各種成分對映成相應的java類【馮偉立】。例如,一個Java類中用一個Class類的物件來表示,一個類中的組成部分:成員變數,方法,構造方法,包等等資訊也用一個個的Java類來表示,就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的例項物件來表示,它們是FieldMethodContructor

Package等等。然後這些類中又有相應的特性和方法。

Constructor

Constructor代表某個類中的一個構造方法

得到某個類所有的構造方法:

Ø 例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();

得到某一個構造方法:

Ø 例子:    Constructor constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

//獲得方法時要用到型別

建立例項物件:

Ø 通常方式:Stringstr = new String(new StringBuffer("abc"));

Ø 反射方式: Stringstr = (String)constructor.newInstance(new StringBuffer("abc"));

//呼叫獲得的方法時要用到上面相同型別的例項物件

Class.newInstance()方法:

Ø 例子:Stringobj = (String)Class.forName("java.lang.String").newInstance();

Ø 該方法內部先得到預設的構造方法,然後用該構造方法建立例項物件。

Ø 該方法內部的具體程式碼是怎樣寫的呢?用到了快取機制來儲存預設構造方法的例項物件。

1. 一個類有多個構造方法,用什麼方式可以區分清楚想得到其中的哪個方法呢?根據引數的個數和型別,例如,Class.getMethod(name,Class... args)中的args引數就代表所要獲取的那個方法的各個引數的型別的列表。重點:引數型別用什麼方式表示?用Class例項物件。例如:

int.class(int []).class

int [] ints = new int[0];

ints.getClass();

Constructor物件代表一個構造方法, Constructor物件上會有什麼方法呢?得到名字,得到所屬於的類,產生例項物件。

2.通常情況下是怎樣做的,String str = new String(new StringBuffer(“abc”));

    String str =(String)constructor.newInstance(/*"abc"*/newStringBuffer("abc"));

   System.out.println(str);

反射方式建立例項物件時,先用string作為引數傳進去,根據錯誤讓大家感受到確實是那個構造方法,然後再改為傳一個StringBuffer型別的引數進去,String str =(String)constructor.newInstance(/*"abc"*/newStringBuffer("abc"));

好比,我叫來一個吃人不吃草的恐龍,等到它要吃東西時,我得給他送真人去了吧。

 

Field

Field代表某個類中的一個成員變數

問題:得到的Field物件是對應到類上面的成員變數,還是對應到物件上的成員變數?類只有一個,而該類的例項物件有多個,如果是與物件關聯,哪關聯的是哪個物件呢?所以欄位fieldX 代表的是x的定義,而不是具體的x變數。

示例程式碼:

private int x;

public int y;

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

}

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));

作業:將任意一個物件中的所有String型別的成員變數所對應的字串內容中的"b"改成"a"

class Xxx

{

String name="abc";

String email="abd";

int x = 5;

}

func(Object obj)

{

Field [] fields =obj.getClass().getDeclaredFields();

for(Field field : fields)

{

if(field.getType()==java.lang.String.class)//使用equals也行,當然這種方式更好

{

field.setAccesible(true);

String original = (String)field.get(obj);

field.set(obj,original.replaceAll("b","a");

}

}

}

一個問題,我把自己的變數定義成private,就是不想讓人家訪問,可是,現在人家用暴力反射還是能夠訪問我,這說不通啊,能不能讓人家用暴力反射也訪問不了我。首先,private主要是給javac編譯器看的,希望在寫程式的時候,在原始碼中不要訪問我,是幫組程式設計師實現高內聚、低耦合的一種策略。你這個程式設計師不領情,非要去訪問,那我攔不住你,由你去吧。同樣的道理,泛型集合在編譯時可以幫助我們限定元素的內容,這是人家提供的好處,而你非不想要這個好處,怎麼辦?繞過編譯器,就可以往集合中存入另外型別了。別人提供的好處我可以不要嘛。

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.4jdk1.5invoke方法的區別:

Jdk1.5public Object invoke(Objectobj,Object... args)

Jdk1.4public Object invoke(Objectobj,Object[] args),即按jdk1.4的語法,需要將一個數組作為引數傳遞給invoke方法時,陣列中的每個元素分別對應被呼叫方法中的一個引數,所以,呼叫charAt方法的程式碼也可以用Jdk1.4改寫為 charAt.invoke(“str”,new Object[]{1})形式。

用反射方式執行某個類中的main方法

寫一個程式,這個程式能夠根據使用者提供的類名,去執行該類中的main方法。

用普通方式調完後,大家要明白為什麼要用反射方式去調啊?

我編寫程式的時候,我並不知道我會呼叫哪個類,你傳給我引數或者在配置檔案中指定我才知道類名,然後呼叫main方法。

我給你的陣列,你不會當作引數,而是把其中的內容當作引數。

Class clazz = Class.forName(arg[0]);

Method mMain = clazz.getMethod("main", String[].class);

mMain.invoke(null,new Object[]{newString[]{"aaa","bbb"}});//編譯器會拆包2個引數,我再打包

mMain.invoke(null,(Object)newString[]{"aaa","bbb"});//告訴編譯器是一個物件不要拆包

陣列也是物件。

class TestArrayArguments {

public static void main(String [] args)

{

for(String arg:args)

{

System.out.println("----------"+ arg + "----------");

}

}

}

程式啟動的時候傳遞給引數【word ppt等開啟一個檔案時,是呼叫這個程式,把檔名作為引數】

陣列的反射

具有相同維數和元素型別的陣列屬於同一個型別,即具有相同的Class例項物件。

代表陣列的Class例項物件的getSuperClass()方法返回的父類為Object類對應的Class

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

      

true

false

false

[I

java.lang.Object

java.lang.Object

基本型別的一維陣列可以被當作Object型別使用,不能當作Object[]型別使用(即基本型別不是Object);非基本型別的一維陣列,既可以當做Object型別使用,又可以當做Object[]型別使用。

Arrays.asList()方法處理int[]String[]時的差異。

Arrays.asList()方法處理int[]【整體,陣列作為一個引數,1.5的可變引數】和String[]【一個字串陣列,物件陣列】時的差異,以及Arrays.deepToString()方法不能處理int[],但能處理String[]的原因。

Array工具類用於完成對陣列的反射操作。

private static void printObject(Object obj) {

if(obj.getClass().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);

}

}

思考題:怎麼得到陣列中的元素型別?

這是不行的,因為對於一個Object[] a來說,裡面可以存放任何的資料型別,只能是針對每一個元素獲取型別。a[i].getClass().getName();

1equals方法用於比較物件的內容是否相等(覆蓋以後),預設按照地址。

2hashcode方法只有在集合中用到。不僅是HashMap,所有使用雜湊的資料結構(HashSet,HashMap,LinkedHashSet,and
LinkedHashMap)
,根據物件的hashCode值將物件放入集合的不同區域,提高查詢的速度。

3、當覆蓋了equals方法時,比較物件是否相等將通過覆蓋後的equals方法進行比較(判斷物件的內容是否相等)。一般來說,要講物件放入hash演算法的集合中,不僅要覆蓋其equals方法,還要覆蓋其hashCode方法。

4如果equals()比較相同,那麼hashcode()肯定相同。如果hashcode()比較相同,那麼equals()不一定相同。將物件放入到集合中時,首先判斷要放入物件的hashcode值與集合中的任意一個元素的hashcode值是否相等,如果不相等直接將該物件放入集合中。如果hashcode值相等,然後再通過equals方法判斷要放入物件與集合中的任意一個物件是否相等,如果equals判斷不相等,直接將該元素放入到集合中,否則不放入。

5ObjecthashCode是根據地址計算,其equals方法是根據==也是是根據地址,你覆蓋他們就是從業務邏輯上來看的。一般認為資料一樣的物件就是一個。

造成記憶體洩露

反射的作用à實現框架功能

框架與框架要解決的核心問題

Ø  我做房子賣給使用者住,由使用者自己安裝門窗和空調,我做的房子就是框架,使用者需要使用我的框架,把門窗插入進我提供的框架中。框架與工具類有區別,工具類被使用者的類呼叫,而框架則是呼叫使用者提供的類。

Ø  什麼是框架,例如,我們要寫程式掃描.java檔案中的註解,要解決哪些問題:讀取每一樣,在每一箇中查詢@,找到的@再去查詢一個列表,如果@後的內容出現在了列表中,就說明這是一個我能處理和想處理的註解,否則,就說明它不是一個註解或者說至少不是一個我感興趣和能處理的註解。接著就編寫處理這個註解的相關程式碼。現在sun提供了一個apt框架,它會完成所有前期工作,只需要我們提供能夠處理的註解列表,以及處理這些註解的程式碼。Apt框找到我們感興趣的註解後通知或呼叫我們的處理程式碼去處理。

你做的門呼叫鎖,鎖是工具,你做的門被房子呼叫,房子是框架,房子和鎖都是別人提供的。

Properties props = new Properties();

//先演示相對路徑的問題

//InputStream ips = newFileInputStream("config.properties");

/*一個類載入器能載入.class檔案,那它當然也能載入classpath環境下的其他檔案,既然它有如此能力,它沒有理由不順帶提供這樣一個方法。它也只能載入classpath環境下的那些檔案。注意:直接使用類載入器時,不能以/打頭。*/

//InputStream ips =ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/javaenhance/config.properties");

//Class提供了一個便利方法,用載入當前類的那個類載入器去載入相同包目錄下的檔案

//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");

InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/javaenhance/config.properties");//**

props.load(ips);

Ips.close();

String className = props.getProperty("className");

Class clazz = Class.forName(className);

Collection collection = (Collection)clazz.newInstance();

//Collection collection = new ArrayList();

ReflectPoint pt1 = new ReflectPoint(3,3);

ReflectPoint pt2 = new ReflectPoint(5,5);

ReflectPoint pt3 = new ReflectPoint(3,3);

collection.add(pt1);

collection.add(pt2);

collection.add(pt3);

collection.add(pt1);

System.out.println(collection.size());

}

Ø  【工具類和框架類,都是使用別人的類,但是呼叫工具類是幹具體的活【先有了工具類,直接使用】,自己寫的類被框架呼叫【框架先寫,使用反射呼叫後寫的類】。框架怎麼能夠呼叫還沒有出現的類呢?框架提供一個抽象,或者一個介面,比如一個方法是用來幹什麼的【抽象的】,具體怎麼幹就是你的事了。比如struts裡面的validate方法抽象出來就是校驗,具體怎麼校驗是你的事,我知道你的累裡面肯定有個validate方法,所以我能夠通過反射的方法來呼叫;你寫的類裡面可以有你的個性,但是必須滿足框架的規定,規則,否則這麼呼叫呢?】,如此一來,什麼都可以配置。

內省à瞭解JavaBean

JavaBean是一種特殊的Java類,主要用於傳遞資料資訊,這種java類中的方法主要用於訪問私有的欄位,且方法名符合某種命名規則。

如果要在兩個模組之間傳遞多個資訊,可以將這些資訊封裝到一個JavaBean中,這種JavaBean的例項物件通常稱之為值物件(Value Object,簡稱VO)。這些資訊在類中用私有欄位來儲存,如果讀取或設定這些欄位的值,則需要通過一些相應的方法來訪問,大家覺得這些方法的名稱叫什麼好呢?JavaBean的屬性是根據其中的settergetter方法來確定的,而不是根據其中的成員變數【屬性只是一個橋樑。如果方法名為setId,中文意思即為設定id,至於你把它存到哪個變數上,用管嗎?如果方法名為getId,中文意思即為獲取id,至於你從哪個變數上取,用管嗎?去掉set字首,剩餘部分就是屬性名,如果剩餘部分的第二個字母是小寫的,則把剩餘部分的首字母改成小的。

Ø  setId()的屬性名àid

Ø  isLast()的屬性名àlast

Ø  setCPU的屬性名是什麼?àCPU

Ø  getUPS的屬性名是什麼?àUPS

總之,一個類被當作javaBean使用時,JavaBean的屬性是根據方法名推斷出來的,它根本看不到java類內部的成員變數。【settergetter

一個符合JavaBean特點的類可以當作普通類一樣進行使用,但把它當JavaBean用肯定需要帶來一些額外的好處,我們才會去了解和應用JavaBean!好處如下:

Ø  Java EE開發中,經常要使用到JavaBean【比如傳遞表單資料】。很多環境就要求按JavaBean方式進行操作,別人都這麼用和要求這麼做,那你就沒什麼挑選的餘地!

Ø  JDK中提供了對JavaBean進行操作的一些API,這套API就稱為內省。如果要你自己去通過getX方法來訪問私有的x,怎麼做,有一定難度吧?用內省這套api操作JavaBean比用普通類的方式更方便。

用一種遍歷的方式獲取某個屬性值

BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());

PropertyDescriptor[]pds = beanInfo.getPropertyDescriptors();

for(PropertyDescriptorpd :pds){

if(pd.getName().equals(propertyName)){//找到你要找的屬性名

pd.getWriteMethod().invoke(pt1,value);

break;

}

                }

開源工具BeanUtils