初步理解java中的反射和內省及反射和內省的用法大集合

         反射是java中一個不太被大多數人關注的一個模塊,尤其對於很多初學者,甚至不知道何爲反射,但是現在在java中越來越多的企業在筆試,面試的時候提到反射的知識,因爲反射機制在web上的確有着不可替代的重要作用。究竟什麼是反射呢?今天我就帶領大家初步理解java中的反射機制,JAVA反射機制是在運行狀態中,能夠動態獲取的信息以及動態調用對象的方法的功能的一種機制。 通俗點說就是:Java程序可以加載一個運行時才得知名稱的class(稱爲運行時類),獲悉其完整構造(但不包括methods定義),並生成其對象實體、對其fields設值或喚起其methods。再通俗講,其實就是一個字符串,當然這個字符串是一個從包到類的一個地址名稱,通過反射機制能夠生成一個運行時對象,然後可以通過這個對象操作類中的屬性和方法,當然這樣講可能也有些片面了。
         上面的解釋可能太過於抽象,大家可能還不能明白究竟什麼是實實在在的反射,沒關係,只要大家細心研究下面的實例,一定會讓大家將反射機制的知識變成自己的東西。
         下面是我自己寫的一個程序,它包括了通過反射機制反射一個類,反射一個方法,反射一個屬性,以及甚至可以反射一個數組,當然還有內省的實現,內省並不屬於反射,要告訴大家的是,我的每個實現都是分爲在默認構造器和在非默認構造器下不同的實現過程。
package com.angel.prac;

public class Earth {
	private String name;
	private double volume;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getVolume() {
		return volume;
	}
	public void setVolume(double volume) {
		this.volume = volume;
	}
}
package com.angel.prac;

public class Mars {
	private String name;
	private float volume;
	public Mars(){}
	public Mars(String name, float volume) {
		this.name = name;
		this.volume = volume;
	}
	public Mars(String name){
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public float getVolume() {
		return volume;
	}
}
反射一個類:
package com.angel.prac;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Reflaction {
	public void reflectClass() throws Exception{
		//反射一個類:
		//默認構造器的反射類:
		String s = "com.angel.prac.Earth";
		Class <?> c = Class.forName(s);
		Earth earth = (Earth)c.newInstance();
		earth.setName("Angel");
		earth.setVolume(521.1314);
		System.out.println("The Earth's name is "+ earth.getName() + "-----" +
				"The volume is "+ earth.getVolume());

		//非默認構造器的反射類:
		String s0 = "com.angel.prac.Mars";
		Class <?> c0 = Class.forName(s0);
		// java.lang.Class.getConstructor(Class<?>... arg0)
		Constructor <?> constructor = c0.getConstructor(String.class,float.class);
		Mars mars = (Mars)constructor.newInstance("Mars",1314.521f);
		System.out.println("The Mars' name is " + mars.getName() + "----- "+"The volume is "
				+ mars.getVolume());		
	}

       首先構造一個字符串,“com.angel.prac”是包名,"Earth"是我自己寫的一個類(Earth類在上面有具體的實現),通過Class類下的forName就可以將這個含有確切是哪個類的字符串返回一個確切的運行時類對象,然後通過newInstance方法就可以產生一個新的對象,這個對象是從c來的,newInstance這個方法的返回值是Object,此刻我們將要強轉爲Earth類型的,從而能夠運用Earth類下的屬性,方法。現在實際上我們已經拿到了這個運行時類對象了,就可以利用這個對象去操縱Earth類下的屬性,方法了。
       現在講講在非默認的情況下,這個Mars類中含有非默認構造器,並且參數的傳遞是通過這個非默認構造器進行的,而不是Earth類中通過Getter和Setter的封裝set到類中的。對於非默認構造器,我們要通過這個運行時類的對象去調用getConstructor方法獲得非默認構造器,getConstructor(String.class,float.class)2個參數意思是指定反射Constructor(String name,float volume)這個構造方法。通過這個constructor對象再去產生一個新的實例,這時,就需要向將要生成的實例的非默認構造器中傳遞參數了。剩下的部分就和前面一樣了。
	public void reflectMethod() throws Exception{
		//反射一個方法:
		//默認構造器的反射方法:
		String s = "com.angel.prac.Earth!nAMe!Earth";
		String [] string = s.split("!");
		Class<?> c = Class.forName(string[0]);
		Earth earth = (Earth) c.newInstance();	
		Method setMethod = c.getMethod("set" + string[1].substring(0, 1).toUpperCase() + 
				string[1].substring(1, string[1].length()).toLowerCase(),String.class);
		Method getMethod = c.getMethod("get" + string[1].substring(0, 1).toUpperCase() + 
				string[1].substring(1, string[1].length()).toLowerCase());
		setMethod.invoke(earth, string[2]);
		Object obj = getMethod.invoke(earth);
		System.out.println(obj);

		//非默認構造器的反射方法:
		String s0 = "com.angel.prac.Mars!NaMe!vOLUme!Mars!521.1314f";
		String [] str = s0.split("!");
		Class<?> c1 = Class.forName(str[0]);
		Constructor <?> constructor1 = c1.getConstructor(String.class, float.class);
		Mars mars1 = (Mars)constructor1.newInstance(str[3] , Float.parseFloat(str[4]));
		Method getMarsName = c1.getMethod("get" + str[1].substring(0, 1).toUpperCase()+
				str[1].substring(1,str[1].length()).toLowerCase());
		Method getMarsVolume = c1.getMethod("get" + str[2].substring(0, 1).toUpperCase()+
				str[2].substring(1,str[2].length()).toLowerCase());
		Object obj0 = getMarsName.invoke(mars1);
		Object obj1 = getMarsVolume.invoke(mars1);
		System.out.println(obj0);
		System.out.println(obj1);
	}	

      反射一個方法,splite這個方法是處理字符串的一個方法,意爲分割,在本例中就是說只要遇到一個!號那麼我就分割成一部分,存到一個數組中,最後返回一個字符串數組。用這個運行時類的對象去getMethod,就可以去獲得一個程序員指定名字的方法了,在這裏,我用了一系列處理字符串的API方法,在這裏,就不多多細說了,大家可以參考API中文文檔。在這裏有一個非常重要的方法那就是invoke,意思是調用,就是說將執行earth這個新對象下的程序員想調用的方法,這個方法是返回在Method的對象中的。然後返回一個Object對象。
      非默認下的反射方法,其實就是一點不用,參數進入到一個類中的方式不同了,前面已經說過,因爲有了非默認構造器,所以也就不需要setXXX這個方法了。
	public void reflectArray(){
		//反射一個數組:	
		Object obj = Array.newInstance(int.class, 5);
		for (int i = 0; i < 5; i++) {
			Array.set(obj, i, i+1);
		}
		for (int i = 0; i < 5; i++) {
			System.out.println(Array.get(obj, i));
		}
	}
      反射一個數組,很簡單,66行是說產生一個大小爲5的int類型的數組對象。通過Array下的靜態方法set和get向裏面存和往外取數據。
	public void reflectField() throws Exception{
		//反射屬性:
		//默認構造器反射一個屬性:
		String s0 = "com.angel.prac.Earth!volume!5211314";
		String [] str = s0.split("!");
		Class<?> c0 = Class.forName(str[0]);
		Earth earth = (Earth)c0.newInstance();
		//java.lang.Class.getDeclaredField(String arg0)這個用於private的屬性
		//java.lang.Class.getField(String arg0)這個用於public屬性
		Field fie = c0.getDeclaredField(str[1]);
		fie.setAccessible(true);
		fie.set(earth, 3344);
		System.out.println(fie.get(earth));


		//非默認構造器反射一個屬性:
		String s = "com.angel.prac.Mars!name!volume!火星星!521.1314f";
		String [] string = s.split("!");
		Class<?> c = Class.forName(string[0]);
		Constructor<?> constructor = c.getConstructor(String.class, float.class);
		Mars mars = (Mars)constructor.newInstance(string[3],Float.parseFloat(string[4]));
		//java.lang.Class.getDeclaredField(String arg0)這個用於private的屬性
		//java.lang.Class.getField(String arg0)這個用於public屬性
		Field field = c.getDeclaredField(string[1]);
		Field f =c.getDeclaredField(string[2]);
		field.setAccessible(true);
		f.setAccessible(true);
		field.set(mars, "爲什麼是火星星?");
		f.setFloat(mars, 1234.567f);
		System.out.println(field.get(mars));
		System.out.println(f.get(mars));
	}

     反射一個方法,例子中已經註釋了,getDeclaredField和getField的區別,我們可以通過getDeclaredField訪問類中的私有屬性,返回一個Field,要想對數據進行存取,必須將setAccessible設置爲true,這時候,你就告訴JVM了,我要訪問這個屬性,我有對它操作的權利。非默認構造器中的獲取在這就不重複了。
	public void reflectIntrospection() throws Exception{
		//內省(僅提供屬性示例,也可以操作類和方法):
		String s0 = "com.angel.prac.Earth";
		Class<?> c0 = Class.forName(s0);
		Earth earth = (Earth)c0.newInstance();
		BeanInfo bean = Introspector.getBeanInfo(c0, Object.class);
		PropertyDescriptor[]  descriptor = bean.getPropertyDescriptors();
		for(PropertyDescriptor p : descriptor){
			if(p.getName().equals("name")){
				p.getWriteMethod().invoke(earth, "地球球");
				System.out.println(p.getReadMethod().invoke(earth));
			}
		}

		//非默認構造器反射一個屬性:
		String s = "com.angel.prac.Mars!name!volume!火星星!123.09f";
		String [] string = s.split("!");
		Class<?> c = Class.forName(string[0]);
		Constructor<?> constructor = c.getConstructor(String.class, float.class);
		Mars mars = (Mars)constructor.newInstance(string[3],Float.parseFloat(string[4]));
		BeanInfo beanInfo = Introspector.getBeanInfo(c, Object.class); 
		PropertyDescriptor [] pd = beanInfo.getPropertyDescriptors();
		//直接操作屬性:
		pd[0].setValue("name", "反射學完了,感到so easy!");
		pd[1].setValue("volume",234);
		System.out.println(pd[0].getValue("name"));
		System.out.println(pd[1].getValue("volume"));
		//通過方法獲得屬性:
		for (PropertyDescriptor propertyDescriptor : pd) {
			if(propertyDescriptor.getName().equals("volume")){
				System.out.println(propertyDescriptor.getReadMethod().invoke(mars));
			}
		}
	}

        內省,這是個什麼東東呢?不妨我們去問一下google大神,看看google是如何說的:
        內省是 Java 語言對 Bean 類屬性、事件的一種缺省處理方法。例如類 A 中有屬性 name, 那我們可以通過 getName,setName 來得到其值或者設置新的值。通過 getName/setName 來訪問 name 屬性,這就是默認的規則。 Java 中提供了一套 API 用來訪問某個屬性的 getter/setter 方法,通過這些 API 可以使你不需要了解這個規則(但你最好還是要搞清楚),這些 API 存放於包 java.beans 中。 一般的做法是通過類 Introspector 來獲取某個對象的 BeanInfo 信息,然後通過 BeanInfo 來獲取屬性的描述器( PropertyDescriptor ),通過這個屬性描述器就可以獲取某個屬性對應的 getter/setter 方法,然後我們就可以通過反射機制來調用這些方法。
       我們可以直接操作屬性,也可以通過獲得方法來操作屬性,具體的方法,大家可以根據eclipse中的提示和API文檔慢慢琢磨。
	public static void main(String[] args) throws Exception {
		Reflaction reflection = new Reflaction();
		reflection.reflectClass();
		reflection.reflectMethod();
		reflection.reflectArray();
		reflection.reflectField();
		reflection.reflectIntrospection();
	}
}

       好了,到這裏呢,反射和內省講的就差不多了,不明白?不要緊,什麼也不要說,先照着我的代碼打三遍,打完了再看一遍這篇文章,我相信你會有很大的收穫。可能有很多的人問:我費這麼大的勁,就爲了傳個參數何必要這麼複雜呢?在以後的學習和工作中, Java 的反射以及內省應用到程序設計中去可以大大的提供程序的智能化和可擴展性,以後的web開發中我們只要操縱XML的配置文件就可以了,而不必再去修改源代碼,這是不是很爽呢?當然也大大地提高了代碼的可維護性。
      在這裏我只是簡單的給大家介紹一下,大家想了解更多,很簡單,去問一下google大神就可以了,對於我們深入瞭解java語言有很大的幫助。也歡迎大家一起和我進行交流,學習,歡迎大家指出不足之處,請聯繫QQ:497820217 備註:CSDN。
    本文爲原創,如需要轉載,請註明出處。





 

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