java基礎增強---反射
一、反射的基石–Class類
1、Java程式中的各個java類屬於同一事物,描述這些類事物的java類名就是Class。
2、對比提問:從多的人用一個什麼類表示?從多的java類用一個什麼類表示?
人類—Person
java類–Class
注意這裡Class是大寫的,不是關鍵字class。
3、對比提問:
1)Person類代表人,它的例項物件就是張三,李四這樣一個個具體的人,
2)Class類代表java類,它的各個實現物件又分別對應什麼呢?
對應各個類在記憶體中的位元組碼,例如Person類的位元組碼,ArrayList類的位元組碼,等待。
一個類被類載入器載入到記憶體中,佔用一片儲存空間,這個空間裡面的內容就是類的位元組碼,
不同的類的位元組碼是不同的,所以它們在記憶體中的內容是不同的,這一個個的空間分別用一個個的物件來表示,這些物件顯然具有相同的型別,這個型別是什麼呢?
4、得到位元組碼物件的三種方法
1)類名.class,例如System.class
2)物件.getClass(),例如new Date().getClass()
3)Class.forName(“完整類名”),例如 Class.forName(“java.util.Data”);
反射時主要用第三種。它是靜態方法。
面試題:Class.forName()的的作用是什麼?
獲取一個類的位元組碼物件,如果該類的位元組碼已經在記憶體中存在,就可以直接呼叫,
如果還沒有存在,就呼叫類載入器進行載入,然後獲取該類的位元組碼物件。
5、九個預定義的Class物件:
參看 Class.isPrimitive方法的幫助文件
八個基本型別和void,分別是:boolean、byte、char、short、int、long、float、double和void。
int.class == Integer.TYPE
6、陣列型別的Class例項物件用的方法是:
Class.isArray()
7、總之,只要是在源程式中出現的型別,都有各自的Class例項物件,
例如int[],void。
例:獲取String類的位元組碼的三種方法
class Demo1{
public static void main(String[] args) throws Exception{
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); //true
System.out.println(cls1 == cls3); //true
//是否是原始型別
System.out.println(cls1.isPrimitive()); //false
System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class); //false
System.out.println(int.class == Integer.TYPE); //true
}
}
二、反射的概念
反射,就是把java類中的各種成分對映成相應的java類。
也可以理解成反射就是程式自己能夠檢測自身資訊,就像人會通過鏡子來檢視自己的身體。
例如,一個java類中用一個Class類的物件來表示,一個類中的組成部分:成員變數,方法,構造方法,包等等資訊也是用一個個java類來表示。
就像騎車是一個類,騎車中的發動機,變速箱等等也是一個個類,表示java類的Class類顯然要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的例項物件來表示,他們是Field、Method、Contructor、Package等等。
一個類中的每個成員都可以用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可以得到這些例項物件後,得到這些例項物件胡有什麼用?怎麼使用?這這是學習和應用反射的要點
例:
class Demo2{
public static void sop(Object obj){System.out.println(obj);}
public static void main(String[] args) throws Exception {
String s1 = "1234";
Class c1 = s1.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
sop(c1==c2); //c1與c2是否是同一個物件true
sop(c1==c3); //c1與c3是否是同一個物件true
sop(String.class.isPrimitive());//String是否是基本型別false
sop(int.class.isPrimitive()); //int是否是基本型別true
sop(int.class==Integer.class); //int與Integer的位元組碼是否是同一個物件false
sop(int.class==Integer.TYPE); //int與Integer.TYPE的位元組碼是否是同一個物件true
sop(int[].class.isPrimitive()); //int[]是否是基本型別false
sop(int[].class.isArray()); //int[]是否是陣列型別true
}
}
三、構造方法的反射
Constructor 類
1、Constructor類代表某個類中的一個構造方法。
2、得到某個類所有的構造方法:
例子:
Constructor constructor[] =
Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
3、建立例項物件:
通常方式:String str = new String(new StringBuffer(“abc”));
反射方式:String str = (String)constructor.newInstance(new StringBuffer(“abc”));
呼叫獲得的方法時要用到上面相同型別的例項物件。
4、Class.newInstance()方法:
例子:String obj = (String)Class.forName(“java.lang.String”).newInstance();
該方法內部先得到預設的構造方法,然後用該構造方法建立例項物件。
該方法內部的具體程式碼是怎麼樣寫的呢?用到了緩衝機制來儲存預設構造方法的例項物件。
一個類有多個構造方法,用什麼方式可以區分清楚想要得到其中的哪個方法呢?根據引數的個數和型別,
例如,Class.getMethod(name.Class…args)中的args引數就代表索要獲取的那個方法的各個引數的型別的列表。
重點:引數型別用什麼方式表示?用Class例項物件。
例如:
int.class,(int []).class
int [] ints = new int[0];
ints.getClass();
需求:反射String類的 String(StringBuffer buffer) 這個構造方法
/**
*思路:
*1、通過String類的位元組碼物件呼叫getConstructor方法獲取這個類的構造方法。
*2、具體要獲得哪個構造方法,就給這個方法傳遞一個引數型別。這個引數型別是Class物件的一個數組。
*3、用第一步返回的一個Constructor物件呼叫newInstance方法,建立StringBuffer的例項物件。
*/
import java.lang.reflect.Constructor;
public class D19_ReflectConstructor {
public static void main(String[] args) throws Exception {
Constructor<String> c = String.class.getConstructor(StringBuffer.class);
String s = (String)c.newInstance(new StringBuffer("abc"));
System.out.println(s);
}
}
/*
結果
abc
*/
四、成員變數的反射
Field類
Field 提供有關類或介面的單個欄位的資訊,以及對它的動態訪問許可權。
反射的欄位可能是一個類(靜態)欄位或例項欄位。
部分方法:
Object get(Object obj) 返回指定物件上此 Field 表示的欄位的值。
void set(Object obj, Object value) 將指定物件變數上此 Field 物件表示的欄位設定為指定的新值。
String getName() 返回此 Field 物件表示的欄位的名稱。
Class
import java.lang.reflect.Field;
//定義一個用來反射的類
class ClassPoint {
public int x;
private int y;
public ClassPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
public class D20_ReflectField {
public static void main(String[] args) throws Exception {
ReflectField();
}
//對ClassPoint繼承成員變數反射
public static void ReflectField() throws Exception {
//建立該類的例項物件
ClassPoint cp = new ClassPoint(3,5);
//反射變數 x
//獲取物件的位元組碼,根據位元組碼獲得x對應的成員欄位
Field fieldX = cp.getClass().getField("x");
//通過欄位fieldX獲取它對應的值
System.out.println("變數x的值:"+fieldX.get(cp));
//反射私有成員變數y--暴力反射
//getDeclaredField()獲取宣告的欄位,不管是被什麼修飾的,但只要不是繼承的。
Field fieldY = cp.getClass().getDeclaredField("y");
fieldY.setAccessible(true);
//setAccessible()取消的欄位的許可權檢查,false表示要檢查
fieldY.setAccessible(true);
System.out.println("變數y型別:"+fieldY.getType());//獲取該欄位對應的變數型別
System.out.println("變數y名稱:"+fieldY.getName());//獲取該欄位對應的變數名
System.out.println("變數y的值:"+fieldY.get(cp)); //獲取該欄位對應的變數的值
}
}
/*
結果
變數x的值:3
變數y型別:int
變數y名稱:y
變數y的值:5
*/
五、成員變數反射的綜合例項
需求:反射某個類中所有的String型別的成員變數,並將該變數的值中指定的字元替換成新的字元。
分析:其實就是通過反射用用新字串替換所有String型別的原來的字串
思想:
1、定義StringDemo類,類裡定義多種型別的成員變數,有的被public修飾。
2、另外定義一個類實現對StringDemo類的反射和其他操作,該類首先建立StringDemo的例項物件。
3、用getFields方法返回一個Field陣列,用getFields方法是限制了只能反射被public修飾的成員欄位。
4、變數該Field陣列,取出每個欄位,然後用該欄位獲取它的宣告型別的Class物件與String.class比較。
5、如果是同一份位元組碼,就用set方法把該欄位的值用新的值替換掉。
例項:通過反射把String型別的變數的值替換成新值
import java.lang.reflect.Field;
class StringDemo {
public int x = 0;
public String str1 = "wuguangxin";
public String str2 = "howareyou";
String str3 = "jiewin";
}
public class D21_ReflectFieldTest {
public static void main(String[] args) throws Exception {
changeStringValue();
}
public static void changeStringValue() throws Exception{
//建立物件
StringDemo str = new StringDemo();
//用getFields()方法返回一個所有宣告的公共欄位陣列
Field[] fields = str.getClass().getFields();
//變數該陣列,獲取每一個欄位進行
System.out.println("把u替換成*");
for (Field field : fields){
//如果該欄位的位元組碼和String的位元組碼相同,說明是同一份位元組碼。
//位元組碼最適合用==比較,不建議用equals
if(field.getType() == String.class){
//獲取原來的欄位值
String oldValue = (String)field.get(str);
//把原來的值的指定字元替換成指定字元
String newValue = oldValue.replace("u", "*");
//設定該欄位對應的變數的值為新的值
field.set(str, newValue);
System.out.println(oldValue+" --> "+newValue);//測試
}
}
}
}
/*
結果:
把u替換成*
wuguangxin --> w*g*angxin
howareyou --> howareyo*
*/
六、成員方法的反射
Method 類
Method 提供關於類或介面上單獨某個方法(以及如何訪問該方法)的資訊。
所反映的方法可能是類方法或例項方法(包括抽象方法)。
方法:
String getName() 返回此 Method 物件表示的方法名稱。
boolean isVarArgs() 如果將此方法宣告為帶有可變數量的引數,則返回 true;否則,返回 false。
boolean isSynthetic() 如果此方法為複合方法,則返回 true;否則,返回 false。
String toGenericString() 返回描述此 Method 的字串,包括型別引數。
Object invoke(Object obj, Object… args) 對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。
invoke方法引數:
obj - 從中呼叫底層方法的物件
args - 用於方法呼叫的引數
返回:使用引數 args 在 obj 上指派該物件所表示方法的結果
例:成員方法的反射
import java.lang.reflect.Method;
public class D22_ReflectMethod {
public static void main(String[] args) throws Exception {
reflectMethod();
}
/**
* 利用反射呼叫String類的charAt方法來獲取字串str的指定值。
* Method代表位元組碼的方法
* 從String獲取位元組碼,根據位元組碼獲取方法,引數(要獲取的方法名稱,引數列表)
* @throws Exception
*/
public static void reflectMethod() throws Exception {
String str = "abc";
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str,1));
}
}
/*
結果
b
*/
七、 對接收陣列引數的成員方法進行反射
例:對接收陣列引數的成員方法進行反射
package zxx.enhance;
import java.lang.reflect.Method;
public class D23_ReflectArrayMethod {
public static void main(String[] args) throws Exception {
reflectArrayMethod(args);
}
public static void reflectArrayMethod(String[] args) throws Exception {
//普通方式呼叫main方法
//TestArguments.main(new String[]{"abcd","123","eeeee"});
//反射方式呼叫main方法。
String srartingClassName = args[0];
Method mainMethod = Class.forName(srartingClassName).getMethod("main", String[].class);
//null:因為main方法是靜態的,呼叫靜態方法不需要物件
//這樣會報錯,說引數個數不對,怎麼解決這個問題?
//mainMethod.invoke(null, new String[]{"abcd","123","eeeee"});
//上面語句報錯的原因是:因為接收的引數是1個,而new String[]{"abcd","123","eeeee"}是一個數組,
//會被拆成單個的元素,於是就出現了3個引數,為了把以上的寫法當做一個引數,有2種方法:
//解決方法1:把這個陣列在進行一次包裝,把它作為一個元素封裝到Object陣列中。
//mainMethod.invoke(null, new Object[]{new String[]{"abcd","123","eeeee"}});
//解決方法2:型別轉換,在前面加上(Object)轉換型別,告訴編譯器,這是一個Object型別的引數,不要拆包。此效率比較高
//main方法是靜態的,所以invoke方法的引數用null
mainMethod.invoke(null, (Object)new String[]{"abcd","123","eeeee"});
}
}
//注意:在執行時首先要獲取此類的完整路徑“zxx.enhance.TestArguments”,就是包名+類名。
//然後點右鍵-->Run As-->Run Configurations-->(x)=Arguments,在裡面貼上剛才複製的。儲存後在執行。
class TestArguments{
public static void main(String[] args) throws Exception {
for(String arg : args){
System.out.println(arg);
}
}
}
/*
結果
abcd
123
eeeee
*/
八、陣列與Object的關係及其反射型別
package zxx.enhance;
public class D24_ArrayAndObject {
//反射陣列。列印物件中成員方法
public static void main(String[] args) {
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
System.out.println(a1.getClass() == a2.getClass());
//System.out.println(a1.getClass() == a4.getClass());
//System.out.println(a1.getClass() == a3.getClass());
System.out.println(a1.getClass().getName());
//獲取他們的父類的位元組碼名稱
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a2.getClass().getSuperclass().getName());
System.out.println(a3.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object aObj1 = a1;
Object aObj2 = a4;
//基本型別的一維陣列不能轉換為Object型別陣列。
//因為Object這個數組裡面裝的是int型別的陣列,不是Object
//Object[] aObj3 = a1;
Object[] aObj4 = a3;
Object[] aObj5 = a4;
System.out.println(aObj1);
System.out.println(aObj2);
System.out.println(aObj4);
System.out.println(aObj5);
}
}
/*
結果
true
[I
java.lang.Object
java.lang.Object
java.lang.Object
java.lang.Object
[[email protected]
[Ljava.lang.String;@525483cd
[[[email protected]
[Ljava.lang.String;@525483cd
*/
八、陣列與Object的關係及其反射型別
package zxx.enhance;
public class D24_ArrayAndObject {
//反射陣列。列印物件中成員方法
public static void main(String[] args) {
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
System.out.println(a1.getClass() == a2.getClass());
//System.out.println(a1.getClass() == a4.getClass());
//System.out.println(a1.getClass() == a3.getClass());
System.out.println(a1.getClass().getName());
//獲取他們的父類的位元組碼名稱
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a2.getClass().getSuperclass().getName());
System.out.println(a3.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object aObj1 = a1;
Object aObj2 = a4;
//基本型別的一維陣列不能轉換為Object型別陣列。
//因為Object這個數組裡面裝的是int型別的陣列,不是Object
//Object[] aObj3 = a1;
Object[] aObj4 = a3;
Object[] aObj5 = a4;
System.out.println(aObj1);
System.out.println(aObj2);
System.out.println(aObj4);
System.out.println(aObj5);
}
}
/*
結果
true
[I
java.lang.Object
java.lang.Object
java.lang.Object
java.lang.Object
[[email protected]
[Ljava.lang.String;@525483cd
[[[email protected]
[Ljava.lang.String;@525483cd
*/
九、陣列的反射
具有相同維數和元素型別的陣列屬於同一個型別,即具有相同的Class例項物件。
代表陣列的Class例項物件的getSuperClass()方法返回的父類為Object類對應的Class。
基本型別的一維陣列可以被當作Object型別使用,不能當作Object[]型別使用,
非基本型別的一維陣列,既可以當做Object型別使用,又可以當做Object[]型別使用。
Arrays.asList()方法處理int[]和String[]時的差異。
Array工具類用於完成對陣列的反射操作。
例:陣列的反射
package zxx.enhance;
import java.lang.reflect.Array;
public class D25_ReflectArray {
public static void main(String[] args) {
reflectArray();
}
//反射陣列。列印物件中成員方法
private static void reflectArray() {
String[] obj = {"A","B","C"};
//String obj ="ABC";
Class<? extends String[]> cla = obj.getClass();
//如果是一個數組,就拆成單個元素打印出來。
if(cla.isArray()){
int len = Array.getLength(obj);
for (int i = 0; i < len; i++) {
System.out.println(Array.get(obj, i));
}
}
//如果不是陣列,就直接列印物件。
else{
System.out.println(obj);
}
}
}
/*
結果
A
B
C
*/
十、集合的反射
*框架的概念及用反射技術開發框架的原理
*用類載入器的方式管理資源和配置檔案
反射的作用 – 實現框架功能
1、框架
我做房子賣給使用者,由使用者自己安裝門窗和空調,我做的房子就是框架,
使用者需要使用我的框架,把門窗插入進我的框架中。
框架與工具類有區別,工具類被使用者的類呼叫,而框架則是呼叫使用者提供的類。
2、框架要解決的核心問題
我在寫框架(房子)時,你這個使用者可能還在讀小學,還不會寫程式,
那麼我寫的框架程式怎麼呼叫你以後寫的類(門窗)呢?
因為在寫程式時無法知道要被呼叫的類名,所以,
在程式中無法直接new某個類的例項物件,而是要用凡是的方式來做。
3、綜合例項:
先直接用new語句建立ArrayList和HashSet的例項物件,演示用eclipse自動生成
Reflection類的equals和hashcode方法,比較兩個集合的執行結果差異。
然後改為採用配置檔案加反射的凡是建立ArrayList和HashSet的例項物件,
比較觀察執行結果差異。
需求:利用反射實現框架功能
思路:
1)通過檔案輸出流,建立一個檔案Config.properties,
2)獲取”className=java.util.ArrayList“的位元組碼後存入檔案中。檔案可以手動建立。
3)建立檔案輸入流和一個屬性集,讀取指定檔案內容,載入到屬性集中。
4)用鍵”className“在屬性集中收索。
5)獲得新集合,往集合中新增元素。
6)列印集合
例:
package zxx.enhance;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class D27_ReflectCollcetion {
public static void main(String[] args) throws Exception {
reflectCollcetion();
}
//反射集合
public static void reflectCollcetion() throws Exception {
//實際開發中,配置檔案的路徑不是這麼寫的,必須要寫完整的路徑。
//InputStream is = new FileInputStream("config.properties");
InputStream is = ReflectDemo.class.getResourceAsStream("config.properties");
Properties props = new Properties();
props.load(is);
//關閉的是is物件關聯的那個物理資源,物件本身並沒有關閉,
//而是有java的垃圾回收機制處理的。
is.close();
//反射做法
String className = props.getProperty("className");
Collection<ClassPoint> collections = (Collection)Class.forName(className).newInstance();
//原始做法
//Collection<ClassPointDemo> collections = new HashSet<ClassPointDemo>();
ClassPoint cp1 = new ClassPoint(3,3);
ClassPoint cp2 = new ClassPoint(5,5);
ClassPoint cp3 = new ClassPoint(3,3);
collections.add(cp1);
collections.add(cp2);
collections.add(cp3);
collections.add(cp1);
//更改config.properties配置檔案裡的ArrayList為HashSet後,列印結果將不同
//因為集合的儲存方式不同,ArrayList可以有重複元素,是有序的
//而HashSet集合是無序的,不可以重複儲存,因為該集合會判斷hashCode和equals方法。
System.out.println("集合元素個數:"+collections.size());
}
}
/*
結果
集合元素個數:4
*/
十一、通過反射獲得泛型的實際型別引數
分析:
比如:Vector v = new Vector();
那麼通過v是無法知道定義它的那個泛型型別的,那麼可以把這個v交給一個方法當做引數或者返回值型別來使用,
然後通過Method類的getGenericParameterTypes()方法來獲得該方法的引數列表,從而獲得引數實際間型別。
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Date;
import java.util.Vector;
/**
* 通過反射獲得泛型的實際型別引數
* @author Administrator
*/
public class GenericTest {
public static void main(String[] args) throws Exception {
//通過v物件是無法得到的,值能通過方法來獲取,那麼就要定義一個方法如下面applyVector方法。
//Vector<Date> v = new Vector<Date>();
//獲得位元組碼,通過位元組碼獲得方法,引數是一個方法名,Vector的位元組碼
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
//通過Method類的getGenericParameterTypes()方法
//反射applyMethod方法的引數化型別,可能有多個,所以是陣列。
Type[] types = applyMethod.getGenericParameterTypes();
//返回types的第一個引數,返回的是ParameterizedType型別。
ParameterizedType pType = (ParameterizedType)types[0];
//獲得原始型別
System.out.println(pType.getRawType());
//獲得實際型別。
System.out.println(pType.getActualTypeArguments()[0]);
}
//需要定義這個方法,通過這個方法的引數列表來反射他的引數型別。
public static void applyVector(Vector<Date> v) {
}
}
/*
結果
class java.util.Vector
class java.sql.Date
*/
十二、反射介面中的成員變數
package zxx.enhance;
import java.lang.reflect.Field;
interface Inter{
public int z = 8;
}
class ClassPoint1 implements Inter{
public int x;
@SuppressWarnings("unused")
private int y;
public ClassPoint1(int x, int y){
this.x = x;
this.y = y;
}
}
public class D28_ReflectIntefaceField{
public static void main(String[] args) throws Exception{
ClassPoint1 cp = new ClassPoint1(3, 5);
//先得到介面的Class物件陣列
for(Class<?> ca : cp.getClass().getInterfaces()){
//得到Field物件陣列
ca.getFields();
//遍歷陣列得到所有物件的名字
for(Field fd : ca.getFields()){
System.out.println(fd.getType()); //獲得型別
System.out.println(fd.getName()); //獲得名稱
System.out.println(fd.get(cp)); //獲得值
}
}
}
}
/*
結果
int
z
8
*/