1. 程式人生 > >黑馬程式設計師——Java 反射技術

黑馬程式設計師——Java 反射技術

-----------android培訓java培訓、java學習型技術部落格、期待與您交流!------------

一、概述

 1.理解反射:

  反射就是把java類中的各種成分對映成相應的java類。

 2.反射中常用的類:
  Class類:是反射的基石,由它可得到位元組碼中構造方法、成員變數、成員方法、包等資訊。

  Constructor類:用來描述java類中的構造方法。

  Field類:用來描述java類中的成員變數。

  Method類:用來描述java類中的成員方法。

  Package類:用來描述java類屬於的包。

 3.反射的應用:
  實現框架功能:就是指提供一個基本功能的程式框架,很多具體功能的實現類不需要在框架中體現,而是利用反射技術在開發人員根據不同需要和功能在後續進行載入,通過反射可以給開發人員提供更多的拓展空間和更加靈活的功能實現。

二、Class類

 1.概述:
  java程式中的各個Java類都具有共同的屬性,屬於同一類事物,而在java中用來對這類事物進行描述的類,就是Class類。

 2.作用:
  通過java類的Class類可以獲取相應類中的各種成分資訊,如類中成員變數,成員方法,構造方法,修飾符,包等,這些資訊用相應java類來進行描述,分別為Field、Method、Contructor、Modifiers、Package等。

 3.Class例項物件的三種獲取方式:
  1)類名.class:如Class c=Person.class。
  2)呼叫物件的getClass()方法,示例如下:
   Person p=new Person();
   Class c=p.getClass();
  3)Class.forName("類名"),示例如下:
   Class c=Class.forName("java.lang.String");
   說明:該方法返回類的位元組碼,如果該位元組碼已經載入到虛擬機器,則直接返回該位元組碼。如果該位元組碼未載入到虛擬機器中,則類載入器會將該位元組碼載入到虛擬機器,然後返回該位元組碼。

 4.常見的Class例項物件:
  1)八個基本Class例項物件:基本資料型別包裝類呼叫TYPE欄位獲得的位元組碼與基本型別的位元組碼相同,對應關係如下:
   boolean.class==Boolean.TYPE;
   byte.class==Byte.TYPE;
   short.class==Short.TYPE;
   char.class==Character.TYPE;
   int.class==Integer.TYPE;
   float.class==Float.TYPE;
   long.class==Long.TYPE;
   double.class==Double.TYPE;
  2)無返回值型別的關鍵字void的Class例項物件:
   void.class;
  3)其他的Class例項物件:
   資料型別:int[].class等。
   包裝類型別:Integer.class等。
   字串型別:String.class。

  注:每一種資料型別都有一份位元組碼,而每一份資料型別的位元組碼都是Class類的例項物件。也就是說任何資料型別都有對應的Class類的例項物件。

 5.常用的方法:

  1)獲取表示成員變數的Field物件:

   Field getField(String name):返回一個 Field 物件,它反映此 Class 物件所表示的類或介面的指定公共成員欄位。

   Field[] getFields():返回一個包含某些 Field 物件的陣列,這些物件反映此 Class 物件所表示的類或介面的所有可訪問公共欄位。

   Field getDeclaredField(String name):返回一個 Field 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告欄位。

   Field[] getDeclaredFields():返回 Field 物件的一個數組,這些物件反映此 Class 物件所表示的類或介面所宣告的所有欄位。

  2)獲取表示構造方法的Constructor物件:

   Constructor<T> getConstructor(Class<?>... parameterTypes):返回一個 Constructor 物件,它反映此 Class 物件所表示的類的指定公共構造方法。

   Constructor<?>[] getConstructors():  返回一個包含某些 Constructor 物件的陣列,這些物件反映此 Class 物件所表示的類的所有公共構造方法。

   Constructor<?>[] getDeclaredConstructors():返回 Constructor 物件的一個數組,這些物件反映此 Class 物件表示的類宣告的所有構造方法。

   Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 返回一個 Constructor 物件,該物件反映此 Class 物件所表示的類或介面的指定構造方法。

  3)獲取表示成員方法的Method物件:

   Method[] getMethods():返回一個包含某些 Method 物件的陣列,這些物件反映此 Class 物件所表示的類或介面(包括那些由該類或介面宣告的以及從超類和超介面繼承的那些的類或介面)的公共 member 方法。

   Method getMethod(String name, Class<?>... parameterTypes):返回一個 Method 物件,它反映此 Class 物件所表示的類或介面的指定公共成員方法。

   Method[] getDeclaredMethods():返回 Method 物件的一個數組,這些物件反映此 Class 物件表示的類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。

   Method getDeclaredMethod(String name, Class<?>... parameterTypes): 返回一個 Method 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告方法。

  4)獲取表示此類所屬的包的Package物件:

   Package getPackage():獲取此類的包。

  5)其他:

   static Class<?> forName(String className):返回與帶有給定字串名的類或介面相關聯的 Class 物件。

   String getName():以 String 的形式返回此 Class 物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱。

   Class<? super T> getSuperclass():返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的超類的 Class。

   boolean isArray():判定此 Class 物件是否表示一個數組類。

   boolean isPrimitive():判定指定的 Class 物件是否表示一個基本型別。

   T newInstance():建立此 Class 物件所表示的類的一個新例項。

三、Field類(成員變數的反射)

 1.概述:

  用來描述java類中的成員變數。

 2.用反射獲取Field物件的步驟:

  1)獲取表示指定類或介面的Class類例項物件。

  2)呼叫Class類例項物件中的獲取Filed物件的方法。

  示例:假設存在一個Person類,獲取Person類表示成員變數的Filed物件。

  程式碼:

    Class cla=Class.forName("Person");//方法的類名引數一般為完整的類名或者能讓虛擬機器找到即可

    Field field=cla.getField("指定成員變數名");//獲取Field物件的方法有多個,根據需要選擇

 3.常用的方法:

  Object get(Object obj):返回指定物件上此 Field 表示的欄位的值。

  int getInt(Object obj):獲取 int 型別或另一個通過擴充套件轉換可以轉換為 int 型別的基本型別的靜態或例項欄位的值。

  String getName():返回此 Field 物件表示的欄位的名稱。

  Class<?> getType():返回一個 Class 物件,它標識了此 Field 物件所表示欄位的宣告型別。

  void set(Object obj, Object value): 將指定物件變數上此 Field 物件表示的欄位設定為指定的新值。

  void setInt(Object obj, int i): 將欄位的值設定為指定物件上的一個 int 值。

  void setChar(Object obj, char c): 將欄位的值設定為指定物件上的一個 char 值。

  程式碼示例:

import java.lang.reflect.*;
class FieldDemo 
{
	public static void main(String[] args) throws Exception
	{
		//建立ReflectDemo物件
		ReflectDemo re=new ReflectDemo(3,6);
		
		//返回Class表示的類中的公有欄位y的field物件
		Field fieldY=re.getClass().getField("y");

		//獲取物件re在fieldY表示的欄位的值
		int y=(int)fieldY.get(re);

		System.out.println("y="+y);
		System.out.println(fieldY);

//		Field fieldX=re.getClass().getField("x");//不能用getField方法獲取,因為欄位x不是共有的
		
		//返回Class表示的類中的私有欄位x的field物件
		Field fieldX=re.getClass().getDeclaredField("x");

		//值為true表示反射的物件在使用時不進行Java語言訪問檢查,即可訪問私有的欄位
		fieldX.setAccessible(true);
		
		//獲取物件re在fieldX表示的欄位的值
		int x=(int)fieldX.get(re);

		System.out.println("x="+x);
		System.out.println(fieldX);
	}
}

class ReflectDemo
{
	private int x;
	public int y;

	ReflectDemo(int x,int y)
	{
		this.x=x;
		this.y=y;
	}
}
   程式執行後的結果如下截圖所示:


 4.程式碼練習:

  題目:將任意一個物件中的所有的String型別的成員變數所對應的字串內容中的‘b’改成‘a’。

import java.lang.reflect.*;
class FieldTest 
{
	public static void main(String[] args) throws Exception
	{
		//建立ReflectDemo物件
		ReflectDemo re=new ReflectDemo(3,6);

		System.out.println("改變前的字串:"+re);
		changeStr(re);
		System.out.println("改變後的字串:"+re);
	}
	

	public static void changeStr(Object obj)throws Exception
	{
		//返回obj物件的Class類中的所有欄位的field物件陣列
		Field[] fields=obj.getClass().getFields();

		//對field物件陣列進行遍歷
		for(Field field:fields)
		{
			//判斷field表示欄位的資料型別是否為String型別
			if (field.getType()==String.class)
			{
				String str=(String)field.get(obj);//獲取field表示欄位的值,需進行型別強制轉換
				String strNew=str.replace('b','a');//將字串中的字元‘b’替換為‘a'
				field.set(obj,strNew);//將field在obj物件表示欄位的值設定為strNew
			}
		}
	}
}

class ReflectDemo
{
	private int x;
	public int y;
	public String str1="ball";
	public String str2="basketball";
	public String str3="base";

	ReflectDemo(int x,int y)
	{
		this.x=x;
		this.y=y;
	}

	public String toString()
	{
		return str1+":"+str2+":"+str3;
	}
}
  程式執行後的結果如下截圖所示:



四、Constructor類(建構函式的反射)

 1.概述:

  Constructor類用來描述java類中的建構函式。

 2.用反射獲取Constructor物件的步驟:

  1)獲取表示指定類或介面的Class類例項物件。

  2)呼叫Class類示例物件的獲取Constructor物件的方法。

示例:假設存在一個Person類,獲取Person類表示成員變數的Constructor物件。

  程式碼:

    Person p=new Person();

    Class cla=p.class.getClass();//獲取表示Person類的Class類例項物件

    Constructor con=cla.getConstructor("引數型別的位元組碼");//表示獲取指定建構函式的Constructor物件。應傳入建構函式對應引數型別的位元組碼檔案物件,且傳入的個數應與建構函式的引數個數一致

 3.常用的方法:

  String getName():以字串形式返回此構造方法的名稱。

  T newInstance(Object... initargs): 使用此 Constructor 物件表示的構造方法來建立該構造方法的宣告類的新例項,並用指定的初始化引數初始化該例項。

  Class<T> getDeclaringClass():返回 Class 物件,該物件表示宣告由此 Constructor 物件表示的構造方法的類。

  問題思考:Class類中newInstance()方法與Conctructor類中newInstance(Object... initargs)方法有什麼區別?

   答:Class類中newInstance()方法只能建立由Class類例項物件表示的類中的空引數建構函式的物件,而Constructor類中newInstance(Object... initargs)方法可以建立任意指定引數型別的建構函式物件,且每呼叫一次就構造一個物件。

 4.程式碼示例:

import java.lang.reflect.*;
class ConstructorDemo 
{
	public static void main(String[] args) throws Exception 
	{
		//獲取String位元組碼中引數型別為StringBuffer的Constructor物件
		Constructor<String> con=String.class.getConstructor(StringBuffer.class);

		//呼叫Constructor類中newInstance方法建立物件,建立時需要根據引數型別傳入實際引數
		String str=con.newInstance(new StringBuffer("abc"));

		System.out.println(str);

	}
}
  程式執行後的結果如下截圖所示:


五、Method類(成員方法的反射)

 1.概述:

  Method類用來描述java類中的成員方法。

2.用反射獲取Method物件的步驟:

  1)獲取表示指定類或介面的Class類例項物件。

  2)呼叫Class類示例物件的獲取Method物件的方法。

示例:假設存在一個Person類,獲取Person類表示成員方法的Method物件。

  程式碼:

    Person p=new Person();

    Class cla=p.class.getClass();//獲取表示Person類的Class類例項物件

    Method me=cla.getMethod(String name,"引數型別的位元組碼");//表示獲取指定成員方法的Method物件。應傳入成員方法對應引數型別的位元組碼檔案物件和方法名,且傳入的個數應與成員方法的引數個數一致

 3.常用的方法:

  Object invoke(Object obj, Object... args):對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。

Class<?> getDeclaringClass():返回表示宣告由此 Method 物件表示的方法的類或介面的 Class 物件。

  String getName():以 String 形式返回此 Method 物件表示的方法名稱。

  其中,invoke(Object obj, Object... args)方法對於Method物件表示為靜態方法時,呼叫格式為:

   Object invoke(null, Object... args):需要傳入指定物件,因此傳入的指定物件為null。

  程式碼示例:

import java.lang.reflect.*;
class MethodDemo
{
	public static void main(String[] args) throws Exception 
	{
		String str="abc";

		//獲取String的位元組碼中的Method物件,該Method物件表示方法名為charAt,引數型別為int的方法
		Method me=String.class.getMethod("charAt",int.class);

		//呼叫Method物件中的invoke方法,需傳入呼叫物件和實際引數
		//如果Method表示的方法為靜態,則呼叫時,傳入的呼叫物件為null。
		char ch=(char)me.invoke(str,1);

		System.out.println("ch="+ch);
	}
}
  程式執行後的結果如下截圖所示:


 4.程式碼練習:

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

import java.lang.reflect.*;
class MethodTest 
{
	public static void main(String[] args) throws Exception 
	{
		//獲取執行java命令傳入的引數,該引數為類名的字串表示
		String className=args[0];

		//獲取類名的Class物件,並獲取Class類中表示main方法的Method物件
		Method mainMethod=Class.forName(className).getMethod("main",String[].class);
		Object obj=new String[]{"ava"};

		//呼叫invoke方法,並傳入main函式中的實際引數,對於靜態方法呼叫物件為null
		//執行指定類中的main方法
		mainMethod.invoke(null,obj);

		//注意可變引數的引數傳遞規則,對於引用型別的陣列需強制轉換成Object型別
//		mainMethod.invoke(null,(Object)new String[]{"java"});
//		mainMethod.invoke(null,new Object[]{new String[]{"java"}};
	}

}
  程式執行後的結果如下截圖所示:(執行FieldDemo類中的main方法)


  注:此示例用eclipse執行時,需要在Run As——>RunConfigurations——>Arguments——>Program arguments中新增要執行的類名。 

六、陣列的反射

 1.概述:

  具有相同的元素型別和維度的陣列都屬於同一型別陣列,該同一型別的陣列經過反射得到的Class類例項物件都是同一份位元組碼。

  示例:

   int[] a1=new int[3];//對應的位元組碼為int[].class。

   int[] a2=new int[4];//對應的位元組碼為int[].class。

   int[][] a3=new int[2][3];//對應的位元組碼為int[][].class。

   String[] a4=new String[3];//對應的位元組碼未String[].class。

  說明:a1和a2有相同的元素型別(即int)和維度(即一維陣列),因此屬於同一型別陣列,具有同一份位元組碼,即int[].class。同理a1和a3、a1和a4等都不屬於同一型別陣列,因此都不是同一份位元組碼。

 2.獲取指定陣列型別的Class例項物件:

  1)建立指定陣列型別的物件。

  2)呼叫物件的getClass()方法獲取Class物件。

  示例:

   int[] a={3,4,4};//建立int型別一維陣列

   Class cls=a.getClass();//獲取該型別陣列的Class物件

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

  static Object get(Object array, int index):返回指定陣列物件中索引元件的值。

  static void set(Object array, int index, Object value):將指定陣列物件中索引元件的值設定為指定的新值。

  static int getLength(Object array):以 int 形式返回指定陣列物件的長度。  

 4.程式碼示例:

import java.lang.reflect.*;
class ArrayReflect 
{
	public static void main(String[] args) throws Exception
	{
		String str="java";
		int[] in={3,43,35};
		printObject(str);//列印字串
		printObject(in);//列印陣列
	}

	public static void printObject(Object obj)throws Exception
	{
		//獲取obj物件的Class例項物件
		Class cla=obj.getClass();

		//判斷cla型別是否為陣列型別
		if (cla.isArray())
		{
			//獲取陣列的長度
			int len=Array.getLength(obj);

			//遍歷列印陣列中的元素
			for (int x=0;x<len ;x++ )
			{
				System.out.println(Array.get(obj,x));
			}
		}
		else
			System.out.println(obj);//如果不是陣列型別,則直接列印
	}
}
  程式執行後的結果如下截圖所示:



七、反射的應用案例——簡單框架功能的實現

思路:

  1.新建一個配置檔案,並將需要反射的類,以鍵值對的形式儲存到配置檔案中。

  2.建立Properties集合,並用IO流將配置檔案的資訊載入到集合中。

  3.獲取需要被反射的類,並使用反射技術獲取Class類例項物件。

  4.建立Class類表示的類的例項物件。

 程式碼:

<pre name="code" class="java">import java.util.*;
import java.io.*;
class ReflectUseDemo
{
	public static void main(String[] args) throws Exception
	{
		//建立字元讀取流物件,並關聯儲存需要被反射的類的配置檔案
		FileReader fr=new FileReader("e:\\heima\\measure\\config.properties");
		Properties prop=new Properties();//建立Properties集合

		prop.load(fr);//將配置檔案的資訊載入到集合
		fr.close();//關閉資源
		String name=prop.getProperty("className");

		Class<?> cla=Class.forName(name);//獲取表示指定類的Class物件例項

		//建立Class物件表示的類的例項物件,並強制型別轉換為Collection
		Collection coll=(Collection)cla.newInstance();

		//建立ReflectDemo物件
		ReflectDemo ref1=new ReflectDemo(3,5);
		ReflectDemo ref2=new ReflectDemo(4,3);
		ReflectDemo ref3=new ReflectDemo(3,5);
		
		//向Collection集合新增元素
		coll.add(ref1);
		coll.add(ref2);
		coll.add(ref3);
		coll.add(ref1);

		//ref1.y=7;  
        //coll.remove(ref1); //對於HashSet集合,ref1的值在新增後進行了修改,此時刪除無效,因為雜湊值發生了變化 

		System.out.println(coll.size());
	}
}
class ReflectDemo
{
	private int x;
	public int y;
	
	ReflectDemo(int x,int y)
	{
		this.x=x;
		this.y=y;
	}

}
  1)當配置檔案資訊為:


  執行的結果如下截圖所示:


  2)當配置檔案資訊為:


執行的結果如下截圖所示: