1. 程式人生 > >黑馬程式設計師—JAVA高新技術之反射

黑馬程式設計師—JAVA高新技術之反射

反射

一、反射的基石

  反射的基石是Class類。Java程式中的各個java類屬於同一類事物,描述這類事物的java類名就是Class.

1.1 Class類的分析

  在程式執行時呼叫類的時候,首先將這個類在硬碟上的二進位制程式碼載入到記憶體中,才可以用這個類建立物件,也就是說先將類的位元組碼載入到記憶體,再用這個位元組碼建立物件。每個類的位元組碼就是一個Class類的例項物件。

1.2得到位元組碼對應的例項物件(Class型別)有三種方式:

  1. 類名.class 例:PersonDemo.class

  2.  物件.getClass() 例:new Person().getClass();

  3.  Class.forName(“完整類名”),

例:Class.forName(“java.util.Data”);

  Class.forName方法的作用是返回位元組碼,返回方式有兩種:

  第一種,如果位元組碼已經被載入進記憶體,虛擬機器快取了,那麼就可以直接用全類名的方式得到位元組碼Class.forName(String className)。

  第二種,如果class檔案沒有被載加過,也就是說虛擬機器裡還沒有該位元組碼,就要指定檔名、載入器,來得到位元組碼(用載入器載入,把那份位元組碼快取起來,通過該方法返回位元組碼)Class.forName(String name, boolean initialize, 

ClassLoader loader)

1.3九個預定義的Class例項物件

   任何型別都是Class的例項物件。

  八個基本資料型別都是Class類的例項物件,void類也是Class類的例項物件,Class cls = void.class;  

  方法isPrimitive()用於判斷指定的Class物件是否表示一個基本型別(原始型別),有八個基本型別和關鍵字void。基本型別和void的Class例項物件都可用對應的包裝型別的常量值TYPE來獲取。

   例:Integer.TYPE表示所包裝的基本型別int的Class例項物件。

    Class類中有個方法isArray()用於判斷Class例項是不是陣列型別。

  總之,只要是在源程式中出現的型別,都有各自的Class例項物件。

二、反射的概念

  反射就是把java類中的各種成分對映成相應的java類,例如:一個java類中用一個Class類的物件來表示,一個類中的組成部分:成員變數,方法,構造方法,包等等資訊也用一個個的java類來表示,就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。

  表示java類的Class類顯要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的例項物件來表示。它們是Field、Method、Contructor、Package等等。

  一個類中的每個成員都可以用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可以得到這些例項物件。

三、 Constructor類

Constructor類代表某個類中的一個構造方法。

import java.lang.reflect.*;

public class ConstructorTest {

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

// 返回類裡面的所有構造方法

Constructor[] constructors = String.class.getConstructors();

//用引數型別來指定某一個構造方法,從而返回該構造方法物件

Constructor con = String.class.getConstructor(StringBuilder.class);

//用得到的有參構造方法進行new物件,用的什麼引數型別獲取的構造方法,

//在new物件的時候,就一定要用相應的實際引數型別傳值。

String str = (String)con.newInstance(new StringBuilder("abc"));//該方法返回的是Object所以一定要強轉

System.out.println(str.charAt(2));

//這個方法也是用於建物件,只是通過無參構造方法建的。

String.class.newInstance();

}

}

Class類裡面的newInstance()方法就是在用無參的構造方法建立一個物件。

四、Field類

  Field類代表某個類中的一個成員變數,也就是位元組碼裡的一個變數,不代表一個物件上的變數。所以用get()方法獲取變數值時要指定是哪個物件。

4.1成員變數的反射

  當成員變數私有化時,通過Class物件呼叫getDeclaredField方法傳入指定的成員變數字串,返回Field物件,並通過Field物件呼叫setAccessible(true)方法設為可訪問性,就可用get方法獲取到私有的成員變數,也稱暴力反射。

public class ReflectPoint {

public int x;

private int y;

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

}

import java.lang.reflect.*;

public class ReflectDemo

{

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

ReflectPoint rp = new ReflectPoint(5,9);

Field fieldX = rp.getClass().getField("x");

System.out.println(fieldX.get(rp));

Field fieldY = rp.getClass().getDeclaredField("y");

fieldY.setAccessible(true);

System.out.println(fieldY.get(rp));

}

}

4.2獲取一個類中所有String型別的成員變數,並替換指定字元

import java.lang.reflect.*;
public class ReflexDemo {
public static void main(String[] args) throws Exception {
Student s = new Student("zhangsan","beijing",23);
refField(s);
System.out.println(s.name+":"+s.add+":"+s.age);

}

static void refField(Object o) throws Exception{
Field[] field = o.getClass().getFields();
for(Field f : field){
if(f.getType() == String.class){
String oldValue = (String)f.get(o);
String newValue = oldValue.replaceAll("zhangsan", "lisi");
f.set(o, newValue);

}
}
}
}
class Student{
public String name,add;
public int age;

public Student(String name,String add,int age){
this.name = name;
this.add = add;
this.age = age;
}
}

五、Method類

     Method類代表某個類中的一個成員方法。

5.1成員方法的反射

  1、得到成員方法物件:Class物件.getMethod(“方法名”,引數型別.class);

  方法有幾個引數型別就要寫幾個對應的.class。

  2、用反射方式呼叫方法:Method物件.invoke(物件,實際引數);

  如果傳遞給Method物件的invoke()方法的第一個引數為null,該Method物件對應的是一個靜態方法,因為靜態方法不需要用物件呼叫,是跟著類走的。

  3、JDK1.4和JDK1.5的invoke方法的區別:

  JDK1.5:public Object invoke(Object obj,Object。。。args)支援可變引數

  JDK1.4:public Object invoke(Object obj,Object[] args)需要將一個數組作為引數傳遞給invoke方法時,陣列中的每個元素分別對應被呼叫方法中的一個引數,所以,用JDK1.4寫為charAt.invoke(“str”,new Object[]{1})

例:

import java.lang.reflect.*;

public class ReflectDemo{

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

String str = new String("abcde");

Method method = String.class.getMethod("charAt", int.class);//得到method物件

System.out.println(method.invoke(str, 2));

}

}

5.2用反射方式執行某個類中的main方法

  需求:寫一個程式,這個程式能夠根據使用者提供的類名,去執行該類中的main方法。

  分析:啟動java程式的main方法的引數是一個字串陣列,即public static void main(String[] args),通過反射方式來呼叫這個main方法時,怎樣給invoke方法傳遞引數呢?按JDK1.5的語法,整個陣列是一個引數,而按1.4的語法,陣列中的每個元素對應一個引數,當把一個字串陣列作為引數傳遞給invoke方法時,javac會按1.4的語法處理(因為新版本要相容老版本),即把陣列打散成為若干個單獨的引數,所以給main方法傳遞引數時,不能寫mainMethod.invoke(null,new String[]{“abc”,”def”});

解決辦法有兩種:

  1、mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});這種方式相當於Object數組裡存了一個String陣列型別的元素,這樣編譯器就會認為是一個引數。

  2、mainMethod.invoke(null.(Object)new String[]{“xxx”});這種方式編譯時,相當於是一個Object物件,編譯器就不會把引數當作陣列看待,也就不會把數組裡的元素打散成若干個引數。

具體程式碼如下:

import java.lang.reflect.*;

public class ReflectDemo{

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

String startClassName = args[0];

Method mainMethod = Class.forName(startClassName).getMethod("main", String[].class);

mainMethod.invoke(null, (Object)new String[]{"123"});

}

}

class TestArguments{

public static void main(String[] args){

for(String str:args){

System.out.println(str);

}  

}

}

  注:要在執行ReflectTest的時候給主函式傳值,開啟Run Configurations視窗,並在Arguments選項卡的Program arguments欄裡輸入cn.itcast.day1.TestArguments 表示TestArguments位元組碼傳給ReflectTestr 的main函式,那麼在執行ReflectTest的時候就會同時載入得到TestArguments位元組碼檔案,就相當於。ReflectTest主函式的args[0],那麼就可通過Class.forName方法得到傳遞進來的位元組碼檔案物件。

六、陣列的反射

6.1陣列與Object的關係及其反射型別

  1、只要陣列型別相同,維度相同,就是同一份位元組碼物件。

  2、只要是引用資料型別(比如陣列型別)的超類都是Object,所以可以通過getSuperclass方法獲取到超類的Class物件。

  3、如果陣列是一維的,數組裡的元素型別又是基本資料型別,那麼就不能寫成Object[] obj = a1;只能寫成Object obj = a1;

例:

int[] a1 = new int[3];

int[] a2 = new int[5];

int[] [] a3 = new int[3];

Stirng[] a4 = new String[3];

System.out.println(a1.getClass()==a2.getClass());//結果為true

System.out.println(a1.getClass().getSuperclass().getName());//結果為Object

Object a1Obj = a1;//正確

Object[] a1Obj = a1;//錯誤

Object[] a3Obj = a3;//正確,一維陣列是陣列型別,屬於Object子類,Object[]相當於有一個Object陣列,裡面存的是Object

Objcet[] a4Obj = a4;//正確,因為String是類型別,屬於Object子類

4、Arrays.asList()方法處理int[]和String[]時的差異:

  從反射的角度理解:虛擬機器執行時會先根據JDK1.4的方法處理Arrays.asList(Object[] a),接收的是Object型別的陣列,當傳入的是String[],就代表String數組裡的每個元素都是一個引數並存到集合裡,所以可以全部打印出元素。這時int[]傳進來它代表的一個Object而不是Object[],不符合1.4的引數型別就處理不了,就會按1.5的方法處理,Arrays.asList(T…a)是接收的Object物件,就把int[]當作一個整體處理了,就相當於是一個引數,所以列印的是地址值。

  個人理解:Arrays.asList()返回的是一個list集合,而集合裡只能存物件的引用,不能存基本資料型別int,所以列印list就是一個地址值,而String[]裡存的是String型別的元素,每個元素都所屬Object子類,列印list就可看到每個元素的值。

6.2、陣列的反射應用

Array類是用於對陣列反射的類。

例:用反射方式做:只要傳進來的是陣列就列印裡面的元素,不是陣列就直接列印物件。

public class ReflectArray{

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

String[] str = new String[]{"ab","cde","w"};

Object obj = null;

printObject(str);

printObject("sss");

}

public static void printObject(Object obj){

if(obj.getClass().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);

}

}  

}