Java基礎知識_反射

反射的這一段內容我整整看了三遍纔算看明白,並不是因爲內容多麼的深奧,而是一直不明白這東西是用來幹什麼。不關注“是什麼”和“爲什麼”而只關注“怎麼用”是學習時最痛苦的事情。因爲你會發現,明明每一步你都能看懂,但連在一起就不知道是什麼意思,或者寫着上一步卻不知道下一步該怎麼寫,這就是沒有概覽全局的弊端。


(以下內容一部分是從網上搜集的資料)


一 、什麼是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力。這一概念的提出很快引發了計算機科學領域關反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和於應用反射性的研究。它首先被程序語言的設計領域所採用,並在Lisp和麪向對象方面取得了成績。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基於反射機制的語言。最近,反射機制也被應用到了視窗系統、操作系統和文件系統中。
反射本身並不是一個新概念,儘管計算機科學賦予了反射概念新的含義。在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過採用某種機制來實現對自己行爲的描述(self-representation)和監測(examination),並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。


二、什麼是Java中的類反射:

Reflection 是 Java 程序開發語言的特徵之一,它允許運行中的 Java 程序對自身進行檢查,或者說“自審”,並能直接操作程序的內部屬性和方法。Java 的這一能力在實際應用中用得不是很多(主要用於開發框架,目前在Android中也會運用到此技術),但是在其它的程序設計語言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒有辦法在程序中獲得函數定義相關的信息。

Reflection 是 Java 被視爲動態(或準動態)語言的關鍵,允許程序於執行期 Reflection APIs 取得任何已知名稱之 class 的內部信息,包括 package、type parameters、superclass、implemented interfaces、inner classes, outer class, fields、constructors、methods、modifiers,並可於執行期生成instances、變更 fields 內容或喚起 methods。


三、Java類反射中所必須的類:

Java的類反射所需要的類並不多,它們分別是:Field、Constructor、Method、Class、Object,下面我將對這些類做一個簡單的說明。
Field類:提供有關類或接口的屬性的信息,以及對它的動態訪問權限。反射的字段可能是一個類(靜態)屬性或實例屬性,簡單的理解可以把它看成一個封裝反射類的屬性的類。
Constructor類:提供關於類的單個構造方法的信息以及對它的訪問權限。這個類和Field類不同,Field類封裝了反射類的屬性,而Constructor類則封裝了反射類的構造方法。
Method類:提供關於類或接口上單獨某個方法的信息。所反映的方法可能是類方法或實例方法(包括抽象方法)。 這個類不難理解,它是用來封裝反射類方法的一個類。
Class類:類的實例表示正在運行的 Java 應用程序中的類和接口。枚舉是一種類,註釋是一種接口。每個數組屬於被映射爲 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象。

Object類:每個類都使用 Object 作爲超類。所有對象(包括數組)都實現這個類的方法。


四、Java的反射類怎麼用

說到如何運用反射,就得分別談談每一種反射類的用途。

(一)構造函數的反射(Constructor類)

Constructor類代表某個類的一個構造方法。
package cn.icecino.d1;
import java.lang.reflect.Constructor;
public class ConstructorDemo {

	public static void main(String[] args) throws Exception{
		// TODO 自動生成的方法存根
		
		//得到某個類所有的構造方法
		Constructor<?>[] constructors = Class.forName("java.lang.String").getConstructors();
		
		//取得指定類的構造方法
		Class classType = Class.forName("java.lang.String");
        Constructor constructor = classType.getDeclaredConstructor(StringBuffer.class);
        
        /*創建實例對象*/
        //通常方式:
        String str = new String(new StringBuffer("abc"));
        //反射方式
        String str1 = (String)constructor.newInstance(new StringBuffer("abc"));
        
        //Class.NewInstance()方法
        String str2 = (String)Class.forName("java.lang.String").newInstance();
        /*該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。該方法的內部用到了緩存機制來保存默認構造方法的實例對象*/
        
        //獲得構造方法並創建實例對象
        Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
        /*getConstructor()中用到是不定長度參數,1.4版本之前,則是通過傳入數組來調節參數類型和數組不確定的情況*/
        String str3 = (String)constructor1.newInstance(new StringBuffer("aaa"));
	}
}

(二)字段的反射(Field類)

在一個類中,他的屬性可能是公有的,也可能是私有的,這時我們可以根據不同的需要來選擇反射的方法。getFields()用於反射類中的公有成員,getDeclaredField()用於反射類中的所有成員。
package cn.icecino.d1;
import java.lang.reflect.Field;
public class FieldDemo {

	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		// TODO 自動生成的方法存根

		/*首先,通過getFields()方法,我們將獲得類中的public屬性*/
		
		Field[] fields = Demo.class.getFields();		
		for (Field f : fields)
		{
			System.out.println(f.toString());
		}
		
		/* 輸出:
		 *  public java.lang.String cn.itcast.day1.Demo.name
		 *  public int cn.itcast.day1.Demo.number
		 *  都是public的屬性
		 */		
		
		/*如果我們不光想要反射出公有的屬性,還想獲得被私有的屬性,則需要使用getDeclaredFields()方法*/
		
		Field[] fields2 = Demo.class.getDeclaredFields();
		for (Field f : fields2)
		{
			f.setAccessible(true);//將可訪問設置爲true
			/*這種強行輸出類中私有屬性的反射也被稱爲暴力反射*/
			System.out.println(f.toString());
		}
		
		/* 輸出:
		 *  public java.lang.String cn.itcast.day1.Demo.name
		 *  public int cn.itcast.day1.Demo.number
		 *  private int cn.itcast.day1.Demo.age
		 *  將Demo類中的私有方法也輸出出來
		 */
		
		/*也可以反射出指定的屬性*/
		Demo d = new Demo("a",123,23);
		Field field = Demo.class.getDeclaredField("age");
		//由於反射的是一個私有屬性,所以需要使用setAccessible()方法
		field.setAccessible(true);
		//獲取制定對象中的屬性信息
		Integer i = (Integer)field.get(d);
		System.out.println(i);
		
		/*
		 * 輸出:23
		 */		
	}
}


class Demo{
	public String name;
	public int number;
	private int age=23;
	
	public Demo(String name, int number, int age) {
		super();
		this.name = name;
		this.number = number;
		this.age = age;
	}
	
	public Demo() {
	// TODO 自動生成的構造函數存根
	}	
}

練習:將任意一個對象中的所有String類型的成員變量所對應的字符串內容中的"b"改成"a"

  1. import java.lang.reflect.Field;  
  2. public class Reflectest {  
  3.     public static void main(String[] args) throws Exception {  
  4.         ReflectPoint pt1=new ReflectPoint();  
  5.         changeStringValue(pt1);  
  6.         System.out.println(pt1);  
  7.     }  
  8.   
  9.     private static void changeStringValue(Object obj) throws Exception{  
  10.         Field[] fields=obj.getClass().getFields();//獲取所有的成員變量  
  11.         //遍歷成員變量  
  12.         for(Field field:fields){  
  13.   //比較字節碼用==  
  14.             if(field.getType()==String.class){  
  15.             String oldValue=(String)field.get(obj);//獲取obj的String類型的成員變量  
  16.             String newValue=oldValue.replace('b''a');//將b換成a  
  17.             field.set(obj, newValue);//將此 Field表示的字段設置爲指定的新值  
  18.         }  
  19.         }  
  20.     }  
  21. }  
  22. class ReflectPoint {  
  23.     public String str1="ball";  
  24.     public String str2="basketball";  
  25.     public String str3="itcast";  
  26.     //重寫toString方法  
  27.     public String toString(){  
  28.         return str1+"  "+str2+"  "+str3+"  ";  
  29.     }  
  30. }  


(三)成員方法的反射(Metho類)

Method類代表某個類(接口)或對象中的一個成員方法,這個方法可以是抽象的。JDK API中對Method的描述是這樣的:
“A Method provides information about, and access to, a single method on a class or interface. The reflected method may be a class method or an instance method (including an abstract method).”

package cn.icecino.d1;

import java.lang.reflect.Method;

public class MethoDemo {

	public static void main(String[] args) throws Exception {
		
		String str = "like java";
		
		//獲取String的charAt()方法
		Method McharAt = String.class.getMethod("charAt", int.class);
		
		/*調用該方法*/
		
		//通常方式
		System.out.println(str.charAt(2));//輸出:k		
		
		//反射方式
		System.out.println(McharAt.invoke(str, 3));//輸出:e
		
		/*如果傳給invoke方法的第一個參數不是對象而是null,說明調用的是一個靜態方法*/
	}
}

在這裏,有個特殊的問題需要考慮,那就是在反射方法時,有時方法的參數會是數組類型。在jdk1.4版本及以前是沒有不定長度參數的,所以涉及到無法確定參數個數的時候會使用數組,然後由jvm將數組拆包,數組裏的每個元素都作爲一個參數。而在jdk1.5的新特性中添加了不定長度參數,那麼在我們通過invoke傳入參數的時候,是按照1.4的規則編譯還是1.5的規則編譯呢?——顯然,本着向下兼容的特性,肯定是按照1.4版本的規則編譯,但這樣才反射時就會出現一些問題。


package cn.icecino.d1;

import java.lang.reflect.Method;

public class MethoDemo {

	public static void main(String[] args) throws Exception {		
		Sring className = args[0];
		Method method = Class.forName(className).getMethod("main", String[].class);
		
		method.invoke(null, (Object)new String[]{"aaa","bbb","ccc"});
		/*
		 * 如果按照一般寫法,傳遞參數應該是這樣:
		 * method.invoke(null, new String[]{"aaa","bbb","ccc"});
		 * 但是由於jvm自動拆包,會將String數組當作三個參數傳入,這個main方法中只接受一個String[]不符,編譯器會報錯,所以有兩種解決方案。
		 * 其一:像上述程序中所寫的那樣,在前面加上強制類型轉換,告訴編譯器這是一個整體,不要拆包
		 * 其二:可以這樣寫——method.invoke(null, new Object[]{new String[]{"aaa","bbb","ccc"}});
		 * 	        定義一個Object類型數組,並將String[]整體作爲一個元素放入數組中,編譯器拆包後得到的便是一個String[]類型參數。
		 */
	}
}

class Test{
	
	public static void main(String[] args){
		
		for (String str : args){
			System.out.println(str);
		}
	}
}


輸出結果:
aaa
bbb
ccc


(四)數組的反射

<1>數組與Object類的關係及其反射類型

數組類型及其反射類型名稱
 
讓我們來看一個Demo:
package cn.icecino.d1;

import java.lang.String;
import java.lang.System;
import java.util.Arrays;

public class ReflectArrayDemo {

	public static void main(String[] args) {
		// TODO 自動生成的方法存根
		
		int[] a1 = new  int[]{1,2,3};
		int[] a2 = new  int[4];
		int[][] a3 = new int[3][4];
		String[] a4 = new String[]{"a","b","c"};
		
		System.out.println(a1.getClass() == a2.getClass());
		System.out.println(a1.getClass().getName());
		System.out.println(a1.getClass().getSuperclass().getName());	
		System.out.println(a3.getClass().getSuperclass().getName());	
		System.out.println(a4.getClass().getSuperclass().getName());	
		
		/*
		 * output:
		 * true
		 * [I
		 * class java.lang.Object
		 *
		 */
		
		Object o1 = a1;
		Object o2 = a4;
		/*Object[] o3 = a1; 不能這樣寫,因爲定義引用的時候申明瞭數組內裝的是Object,但是int不是Object,只是一個基本數據類型*/
		Object[] o4 = a3;
		Object[] o5 = a4;
		
		System.out.println(a1);
		System.out.println(a4);
		System.out.println(Arrays.asList(a1));
		System.out.println(Arrays.asList(a4));
		
		/*
		 * ourput:
		 * [I@55e55f  打印出a1的字節碼類型和hashcode值
		 * [Ljava.lang.String;@145c859   打印出a4的字節碼類型和hashcode值
		 * [[I@55e55f] 利用數組工具類取int數組中的數值,失敗
		 * [a, b, c] 利用數組工具類取String數組中的值,成功
		 * 
		 */
	}
}


爲什麼同樣是用Arrays工具類取數組中的值,int數組無法取出,而String卻成功了呢?

讓我們先來看一下API中的方法說明:
首先是JDK1,7中的內容:
public static <T> List<T> asList(T... a)
這裏asList中接收的是一個可變參數類型,但是我們知道,在1.4及其之前的版本是沒有可變參數類型的,所以我們要向下對1.4版本兼容。
在1.4版本的API中,asList接收的是“Object[]  obj”所以當我們使用該方法接受String類型數組時,會用Object數組來調用該方法,這是可以成功的。但是當asList接收的是一個int類型數組時,Object數組中無法接受int元素,所以1.4的方法無法編譯,便使用可變參數類型來進行編譯,將整個int數組看成一個對象,得到的便是他的哈希碼。


<2>數組的反射應用

數組的反射可以用java.lang.reflext.Array類(注意不是Arrays),該類提供的所有方法均爲靜態,我們可以通過它來判斷判斷對應數組的長度,然後遍歷整個數組

public 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(clazz, i));
			}
		}else{
			System.out.println(obj);
		}
	}

結束語:
Java語言反射提供一種動態鏈接程序組件的多功能方法。它允許程序創建和控制任何類的對象,無需提前硬編碼目標類。這些特性使得反射特別適用於創建以非常普通的方式與對象協作的庫。Java reflection 非常有用,它使類和數據結構能按名稱動態檢索相關信息,並允許在運行着的程序中操作這些信息。Java 的這一特性非常強大,並且是其它一些常用語言,如 C、C++、Fortran 或者 Pascal 等都不具備的。

但反射有兩個缺點。第一個是性能問題。用於字段和方法接入時反射要遠慢於直接代碼。性能問題的程度取決於程序中是如何使用反射的。如果它作爲程序運行中相對很少涉及的部分,緩慢的性能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在性能關鍵的應用的核心邏輯中使用時性能問題才變得至關重要。

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