-----------android培訓、java培訓、java學習型技術博客、期待與您交流!------------
概述
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
要想解剖一個類,必須先要獲取到該類的字節碼文件對象。而解剖使用的就是Class類中的方法.所以先要獲取到每一個字節碼文件對應的Class類型的對象.
反射技術:
應用場景
一個已經可以使用的應用程序,因爲程序已經做好可以運行使用,不能再進行代碼的加入了。而當後期我們新的功能加入程序時,該怎麼做呢?就如我們的電腦一樣,後期我們可能會鼠標、鍵盤等,所以電腦給我們預留了usb接口,只要符合這個接口規則的設備,電腦就可以通過加載驅動等操作來使用。
那這個程序能用了,如何使用後期出現的功能類呢?
常用的作法,會提供一個配置文件,來供以後實現此程序的類來擴展功能。對外提供配置文件,讓後期出現的子類直接將類名字配置到配置文件中即可。該應用程序直接讀取配置文件中的內容。並查找和給定名稱相同的類文件。進行如下操作:
1)加載這個類。
2)創建該類的對象。
3)調用該類中的內容。
應用程序使用的類不確定時,可以通過提供配置文件,讓使用者將具體的子類存儲到配置文件中。然後該程序通過反射技術,對指定的類進行內容的獲取。
好處:反射技術大大提高了程序的擴展性。
反射的基石——Class類
1、所有的類文件都有共同屬性,所以可以向上抽取,把這些共性內容封裝成一個類,這個類就叫Class(描述字節碼文件的對象)。
Class類中就包含屬性有field(字段)、method(方法)、construction(構造函數)。
而field中有修飾符、類型、變量名等複雜的描述內容,因此也可以將字段封裝稱爲一個對象。用來獲取類中field的內容,這個對象的描述叫Field。同理方法和構造函數也被封裝成對象Method、Constructor。要想對一個類進行內容的獲取,必須要先獲取該字節碼文件的對象。該對象是Class類型。
Class類描述的信息:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表,方法名稱的列表等。每一個字節碼就是class的實例對象。如:classcls=Data.class;
小知識:什麼叫字節碼?
當源程序中用到類時,首先要從硬盤把這個類的那些二進制代碼,一個類編譯成class放在硬盤上以後,就是一些二進制代碼,要把這些二進制代碼加載到內存中裏面來,再用這些字節碼去複製出一個一個對象來。
2、Class和class的區別
1)class:Java中的類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性,至於這個屬性的值是什麼,則由此類的實例對象確定,不同的實例對象有不同的屬性值。
2)Class:指的是Java程序中的各個Java類是屬於同一類事物,都是Java程序的類,這些類稱爲Class。例如人對應的是Person類,Java類對應的就是Class。Class是Java程序中各個Java類的總稱;它是反射的基石,通過Class類來使用反射。
3、獲取Class對象的三種方式
加載XX.class文件進內存時就被封裝成了對象,該對象就是字節碼文件對象。如何獲取Class對象呢?
方式一:
通過對象的getClass方法進行獲取。
如:Class clazz=new Person().getClass();//Person是一個類名
麻煩之處:每次都需要具體的類和該類的對象,以及調用getClass方法。
方式二:
任何數據類型都具備着一個靜態的屬性class,這個屬性直接獲取到該類型的對應Class對象。
如:Class clazz=Person.class;//Person是一個類名
比第一種較爲簡單,不用創建對象,不用調用getClass方法,但是還是要使用具體的類,和該類中的一個靜態屬性class完成。
方式三:
這種方式較爲簡單,只要知道類的名稱即可。不需要使用該類,也不需要去調用具體的屬性和行爲。就可以獲取到Class對象了。
如:Class clazz=Class.forName("包名.Person");//Person是一個類名
這種方式僅知道類名就可以獲取到該類字節碼對象的方式,更有利於擴展。
注:
1、九個預定義的Class:
1)包括八種基本類型(byte、short、int、long、float、double、char、boolean)的字節碼對象和一種返回值爲void類型的void.class。
2)Integer.TYPE是Integer類的一個常量,它代表此包裝類型包裝的基本類型的字節碼,所以和int.class是相等的。基本數據類型的字節碼都可以用與之對應的包裝類中的TYPE常量表示
2、只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,可以用Class.isArray()方法判斷是否爲數組類型的。
4、Class類中的方法
static Class forName(String className)
返回與給定字符串名的類或接口的相關聯的Class對象。
Class getClass()
返回的是Object運行時的類,即返回Class對象即字節碼對象
Constructor getConstructor()
返回Constructor對象,它反映此Class對象所表示的類的指定公共構造方法。
Field getField(String name)
返回一個Field對象,它表示此Class對象所代表的類或接口的指定公共成員字段。
Field[] getFields()
返回包含某些Field對象的數組,表示所代表類中的成員字段。
Method getMethod(String name,Class… parameterTypes)
返回一個Method對象,它表示的是此Class對象所代表的類的指定公共成員方法。
Method[] getMehtods()
返回一個包含某些Method對象的數組,是所代表的的類中的公共成員方法。
String getName()
以String形式返回此Class對象所表示的實體名稱。
String getSuperclass()
返回此Class所表示的類的超類的名稱
boolean isArray()
判定此Class對象是否表示一個數組
boolean isPrimitive()
判斷指定的Class對象是否是一個基本類型。
T newInstance()
創建此Class對象所表示的類的一個新實例。
5、通過Class對象獲取類實例
通過查看API我們知道,Class類是沒有構造方法的, 因此只能通過方法獲取類實例對象。之前我們用的已知類,創建對象的做法:
1)查找並加載XX.class文件進內存,並將該文件封裝成Class對象。
2)再依據Class對象創建該類具體的實例。
3)調用構造函數對對象進行初始化。
如:Person p=new Person();
現在用Class對象來獲取類實例對象的做法:
1)查找並加載指定名字的字節碼文件進內存,並被封裝成Class對象。
2)通過Class對象的newInstance方法創建該Class對應的類實例。
3)調用newInstance()方法會去使用該類的空參數構造函數進行初始化。
如:
String className="包名.Person";
Class clazz=Class.forName(className);
Object obj=clazz.newInstance();
示例:
//Person類
public class Person{
private String name;
public Person(){
}
person (String name){
this.name = name;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void show(){
System.out.println("show");
}
public void function(String s ){
System.out.println("function"+s);
}
public String restrnValue(String name,int age){
return "hello"+name+"***"+age;
}
private boid hello(){
System.out.println("helloworld");
}
public String toString(){
Return name+"..."+age;
}//示例
//通過Class對象創建類實例方法
public static void createPersonClass() throws Exception{
//方式1,硬編碼
Person p = new Person();
Class c = p.getClass();//c爲Person的字節碼文件對象
Person p2 = new Person();
Class c2 = p2.getClass();
System.out.ptintln(p==p2);//false
System.out.println(c==c2);//true,一個類的字節碼文件對象肯定只有一個,雖然可以創建多個對象
//方式2,硬編碼
Class c3 = Person.class;
System.out.println(c==c3);//true
//方式3
Class c4 = Class.forName("cn.itcast.Person");//可以不用必須導包
System.out.printn(c==c4);//true }
}
反射獲取構造方法
通過反射拿到的構造方法被稱爲構造器對象 。
Constructor<t>
位於java.lang.reflect包中,提供關於類的單個構造方法的信息以及對它的訪問權限
獲取構造方法:
1)得到這個類的所有構造方法:如得到上面示例中Person類的所有構造方法
Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
2)獲取某一個構造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
創建實例對象:
1)通常方式:Person p = new Person(“lisi”,30);
2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);
注:
1、創建實例時newInstance方法中的參數列表必須與獲取Constructor的方法getConstructor方法中的參數列表一致。
2、newInstance():構造出一個實例對象,每調用一次就構造一個對象。
3、利用Constructor類來創建類實例的好處是可以指定構造函數,而Class類只能利用無參構造函數創建類實例對象。
演示用例:
//獲取字節碼文件對象
Class c = Class.forName("cn.itcast.Person");
//獲取構造器對象
//所以公共構造方法
//public Constructor<?>[] getConstructors()返回一個包含某些Constructor對象的數值,這些對象反應此Class對象所表示的類的所以公共構造方法。
Constructor[] cons = c.getConstructors();
for (Constructor[] con: cons){
System.out.println(con);
}
//所以構造方法
//public Constructor<?>[] getDeclaredConstructors()返回Constructor對象的一個數組,這些對象反映此Class對象表示的類聲明的所以構造方法。
Constructor[] cons = c.getDeclaredConstructors();
for (Constructor[] con: cons){
System.out.println(con);
}
//一般我們創建對象,只要一個構造方法就可以了,所以,我們只需要一個構造方法即可
//無參構造
//public Constructor<?>[] getConstructor(Class<?>...parameterTypes)
Constructor con = c.getConstructor();//表示無參構造
//通過構造器對象創建對象
//public T newInstance(Object... initargs)返回:通過調用此對象表示的構造方法來創建的新對象;initargs 將作爲變量傳遞給構造方法調用的對象數組
Object obj = con.newInstance();
System.out.println(obj);
//結果:Person[name = null,age = 0]
}
帶參構造函數
//獲取字節碼文件
Class c = Class.forName("cn.itcast.Person");
Class[] classes = new Class[2];
classes[0] = String.class;
classes[1] = int.class;
//獲取構造函數
Constructor con = c.getConstructor(classes);
//改進版
Constructor con = c.getConstructor(new Class[](String.class,int.class});
//最終版
Constructor con = c.getConstructor(String.class,int.class);
//創建對象
Object obj = con.newInstance("李易峯","28");
System.out.println(obj);
反射獲取成員變量
想要通過反射獲取成員變量,就要通過一個類——Field。
Field 提供有關類或接口的單個字段的信息,以及對它的動態訪問權限。反射的字段可能是一個類(靜態)字段或實例字段。
常用方法
Field getField(String s);//只能獲取公有和父類中公有
Field getDeclaredField(String s);//獲取該類中任意成員變量,包括私有
setAccessible(ture);
//如果是私有字段,要先將該私有字段進行取消權限檢查的能力。也稱暴力訪問。
set(Object obj, Object value);//將指定對象變量上此Field對象表示的字段設置爲指定的新值。
Object get(Object obj);//返回指定對象上Field表示的字段的值。
演示用例:
//獲取字節碼文件
Class c = Class.forName("cn.itcast.Person");
////獲取成員變量對象
//Field[] fields = c.getFields();//獲取所以公共的成員變量
//Field[] fields = c.getDeclaredFields();//獲取所以的成員變量
//for (Field field:fields){
//System.out.println(field);
//}
//獲取構造器對象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//獲取單個的成員變量
Field field = c.getField("age");
field.set(obj,20);//給obj對象的field字段賦值爲20
System.out.println(obj);
}
反射獲取成員方法
想要通過反射獲取成員方法就要先了解一下類Method。
method類位於java.lang.reflect包中, 提供關於類或接口上單獨某個方法(以及如何訪問該方法)的信息。所反映的方法可能是類方法或實例方法(包括抽象方法)。
常用方法
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對象對應的是一個靜態方法
用反射方式執行某個main方法:
首先要明確爲何要用反射:在寫源程序時,並不知道使用者傳入的類名是什麼,但是雖然傳入的類名不知道,而知道的是這個類中的方法有main這個方法。所以可以通過反射的方式,通過使用者傳入的類名(可定義字符串型變量作爲傳入類名的入口,通過這個變量代表類名),內部通過傳入的類名獲取其main方法,然後執行相應的內容。
此時會出現下面的問題:
啓動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 c = Class.forName("cn.itcast.Person");
//創建對象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//
//Method[] methods = c.getMethods();//所以公共方法,包括父類的
//Method[] methods = c.getDeclaredMethods();//本類的所以方法
//for (Method[] method:methods){
//System.out.println(method);
//}
//第一種:無參無返回值
Method[] m1 = c.getMethod("show",null);
m1.invoke(obj,null);
System.out.println("------------");
//第二種:帶參數無返回值
Method[] m2 = c.getMethod("function",String.class);
m2.invoke(obj,"李易峯");
System.out.println("------------");
//第三種:帶多個參數有返回值
Method[] m3 = c.getMethod("reutrnValue",String.class,int.class);
Object ooo = m3.invoke(obj,"李易峯",28);
System.out.println(ooo);
System.out.println("------------");
//第四種:私有方法的調用
Method[] m4 = c.getMethod("hello",null);
m4.setAccessible(true);
m4.invoke(obj,null);
數組的反射
1、具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。數組字節碼的名字:有[和數組對應類型的縮寫,如int[]數組的名稱爲:[I
2、Object[]與String[]沒有父子關係,Object與String有父子關係,所以new Object[]{“aaa”,”bb”}不能強制轉換成new String[]{“aaa”,”bb”}; Object x =“abc”能強制轉換成String x =“abc”。
3、如何得到某個數組中的某個元素的類型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
無法得到某個數組的具體類型,只能得到其中某個元素的類型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具類用於完成對數組的反射操作。
Array.getLength(Object obj);//獲取數組的長度
Array.get(Object obj,int x);//獲取數組中的元素
5、基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。
演示用例:
package cn.itheima.Demo;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayReflect {
public static void main(String[] args) {
int [] a1 = new int[]{1,2,3};
int [] a2 = new int[4];
int[][] a3 = new int[2][3];
String [] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass().equals(a2.getClass()));//true
System.out.println(a1.getClass().equals(a3.getClass()));//false
System.out.println(a1.getClass().equals(a4.getClass()));//false
System.out.println(a1.getClass().getName());//[I
System.out.println(a4.getClass().getName());//[Ljava.lang.String;
System.out.println(a1.getClass().getSuperclass());//class java.lang.Object
System.out.println(a4.getClass().getSuperclass());//class java.lang.Object
Object obj1=a1;
Object obj2=a3;
Object obj3=a4;
// Object[] obj11=a1;//這樣是不行的,因爲a1中的元素是int類型,基本數據類型不是Object
Object[] obj13=a3;
Object[] obj14=a4;//這樣可以,因爲String數組中的元素屬於Object
System.out.println(a1);//[I@4caaf64e
System.out.println(a4);//[Ljava.lang.String;@6c10a234
System.out.println(Arrays.asList(a1));//[I@4caaf64e
System.out.println(Arrays.asList(a4));//[a, b, c]
/* Arrays.asList()方法處理int[]和String[]時的差異。
* 打印Arrays.asList(a1);還是跟直接打印a1是一樣的
打印Arrays.asList(a4);就會把a3的元素打印出來。
這是因爲此方法在JDK1.4版本中,接收的Object類型的數組,
而a3可以作爲Object數組傳入。但是a1不可以作爲Object數組傳入,所以只能按照JDK1.5版本來處理。
在JDK1.5版本中,傳入的是一個可變參數,所以a1就被當作是一個object,也就是一個參數,
而不是數組傳入,所以打印的結果還是跟直接打印a1一樣。
*/
//Array工具類用於完成對數組的反射操作。如打印任意數值
printObject(a1);
printObject(a4);
printObject("abc");
}
//打印任意數值
private static void printObject(Object obj) {
Class clazz=obj.getClass();
//如果傳入的是數組,則遍歷
if(clazz.isArray()){
int len =Array.getLength(obj);//Array工具類獲取數組長度方法
for(int x=0;x<len;x++){
System.out.println(Array.get(obj, x));//Array工具獲取數組元素
}
}
else
System.out.println(obj);
}
}
通過反射運行配置文件中的內容
//如果有需求上的改動,代碼會一直被改動
//反射+配置文件
/*
作爲配置文件來說
鍵時固定的,是已經知道的
值是變化的
className,methodName
*/
Properties prop = new Properties();
FileReader fr = new FileReader("test.properties");
prop.load(fr);
fr.close();
//獲取類名
String className = prop.getProperty("className");
//獲取方法名
String methodName = prop.getProperty("methodName");
//獲取字節碼文件對象
Class c = Class.forName(className);
Constructor con = c.getConstructor();
Object obj = con.newInstance();
Method m = c.getMethod(methodName,null);
m.invoke(obj,null);
}
}
public class Student{
public void love{
System.out.println("");
}
}
public class Teacher{
public void love{
System.out.println("");
}
}
--------------------------------------
properties文件
className = cn.itcast.text.Student
methodName = love;
/*
反射越過泛型檢查
我給你ArrayList<Integer>的一個對象,我想在這個集合中添加一個字符串數據,如何實現?
通過反射實現
反射可以越過泛型檢查
*/
class {
public static void main(String[] args)throws Exception{
ArrayList<Integer> array = ArrayList<Integer>();
//獲取字節碼文件
Class c = array.getClass();
Method m = c.getMethod("add",Object.class);
m.invoke(array,"hello");
System.out.println(array);
}
}
實現框架的功能
概述
1、框架:通過反射調用Java類的一種方式。
如房地產商造房子用戶住,門窗和空調等等內部都是由用戶自己安裝,房子就是框架,用戶需使用此框架,安好門窗等放入到房地產商提供的框架中。
框架和工具類的區別:工具類被用戶類調用,而框架是調用用戶提供的類。
2、框架機器要解決的核心問題:
我們在寫框架(造房子的過程)的時候,調用的類(安裝的門窗等)還未出現,那麼,框架無法知道要被調用的類名,所以在程序中無法直接new其某個類的實例對象,而要用反射來做。
3、簡單框架程序的步驟:
1)右擊項目File命名一個配置文件如:config.properties,然後寫入配置信息。如鍵值對:className=java.util.ArrayList,等號右邊的配置鍵,右邊是值。
2)代碼實現,加載此文件:
①將文件讀取到讀取流中,要寫出配置文件的絕對路徑。
如:InputStream is=new FileInputStream(“配置文件”);
②用Properties類的load()方法將流中的數據存入集合。
③關閉流:關閉的是讀取流,因爲流中的數據已經加載進內存。
3)通過getProperty()方法獲取className,即配置的值,也就是某個類名。
4)用反射的方式,創建對象newInstance()。
5)執行程序主體功能
-----------android培訓、java培訓、java學習型技術博客、期待與您交流!------------