1. 程式人生 > >Java反射-再次認識

Java反射-再次認識

    最近的學習發現在很多方面,基礎知識掌握的還很不牢固,所以對於架構、知識點等屬於那種問啥啥知道,做啥啥不出來的那種型別。前些日子,老師一直在抓基礎,做什麼都要從最簡單的demo開始,只有懂了原理之後再去用一些高深的東西如框架等才會理解的更深刻。現在首先需要理解的就是基本上每個Java框架都在用的反射技術。
    要想理解反射,首先得了解類的載入過程,看下圖:
    我們的原始碼經過編譯之後變成位元組碼,然後在JVM中執行時通過類載入器載入位元組碼在記憶體中生成Class類物件,這個Class類物件內包含有field物件(類的成員變數生成)、constructor物件(類的構造方法生成)和method物件(類的方法生成)。當我們拿到一個類或者物件的時候就可以通過反射對它們進行操作,下面再來看反射:

  • 什麼是反射
    •  Java反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力,是Java被視為動態(或準動態)語言的一個關鍵性質。這個機制允許程式在執行時透過Reflection APIs取得任何一個已知名稱的class的內部資訊,包括其modifiers(諸如public, static 等)、superclass(例如Object)、實現之interfaces(例如Cloneable),也包括fields和methods的所有資訊,並可於執行時改變fields內容或喚起methods。 Java反射機制容許程式在執行時載入、探知、使用編譯期間完全未知的classes。換言之,Java可以載入一個執行時才得知名稱的class,獲得其完整結構。
  • 反射用在哪兒
    • 多用於框架和元件,寫出複用性高的通用程式。
      • 如struts的form只要有了form物件和 property名字就可以利用反射給property賦值和取值 對這類操作 一個方法就可以搞定。如果hibernate不用欄位進行反射對映 那麼每個HQL的編譯和結果處理 將無法進行等等。
  • 怎麼用
    • 針對我們所知的不同情況分別有3種方法獲取Class位元組碼物件
      • 當已知類名的時候,通過 “類名.class”獲得
      • 當已知物件的時候,通過 “物件.getClass”獲得
      • 當已知包括包名在內的完整類名(假設為String格式)的時候,可通過 “Class.forName(String)”獲得
    • 獲取Class位元組碼物件之後可以構造物件例項、獲取物件中的屬性物件、方法物件和建構函式物件
    • 獲取以上需要的各種物件之後就可以操作它們,進行增刪改查等操作了。
  • 優點與缺點是什麼
    • 優點
為什麼要用反射機制?直接建立物件不就可以了嗎,這就涉及到了動態與靜態的概念,     靜態編譯:在編譯時確定型別,繫結物件,即通過。     動態編譯:執行時確定型別,繫結物件。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,用以降低類之間的藕合性。     一句話,反射機制的優點就是可以實現動態建立物件和編譯,體現出很大的靈活性,特別是在J2EE的開發中 它的靈活性就表現的十分明顯。比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,釋出了,當發現需要更新某些功能時,我們不可能要使用者把以前的解除安裝,再重新安裝新的版本,假如這樣的話,這個軟體肯定是沒有多少人用的。採用靜態的話,需要把整個程式重新編譯一次才可以實現功能的更新,而採用反射機制的話,它就可以不用解除安裝,只需要在執行時才動態的建立和編譯,就可以實現該功能。
    • 缺點
它的缺點是對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於只直接執行相同的操作。 
  • 例子
    • 獲取Class位元組碼物件的方法
<span style="font-size:18px;"><span style="font-size:14px;">/**
	 * 獲取位元組碼Class物件的方法
	 * @throws ClassNotFoundException
	 */
	@Test
	public void demo1() throws ClassNotFoundException{
		//獲取Class物件三種方式
		
		//1.已知類
		Class  c1 = ReflectTest.class;
		
		//2.已知物件
		Object o = new ReflectTest();
		Class c2 = o.getClass();
		
		//3.未知類和物件,知道完整類名
		String className = "com.lc.reflect.ReflectTest";
		Class c3 = Class.forName(className);
		
		System.out.println(c1);
		System.out.println(c2);
		System.out.println(c3);
	}</span></span>
    • 操作構造方法
<span style="font-size:18px;"><span style="font-size:14px;">/**
	 * 獲取構造方法練習
	 * @throws Exception
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Test
	public void demo2() throws Exception{
		
		//獲取的Person類位元組碼物件
		String className = "com.lc.reflect.Person";
		Class c = Class.forName(className);
		
		//通過位元組碼物件獲得所有的構造方法
		Constructor[] constructors = c.getConstructors();
		for (int i = 0; i < constructors.length; i++) {
			System.out.println(constructors[i]);
		}
		
		//獲取指定的構造方法
		Constructor constructorDefault = c.getConstructor();
		System.out.println(constructorDefault);
		//Sring.class為String的位元組碼物件
		Constructor constructor = c.getConstructor(String.class); //帶引數型別為String的構造方法
		System.out.println(constructor);
		
		//建立物件例項的正常寫法:
		Person person1 = new Person();
		Person person2 = new Person("lc");
		
		//使用反射構造Person物件的例項
		Person reflectPerson1 = (Person)constructorDefault.newInstance();  //無參構造方法
		Person reflectPerson1_1 = (Person)c.newInstance();  //通過Class物件直接newInstance,將會預設呼叫目標類無參構造方法
		Person reflectPerson2 = (Person)constructor.newInstance("lc");//引數為String型別的構造方法
		
		
	}</span></span>
    • 操作成員變數
<span style="font-size:18px;"><span style="font-size:14px;">/**
	 * 使用反射操作類成員變數的練習
	 */
	@SuppressWarnings("rawtypes")
	@Test
	public void demo3() throws Exception{
		
		//面向物件的寫法是物件呼叫屬性,而反射就正好相反了。。
		Person p = new Person("lc");
		System.out.println("物件呼叫屬性的寫法=====>:"+p.getName());
		
		//使用反射操作類成員變數 --Field類
		//1.必須獲得目標類的位元組碼物件
		Class c = Class.forName("com.lc.reflect.Person");
		
		//2.操作成員例項變數name--獲得name代表Field物件
		Field[] f1 = c.getFields(); //獲取所有public成員變數,包括父類繼承
		for (int i = 0; i < f1.length; i++) {
			System.out.println(f1[i]);
		}
		Field[] f2 = c.getDeclaredFields(); //獲取當前類定義的所有成員,包括private
		for (int i = 0; i < f2.length; i++) {
			System.out.println(f2[i]);
		}
		
		//獲得name成員變數
		Field field = c.getDeclaredField("name"); //當前field是private
		//設定private變數可以訪問
		field.setAccessible(true);
		
		//獲得p物件指定name屬性值
		Object value = field.get(p); //相當於p.getName();
		System.out.println("反射操作成員變數的寫法=====>"+value);
		
	}
	
	/**
	 * 使用反射改變成員變數的值(包括私有)
	 * @throws Exception
	 */
	@Test
	public void demo4() throws Exception{
		Person p = new Person();
		//呼叫p物件中setName設定name的值
		//1.獲取位元組碼物件
		Class c = Class.forName("com.lc.reflect.Person");
		//2.操作setName獲得setName物件反射物件的Method物件
		//String型別引數setName方法
		Method setName = c.getDeclaredMethod("setName", String.class);
		//呼叫p物件中setName
		setName.invoke(p, "sky"); //相當於p.setName("sky");
		//3.讀取name的值getName方法
		Method getName = c.getDeclaredMethod("getName");
		Object name = getName.invoke(p); //相當於p.getName();
		System.out.println("反射獲取成員變數的值======>"+name);
	}</span></span>
    • 操作普通方法
<span style="font-size:18px;"><span style="font-size:14px;">/**
	 * 操作方法物件
	 * @throws Exception
	 */
	@Test
	public void demo5() throws Exception{
		//已知String型別完整類名---獲得位元組碼物件
		String className = "com.lc.reflect.Person";
		Class c = Class.forName(className);
		
		//已知Class物件,構造例項
		Object obj = c.newInstance(); //呼叫無參構造方法
		
		//獲得位元組碼物件中指定屬性和方法
		//獲得name屬性
		Field f = c.getDeclaredField("name");
		//獲得setName方法
		Method setName = c.getDeclaredMethod("setName", String.class);
		
		//修改屬性的值,執行相應方法
		f.setAccessible(true);
		f.set(obj, "sky");
		
		setName.invoke(obj, "sky_lc");
		
		//以上程式碼等價於下面的程式碼
		Person p = new Person();
		//p.name = "sky";
		p.setName("sky_lc");
	}</span></span>
    Java語言反射提供一種動態連結程式元件的多功能方法。它允許程式建立和控制任何類的物件,無需提前硬編碼目標類。這些特性使得反射特別適用於建立以非常普通的方式與物件協作的庫。Java reflection 非常有用,它使類和資料結構能按名稱動態檢索相關資訊,並允許在執行著的程式中操作這些資訊。Java 的這一特性非常強大,並且是其它一些常用語言,如 C、C++、Fortran 或者 Pascal 等都不具備的。 由於用於欄位和方法接入時反射要遠慢於直接程式碼,反射在效能上會有所影響,但效能問題的程度取決於程式中是如何使用反射的。如果它作為程式執行中相對很少涉及的部分,緩慢的效能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在效能關鍵的應用的核心邏輯中使用時效能問題才變得至關重要。所以,合理的使用反射將大大提高我們程式的通用性和複用性。