--------------android培訓、java培訓、學習型技術博客、期待與您交流! --------------
反射概述
通過Java的反射機制,在運行狀態中,對於任意一個類(.class文件),都能夠知道這個類中的所有屬性和方法;對於任意一
個對象,都能夠調用它的任意一個方法和屬性。這種動態獲取類的信息及動態調用對象的方法功能稱爲Java語言的反射機制。
結論:反射技術可以對類進行解剖。
反射原理:
平常的java類是通過一些固定的支架(方法,成員變量等)搭建來的,搭建完成後它就具備了描述一類事物的特性。而這些支架也屬於事物,它也能夠被類描述。所以java中就提供了Class類進行描述。Class類所描述的事物是:java類在編譯完成後,會生成class文件存在硬盤上。也就是字節碼。把字節碼(保存了該類的基本信息)加載到內存中來。返回的類型是Class,當然Class就具備了描述該類固定支架(方法,成員變量等)的特性。
得到字節碼的方法3種:
類名.class 例如,System.class
對象.getClass() 例如,new Date().getClass();
Class.forName(),例如,Class.forName("java.util.Date");
提示:如果字節碼已經在內存中,直接拿過來。
如果字節碼不在內存中,需要從硬盤上把字節碼加載到內存中去。
分析Class類
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
String abc = "afd";
Class cls1 = abc.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
// 虛擬機上做出該類一份字節碼,其他要用就直接拿了。所以打印結果都爲true
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
// 判斷是否是基本數據類型的字節碼isPrimitive()。false
System.out.println(cls1.isPrimitive());
// 基本類型的字節碼和對應的封裝類的字節碼是不相同的。false、
System.out.println(int.class == Integer.class);
// TYPE常量代表包裝類型所包裝的基本類型的字節碼。true
System.out.println(int.class == Integer.TYPE);
// 數組的字節碼類型,不是原始類型false。
System.out.println(int[].class.isPrimitive());
// 數組的字節碼類型,判斷一個Class類型是否爲數組用isArray()。true
System.out.println(int[].class.isArray());
// Class類型,判斷基本數據類型:isPrimitive();判斷數組類型:isArray();
// 總之:只要是在源程序中出的類型,都有各自的Class
}
}
九個預定義的Class:
1)包括八種基本類型(byte、short、int、long、float、double、char、boolean)的字節碼對象和一種返回值爲void類型的void.class。
2)Integer.TYPE是Integer類的一個常量,它代表此包裝類型包裝的基本類型的字節碼,所以和int.class是相等的。基本數據類型的字節碼都可以用與之對應的包裝類中的TYPE常量表示
只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,可以用Class.isArray()方法判斷是否爲數組類型的。
反射的概念
反射就是把Java類中的各種成分映射成相應的java類。
例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是Field、Method、Contructor、Package等等
Constructor類
1、概述
如果指定的類中沒有空參數的構造函數,或者要創建的類對象需要通過指定的構造函數進行初始化。這時怎麼辦呢?這時就不能使用Class類中的newInstance方法了。既然要通過指定的構造函數進行對象的初始化。就必須先獲取這個構造函數——Constructor。Constructor對象代表某個類的構造方法。
2、獲取構造方法:
1)得到這個類的所有構造方法:如得到上面示例中Person類的所有構造方法
Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
2)獲取某一個構造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
3、創建實例對象:
1)通常方式:Person p = new Person(“lisi”,30);
2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);
注:
1、創建實例時newInstance方法中的參數列表必須與獲取Constructor的方法getConstructor方法中的參數列表一致。
2、newInstance():構造出一個實例對象,每調用一次就構造一個對象。
3、利用Constructor類來創建類實例的好處是可以指定構造函數,而Class類只能利用無參構造函數創建類實例對象。
//Constructor類代表某個類中的一個構造方法。
//得到構造方法: 通過構造函數參數列表的類型,來確定獲取的具體構造類。
Constructor<String> strCon = String.class.getConstructor(StringBuffer.class);
// new String(new StringBuffer("abc"));
//創建實例對象:
// 1,在對strCon進行實例化對象的時候,需要指定參數對象。
String str2 = strCon.newInstance(new StringBuffer("abc"));
System.out.println(str2.charAt(2));
// 2,Class類提供了一個便利,有newInstance方法。直接可以創建對象。省去了獲取Constructor再去創建對象。
// 反射的缺點:反射比較消耗性能佔用時間,進行反射以後把得出的結果存在緩存起來。爲後面儘可能的減少反射的使用量。
String s = String.class.newInstance();
Field類
1、Field類代表某個類中一個成員變量
2、方法
Field getField(String s);//只能獲取公有和父類中公有
Field getDeclaredField(String s);//獲取該類中任意成員變量,包括私有
setAccessible(ture);
//如果是私有字段,要先將該私有字段進行取消權限檢查的能力。也稱暴力訪問。
set(Object obj, Object value);//將指定對象變量上此Field對象表示的字段設置爲指定的新值。
Object get(Object obj);//返回指定對象上Field表示的字段的值。
import java.lang.reflect.Field;
public class FieldDemo {
public static void main(String[] args) throws SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException {
ReflectPoint p = new ReflectPoint(3,5);
// 字段通過字段名字區分。它不代表某個特定對象上面的值。而是告訴程序員,這個叫y的變量已經給你取到了。封裝成了一個類。
// 類上的變量,不是對象上的變量。不包括私有的。
Field fieldY = p.getClass().getField("y");
// 從特定對象上取值。這個異常說明了,成員是私有的不讓訪問。
// Exception in thread "main" java.lang.NoSuchFieldException: y
// at java.lang.Class.getField(Class.java:1520)
// at reflect.FieldDemo.main(FieldDemo.java:10)
System.out.println(fieldY.get(p));
// 獲取所有申明的變量。包括private類型。
Field fieldX = p.getClass().getDeclaredField("x");
// java.lang.IllegalAccessException: Class reflect.FieldDemo can not access a member of class reflect.ReflectPoint with modifiers "private"
// 上面異常的出現說明了,沒有獲取私有變量值的權限。
// 如果想要獲取的話,需要進行暴力反射。
fieldX.setAccessible(true);
System.out.println(fieldX.get(p));
// Class c = String.class;
// String s = (String) c.newInstance();
//
// String s1 = String.class.newInstance();
}
}
將一個對象上所有的String類型的值中含有a的,進行替換。
private static void changeValue(ReflectPoint p) throws IllegalArgumentException, IllegalAccessException {
// 獲取ReflectPoint中所有申明的字段。
Field[] fs = p.getClass().getDeclaredFields();
// 遍歷字段。
for(Field f:fs){
// getType獲取Field的類型,用==好,因爲字節碼只有一份。如果是String類型的話,就進行操作。
if(f.getType()==String.class){
String oldValue = (String)f.get(p);
String newValue = oldValue.replace("b", "a");
f.set(p,newValue);
System.out.println(newValue);
}
}
}
}
反射的作用:你給我一個對象我能把他裏面的值都改掉。例如Spring框架中用的就是這個技術。你給我一個類,我就能用你給的資源來做些事情。
Method類
1、概述:Method類代表某個類中的一個成員方法。調用某個對象身上的方法,要先得到方法,再針對某個對象調用。
2、專家模式:誰調用這個數據,就是誰在調用它的專家。
如人關門:
調用者:是門調用關的動作,對象是門,因爲門知道如何執行關的動作,通過門軸之類的細節實現。
指揮者:是人在指揮門做關的動作,只是給門發出了關的信號,讓門執行。
總結:變量使用方法,是方法本身知道如何實現執行的過程,也就是“方法對象”調用方法,才執行了方法的每個細節的。
3、方法
Method[] getMethods();//只獲取公共和父類中的方法。
Method[] getDeclaredMethods();//獲取本類中包含私有。
Method getMethod("方法名",參數.class(如果是空參可以寫null));
Object invoke(Object obj ,參數);//調用方法
如果方法是靜態,invoke方法中的對象參數可以爲null。
如:
獲取某個類中的某個方法:(如String str =”abc”)
1)通常方式:str.charAt(1)
2)反射方式:
Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);
charAtMethod.invoke(str,1);
說明:如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明Method對象對應的是一個靜態方法
// 通過方法名字,和參數列表來確定方法。導包的時候看到com...這種包是內部用的(不要導),看到java...這就是外部用的。
String str = "abc";
Method methodCharAt = String.class.getMethod("charAt", int.class);
// 已知Method,調用誰的Method並且還要給它傳參數。
Character ch = (Character) methodCharAt.invoke(str, 2);
System.out.println(ch);
// 靜態方法的調用,因爲靜態方法不需要對象。第一個參數應該是null。所以:
// Character ch2 = (Character) methodCharAt.invoke(null, 2);
String className= args[0];
invokeMain(className);
調用其他類的main方法
爲什麼要用反射的方式調用main ?
main的參數是String數組,數組裏面可以裝類名字符串。
獲取這這些類的名字。通過反射的原理可以獲得各個類中的main方法。
分別執行這幾個類
private static void invokeMain(String className) throws Exception {
Method main = Class.forName(className).getMethod("main", String[].class);
// main是靜態,所以第一個參數是空的。
// java.lang.IllegalArgumentException: wrong number of arguments jdk1.4特點,給進去一個數組會將數組拆掉,拆掉後的值作爲參數造成的。導致參數變多了。
main.invoke(null, new Object[]{new String[]{"1","2","3"}});
// main.invoke(null, (Object)new String[]{"1","2","3"});
}
數組的反射
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayReflectDemo {
public static void main(String[] args) {
int[] a = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
// 數組class是否相同:1.數據類型2數組的維數
System.out.println(a.getClass() == a2.getClass());
// 數組的父類是Object
System.out.println(a4.getClass().getSuperclass().getName());
//
Object aObj1 = a;
Object aObj2 = a2;
// 因爲a是一維數組,裏面裝的是int型變量,不是Object子類。不行。
// Object[] obj1=a;
// 二維數組,數組裏面套數組。內層數組(一維數組)相當於是Object。可以
Object[] aobj3 = a3;
// String數組裏面裝的是String類型的變量。可以
Object[] aobj4 = a4;
System.out.println(Arrays.asList(a));
System.out.println(Arrays.asList(a4));
// 數組的反射。
printObjcet(a4);
}
private static void printObjcet(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(obj, i));
}
}
}
}