黑馬程序員——Java基礎---反射

-----------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.classint.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學習型技術博客、期待與您交流!------------  

 

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