1. 程式人生 > >JAVA技術(一)——位元組碼&反射

JAVA技術(一)——位元組碼&反射

Reflection-反射是J2SE1.1就已經提出了,但當時僅支援Introspection自省。在1.2之後開始逐漸成熟,spirng、hibernate等成熟框架都大量使用java反射技術實現。

在面向物件設計思想中,使用類這一概念表述一類具有相同屬性的物件;而這些屬性值具體是什麼,由該類的每個例項化物件來確定,每個物件可以有不同的屬性值。反之,這些是否屬於同一類事務,java使用Class來描述類的訪問屬性、包名、欄位名。學習反射,首先就要了解這個首字母大寫的-Class

一、Class—位元組碼

在源程式中,例項化某個類時,先將類編譯成.class形成二進位制程式碼,然後把二進位制程式碼載入到java記憶體中,用於建立物件。這個二進位制程式碼就是Class類的位元組碼(Byte-code)。當使用到多個類時,記憶體中就會載入多個不同的位元組碼,佔用記憶體空間。位元組碼跟類是對應的,不同位元組碼在jvm中的內容以類的型別區分。

有了物件例項,通過obj.getClass()方法也可以獲得這個物件在記憶體中的位元組碼,得到位元組碼方可得到這個物件所對應的類。反之,知道了這個類也可以獲得例項物件。

二、一個New並不能解決問題

那麼問題來了,有些人可能會說,幹嘛那麼麻煩都個大圈,我直接new 一個類,不就可以輕鬆建立這個例項嗎?

這就涉及到java物件建立的兩個編譯方式:靜態編譯和動態編譯

靜態編譯:在編譯時確定物件,繫結物件即可通過編譯。動態編譯:執行時確定型別,繫結物件。

靜態編譯使用new關鍵字就可以獲取建立物件屬性方法,但在動態編譯時就行不通了。反射正是實現了動態建立物件和編譯。通過反射,傳入類名,形成位元組碼,載入到jvm中建立例項,然後獲取例項資訊,

最大限度發揮了java的靈活性,體現了多型的應用,有以降低類之間的藕合性。

簡言之,在程式還不知道要呼叫哪些類,建立哪些例項時,程式碼是無法將某個具體的類以new的方式例項化的。正如你還沒結婚,沒辦法先給自己未來的小孩買各種女式嬰兒用品,萬一他是個男孩兒呢?使用反射的目的即在於:傳入一個無論是什麼類,通過反射都可一獲取到這個類對應物件的屬性、方法等資訊。

聯想一下Spring 等框架,框架設計者並不知道我們會建立哪些類,那為什麼把bean網配置檔案裡一放,class屬性一寫,它就可以直接使用這個類的可用屬性和公共方法了呢。這就是典型的反射應用。

三、反射具體怎麼用?

通過反射,只要知道類名,就可以獲取該類方法、屬性、構造方法等資訊,然後通過java API對這些方法進行操作,對屬性賦值等。下面介紹幾種常用的利用反射機制實現獲取類資訊型別;

  1. 構造方法(Constructor)

    1)、獲取類的構造方法 Class.forName(java.lang.String).getConstructors();//得到String類的所有構造方法

  2.                                    Class.forName(java.lang.String).getConstructor(StringBuffer.class);//得到某一個類的構造方法

    2)、使用構造方法建立物件例項:

    通常:String str=new String(new StringBuffer(aaa))

    反射:Constructor c=Class.forName("java.lang.String").getDeclaredConstructor(StringBuffer.class);//獲取StringBuffer類的方法構造器
           String s=(String)c.newInstance(new StringBuffer("adf"));//建立名為adf的StringBuffer物件例項,強轉成String型別輸出
           System.out.println(s.toString()); //adf

  3. 獲取變數(Field)

    例如建立一個座標類Point,有xy兩個成員變數。

    1)、使用反射獲取類的成員變數
          System.out.println(Class.forName("java.lang.reflect.Array").getDeclaredFields()); //獲取所有宣告過的成員變數
          System.out.println(Class.forName("java.lang.reflect.Array").getFields());//無法獲取private 私有成員變數
     2)、通過set、get方法對該物件屬性進行資料操作
          Field day=Class.forName("it.webservice.mobile.WeekDay").getField("day");//noSuchFieldEcxeption
          day.setAccessible(true);
          System.out.println(day.get(day));

    使用反射獲取變數在框架中使用頻繁,以Spring為例,在Spring配置檔案中寫入類的包名.類名,新增property屬性,程式啟動時自動掃描配置檔案,把類例項化,同時反射獲取property屬性資訊就知道這個類中聲明瞭多少個欄位,再通過javaBean自省get/set的方式為物件取值賦值。使用非常廣泛。

  4. 獲取方法(Method)

     1)、獲取方法  
      Class.forName("java.lang.reflect.Array").getMethods();//返回一個method[]陣列  
     2)、可以使用invoke方法呼叫該方法
      Method[] methods=Class.forName("java.lang.reflect.Array").getMethods();
     3)、某個類main方法的獲取與呼叫
      System.out.println(Class.forName("it.webservice.mobile.WeekDay").getMethod("main", String[].class));//方法名,方法傳入引數型別
      Class.forName("it.webservice.mobile.WeekDay").getMethod("main", String[].class).invoke(null, (Object)new String[]{"jdkaj"});

      注意在獲取類的main方法時,main方法的引數型別為String集合,在呼叫main方法時,注意第一個引數為null表示main的靜態方法屬性;第二個引數傳入一個string陣列,並強轉成一個Object物件型別。因為如果傳入普通陣列,使用反射會把陣列打散成若干個引數,所以轉成一個Object物件傳入invoke使得main傳入正確引數。對於陣列的反射應用會在下一篇博文中談到。

四、反射在框架中的具體應用

在具體框架中一般都會結合配置檔案的使用,把可變的傳入類型別以配置檔案配置的形式進行配置。簡單的程式碼實現流程如下:

	public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
		InputStream input=new FileInputStream("config.properties");//載入config配置檔案
		Properties prop=new Properties();
		prop.load(input);
		input.close();
		String className=prop.getProperty("className");//獲取配置檔案中className值:java.util.ArrayList
		Collection cl=(Collection)Class.forName(className).newInstance();//例項化ArrayList
		
		System.out.println(cl.toString());
	}

     通過檢視Spring mvc 框架的部分原始碼,很容易發現這種載入配置檔案-利用反射將類例項化的影子。例如在Spring MVC核心過濾器 dispatcher中,通過三級繼承,父類實現ApplicationContextAware介面,而這個介面僅提供了一個方法

public interface ApplicationContextAware extends Aware {

	/**
	 * Set the ApplicationContext that this object runs in.
	 * Normally this call will be used to initialize the object.
	 * <p>Invoked after population of normal bean properties but before an init callback such
	 * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
	 * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader},
	 * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
	 * {@link MessageSourceAware}, if applicable.
	 * @param applicationContext the ApplicationContext object to be used by this object
	 * @throws ApplicationContextException in case of context initialization errors
	 * @throws BeansException if thrown by application context methods
	 * @see org.springframework.beans.factory.BeanInitializationException
	 */
	void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

       從這個方法漫長的註釋不難看出,這個介面主要用於提供配置檔案初始化時,呼叫初始化物件時使用的,也就是說,傳入applicationContext引數,底層肯定跟上面程式碼的邏輯八九不離十,載入配置,同時利用反射初始化配置中的normal Bean 和properties。

       一通百通,搞懂這些原理,可能確實在平時的程式設計中會相對比較少用到反射、委託等,但做平臺、做框架的毋庸置疑,百分之百會使用到這些技術提高靈活擴充套件性。因為目前大多都是在用框架,畢竟金字塔頂端的也只是那麼少部分。但如果遇到系統瓶頸了,考慮使用這些核心技術來解決問題的那一天就不遠了。視野決定高度。

      下一篇會介紹陣列的反射應用,另外側重介紹ArrayList 和HashSet在儲存和使用上的區別與聯絡。