java基礎加強_反射


反射的基石:Class類

Class類概述:
Java程序中的各個Java類屬於同一類事物,描述這類事物的Java類名就是Class。Class類中提供了大量操作字節碼文件的方法。
Class類代表Java類,它的各個實例對象又分別對應對應各個類在內存中的字節碼,
例如,Person類的字節碼,ArrayList類的字節碼,等等。
一個類被類加載器加載到內存中,佔用一片存儲空間,這個空間裏面的內容就是類的字節碼,不同的類的字節碼是不同的,所以它們在內存中的內容是不同的,這一個個的空間可分別用一個個的對象來表示,這些對象顯然具有相同的類型,這個類型就是Class類型

我的理解:其實Class類就是用來操作.class文件的,將.class文件封裝成對象,使用Class類中的方法進行操作


獲取字節碼對應的實例對象的三種方法:
1.類名.class,例如,System.class  通常用於獲取已知類或者類型的.class文件
2.對象.getClass(),例如,new Date().getClass()    通常用於獲取自定義對象的.class文件
|--java.lang.Object
|--class<?> getClass()  返回object的運行時類
3.Class.forName("類名"),例如,Class.forName("java.util.Date")  通常用於獲取API文檔中的.class文件
|--java.lang.Class
|--static Class<?> forName(String className)  返回與帶有給定字符串名的類或接口相關聯的 Class 對象。


九個預定義Class實例對象:
分別是:
8個基本數據 類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也表示爲 Class 對象。 
他們的內部都封裝了一個TYPE字段,這九個類的包裝類對象調用TYPE字段後都會返回一個Class類
static Class<Byte> TYPE 
          表示基本類型 byte 的 Class 實例。
 

代碼示例:
package com.learn;

public class ReflectionDemo
{

	/**
	 * @param args
	 * @throws ClassNotFoundException 
	 */
	public static void main(String[] args) throws ClassNotFoundException
	{
		// TODO Auto-generated method stub
		//獲取字節碼文件的第一種方法
		Class cls1 = Thread.class;
		//獲取字節碼文件的第二種方法
		Class cls2 = new Thread().getClass();
		//獲取字節碼文件的第三種方法
		Class cls3 = Class.forName("java.lang.Thread");
		//判斷他們是否是同一個字節碼文件
		System.out.println(cls1 == cls2);
		System.out.println(cls2 ==cls1);
		//判斷該字節碼文件是否是基本數據類型的字節碼文件
		System.out.println(long.class.isPrimitive());
		//判斷是否是同一個字節碼文件
		System.out.println(int.class == Integer.TYPE);
		//返回值爲false
		System.out.println(int.class == Integer.class);
		//void是9個預定義實例對象中的其中一個
		System.out.println(void.class.isPrimitive());
		//判斷字節碼文件是否爲數組類型
		System.out.println(int[].class.isArray());
	}

}

總結:
只要是在源程序中出現的類型,都有各自的Class實例對象,例如,int[],void…

反射

概念:
反射就是把Java類中的各種成分映射成相應的java類。


概述:

一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示
表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Constructor、Package等等。 

繼承體系
java.lang.reflect
|--java.lang.reflect.AccessibleObject
|--java.lang.reflect.Field
|--java.lang.reflect.Method
|--java.lang.reflect.Constructor


一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象,對這些實例對象的操作是學習重點

java.lang.reflect.Contructor 用於操作字節碼文件中構造函數的類

得到某個類所有的構造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一個構造方法:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//獲得方法時要用到類型
創建實例對象:
1.java.lang.reflect.Constructor中的 newInstance()方法
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
//調用獲得的方法時要用到上面相同類型的實例對象

2.java.lang.Class中的newInstance()方法
Class.newInstance()方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
該方法內部先得到默認的構造方法,然後用該構造方法創建實例對象。
該方法內部的具體代碼是怎樣寫的呢?用到了緩存機制來保存默認構造方法的實例對象。

代碼示例:
package com.learn;

import java.lang.reflect.Constructor;

public class ReflectDemo2
{

	/**
	 * @param args
	 * @throws Exception 
	 * @throws SecurityException 
	 */
	public static void main(String[] args) throws SecurityException, Exception
	{
		// TODO Auto-generated method stub
		//遍歷Thread類中的所有構造函數
		//獲取類中的所有構造方法,存入集合中
		Constructor[] constructors = Class.forName("java.lang.Thread").getConstructors();
		for(Constructor constructor : constructors)
		{
			System.out.println(constructor);
		}
		//獲取一個指定參數類型的構造函數
		//方法一:costructor.newInstance(Type ... args);
		Constructor constructor1 = Class.forName("java.lang.String").getConstructor();
		String str = (String)constructor1.newInstance();
		System.out.println(str.length());
		//方法二:Class.newInstance();
		String str1 = (String)Class.forName("java.lang.String").newInstance();
		System.out.println(str.length());
	}

}

總結:
異常會發生在兩個狀態下
1.編譯時,JVM編譯源文件時,檢查等號左邊的變量定義
2.運行時,執行文件時,檢查等號右邊的部分

java.lang.reflect.Field用於操作字節碼文件中的定義的變量的類

Field類的特點:
當我們獲取指定對象中的Field類時,如果想操作其中的一個Field或者一類Field,那麼要指定這個Field所屬的對象

代碼示例:
package com.learn;

import java.lang.reflect.Field;

public class ReflectDemo3
{

	/**
	 * @param args
	 * @throws Exception 
	 * 
	 */
	public static void main(String[] args) throws Exception
	{
		Demo3 d = new Demo3(6,5);
		//獲取私有的成員變量
		Field fieldX = Demo3.class.getDeclaredField("x");
		//暴力反射,強制獲取私有的成員變量
		fieldX.setAccessible(true);
		System.out.println(fieldX.get(d));
		Field fieldY = Demo3.class.getField("y");
		System.out.println(fieldX.get(d));
	}

}

public class Demo3
{
	private int x = 9;
	public int y = 9;
	public Demo3(int x, int y)
	{
		super();
		this.x = x;
		this.y = y;
	}
	
}

總結:
getField(String str)方法只可以獲取字節碼中權限修飾符爲public的field;

暴力反射:
程序中隱藏的field,通常是不想讓別人使用的,纔會如此定義。此時我們如果想獲取隱藏的field,需使用getDeclaredField()方法獲取。然後對此field進行暴力反射:setAccessible(true),即可使用get方法獲取此field的值

練習:將字符串中的字符‘b’換成'a'
package com.learn;

import java.lang.reflect.Field;

public class FieldTest
{
	/**
	 * @param args
	 * @param Demo 
	 */
	public static void main(String[] args) throws Exception
	{

		// 創建Demo1類的對象
		Demo1 d = new Demo1("abc","aabb","cctv");
		//將對象傳入替換字符的方法中
		replaceChar(d);
		System.out.println(d);
	}
	
	private static void replaceChar(Object obj) throws Exception
	{
		//獲取對象中所有的Field 
		Field[] fields = obj.getClass().getFields();
		//遍歷集合中的field
		for(Field field : fields)
		{
			//判斷該field是否是String類型
			if(field.getType() == String.class)
			{
				//獲取field在指定對象中所對應的字段
				String oldstr = (String) field.get(obj);
				//替換字段中的字符
				String newstr = oldstr.replace('b','a');
				//將新的字段添加進對象對應的字段中
				field.set(obj, newstr);
			}
		}
		
	}

}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

程序中用到的方法:
java.lang.Class
|--Field getField(String name):獲取字節碼文件中包含指定未私有變量的Field對象(只能獲取未被隱藏的field)
|--Field[] getFields():獲取字節碼文件中所有的Field對象
|--Field getDeclaredField:獲取字節碼文件中變量的Field對象(無論是否隱藏都可以獲取,當獲取的field是隱藏成員變量時,需進行暴力反射)
|--Field[] getDeclaredFields:獲取字節碼文件中的所有變量
java.lang.reflect.Field
|--Object get(Object obj):返回指定對象上字段的值
|--Class getType():返回Field對象中field的數據類型
|--void set(Object obj, Object value) :將指定對象變量上此 Field 對象表示的字段設置爲指定的新值。           
java.lang.reflect
|--void AccessibleObject(boolean flag):對隱藏的field進行暴力反射

java.lang.reflect.Method 用於操作字節碼文件中的定義的方法的類

獲取類中的方法:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
調用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1)); 
注意:
如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明該Method對象對應的是一個靜態方法!

代碼示例:
package com.learn;

import java.lang.reflect.Method;

public class ReflctMethod
{
	public static void main(String[] args) throws Exception
	{
		Demo1 d = new Demo1("qwer","tyui","asdf");
		Method toString = Demo1.class.getMethod("toString");//空參數的方法,只要傳入方法名,沒參數就不用傳入參數類型
		System.out.println(toString.invoke(d));//第一個傳入的Object是調用方法的對象,第二個傳入的是具體的參數,沒有就不傳
	}
}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

總結:
當獲取的方法是一個空參數的方法時,getMethod(String name,Class DataType)方法的第二個參數不用傳入,在調用獲取的方法時,
invoke(Object obj,Object... args)第二個參數不用傳值,也不能寫爲null。

jdk1.4和jdk1.5的invoke方法的區別:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的語法,需要將一個數組作爲參數傳遞給invoke方法時,數組中的每個元素分別對應被調用方法中的一個參數,所以,調用charAt方法的代碼也可以用Jdk1.4改寫爲 charAt.invoke(“str”, new Object[]{1})形式。

jdk 1.4版

package com.learn;

import java.lang.reflect.Method;

public class ReflctMethod
{
	public static void main(String[] args) throws Exception
	{
		Demo1 d = new Demo1("qwer","tyui","asdf");
		//如果有參數,那麼此時的參數類型的字節碼文件是Object[].class
		Method toString = Demo1.class.getMethod("toString");
		//空參數的方法,所以數組中的參數列表中沒有元素
		System.out.println(toString.invoke(d,new Object[]{}));
	}
}

public class Demo1
{
		public String str1 ;
		public String str2 ;
		public String str3 ;
		public Demo1(String str1, String str2, String str3)
		{
			super();
			this.str1 = str1;
			this.str2 = str2;
			this.str3 = str3;
		}
		public String toString()
		{
			return str1+"::"+str2+"::"+str3;
		}
}

用反射方式執行某個類中的main方法
程序目標:寫一個程序,這個程序能夠根據用戶提供的類名,去執行該類中的main方法。
回顧主函數:
main方法的格式是固定的,public static void main(String[] args)  注意:JDK1.5後出現的可變參數:String[]... args
從主函數格式中我們知道
函數名:main
參數類型:String[]
參數個數:0
長度:0
package com.learn;

import java.lang.reflect.Method;

public class ReflectMain
{

	public static void main(String[] args) throws Exception
	{

		String str = args[0];
		Method mainMethod = Class.forName(str).getMethod("main", String[].class);
		mainMethod.invoke(null, (Object)new String[]{});
	}
}
運行以上程序發現,將會報角標越界異常
這是因爲我們沒有向數組中傳入參數

解決方法:
Run As--> 點擊Run Configurations -->點擊Arguments選項卡-->輸入需要執行的main方法所在類全名即可
此時String[]數組的長度變爲1,args[0] = 傳入的類全名
以上程序其實就是:Method mainMethod = Class.forName(類全名).getMethod("main",String[].class);

傳入參數後,還會報非法參數異常(IllegalArgumentsException)
原因:
啓動Java程序的main方法的參數是一個字符串數組,即public static void main(String[] args),通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?
按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac會到底按照哪種語法進行處理呢?
jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進行處理,即把數組打散成爲若干個單獨的參數。所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”}),javac只把它當作jdk1.4的語法進行理解,而不把它當作jdk1.5的語法解釋,因此會出現參數類型不對的問題。
解決辦法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,編譯器會作特殊處理,編譯時不把參數當作數組看待,也就不會將數組打散成若干個參數了

數組的反射
數組的class實例對象的特點:
1.具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。
2.代表數組的Class實例對象的getSuperClass()方法返回的父類爲Object類對應的Class。

基本數據類型數組和非基本數據類型的區別
基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;
非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。

Arrays.asList()方法處理int[]和String[]時的差異。
示例:
int[] arr = new int[]{1,2,3};
String[] arr1 = new String[]{"a","b","c"};
Object obj = Arrays.asList(arr);
Object obj1 = Arrays.asList(arr1);
System.out.println(arr);
System.out.println(arr1);

輸出結果:
[[I@b6e39f]   因爲基本類型的一維數組是Object類型,所以asList()方法將arr數組當做一個對象處理
[a, b, c]          而String類型的數組即可作爲Object類型也可當做Object[]類型使用,String內部的元素都是對象,輸出的是對象的集合

Array工具類用於完成對數組的反射操作。
package com.learn;

import java.lang.reflect.Array;

public class ReflectArray
{

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		String[] str = new String[]{"a","b","c"}; 
		PrintObject(str);
	}

	private static void PrintObject(Object obj)
	{
		// 有兩種情況,一種是obj是數組類型,一種就是普通對象
		Class cls = obj.getClass();
		if(cls.isArray())
		{
			for(int x = 0;x<Array.getLength(obj);x++)
			{
				System.out.println(Array.get(obj, x));
			}
		}else
		{
			System.out.println(obj);
		}
		
	}

}

用到的的方法
java.lang.Class
|--isArray():判斷Class對象是否是數組類型
java.lang.reflect.Array
|--static int getLength(Object Array):返回指定數組的長度
|--static object get(Object array,int index):返回指定數組角標爲的值

ArrayList和HashCode的比較及HashCode分析

實例分析:
ReflectPoint類
package com.learn;

public class ReflectPoint
{
	private int x;
	private int y;
	public ReflectPoint(int x, int y)
	{
		super();
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
	
}
測試代碼:
package com.learn;

import java.util.ArrayList;
import java.util.Collection;

public class ReflectTest
{

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		Collection<ReflectPoint> list = new ArrayList<ReflectPoint>();
		ReflectPoint rp = new ReflectPoint(1,2);
		ReflectPoint rp1 = new ReflectPoint(2,3);
		ReflectPoint rp2 = new ReflectPoint(3,4);
		ReflectPoint rp3 = new ReflectPoint(3,4);
		list.add(rp);
		list.add(rp1);
		list.add(rp2);
		list.add(rp3);
		System.out.println(list.size());
	}

}

測試代碼中創建的是ArrayList集合,該集合是有序可重複的,所以該集合中無論存儲多少元素,無論是否相同都可以存入,此時的size()爲4;
如果將ArrayList集合改爲HashSet集合,因爲HashSet集合存取時無序且不可重複,他們通過hashCode()和equals()方法來保證元素的唯一性,先比較對象的hash值,然後用equals()方法判斷對象中的內容。最後的結果爲3

注意:
當一個對象被存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算的字段了,否則,對象修改後的哈希值與最初存儲進HashSet集合中時的哈希值也會改變,在這種情況下,即使在contains方法使用該對象的當前引用作爲的參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從HashSet集合中單獨刪除當前對象,從而造成內存泄露。ArrayList集合沒有這方面的問題

框架的概念及用反射技術開發框架的原理

框架與框架要解決的核心問題

我做房子賣給用戶住,由用戶自己安裝門窗和空調,我做的房子就是框架,用戶需要使用我的框架,把門窗插入進我提供的框架中。框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。


框架要解決的核心問題
我在寫框架(房子)時,你這個用戶可能還在上小學,還不會寫程序呢?我寫的框架程序怎樣能調用到你以後寫的類(門窗)呢?因爲在寫才程序時無法知道要被調用的類名,所以,在程序中無法直接new某個類的實例對象了,而要用反射方式來做。

模擬框架示例:
配置文件config.properties中的內容:
classname = java.util.ArrayList

示例代碼:
package com.learn;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

public class ReflectTest2
{

	/**
	 * @param args
	 * 創建一個加載任意集合的框架
	 */
	public static void main(String[] args) throws Exception
	{
		//獲取文件
		InputStream is = new FileInputStream("config.properties");
		//創建空列表對象
		Properties p = new Properties();
		//加載配置文件
		p.load(is);
		//關閉流資源
		is.close();
		//獲取鍵對應的值
		String classname = p.getProperty("classname");
		//使用反射創建配置文件中要建立的對象
		Collection collection = (Collection)Class.forName(classname).newInstance();
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		collection.add(new ReflectPoint(1,2));
		System.out.println(collection.size());
	}

}

示例代碼就是一個小型的框架,我們只需要改變config.properties文件中classname的值,即可使用這個框架來爲我們工作,非常的方便。上面的程序我們可以將任意集合類作爲classname的值

加載配置文件的三種方式:
1,採用類加載器進行加載,使用相對路徑的方式 
InputStream is=ReflectTest.class.getClassLoader(). getResourceAsStream("com/itheima/day01/config.properties");

2,利用Class方式進行加載,使用相對路徑的方式 
InputStream is = ReflectTest.class.getResourceAsStream("config.properties"); 

3,利用Class方式進行加載,使用絕對路徑的方式
InputStream is = ReflectTest.class.getResourceAsStream("com/itheima/day01/config.properties");






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