Java之反射機制初識

       前幾天被問到了反射,當時沒有回答出來多少,後來去看了一下,這裏大概總結一下!java

       首先,咱們要知道反射機制,那麼什麼是反射呢?程序員

       答:反射是程序能夠訪問、檢測和修改他自己狀態或行爲的一種能力。那麼java語言是如何支持反射的呢?別急,咱們來慢慢聊。數組

     咱們之前的學習中有遇到過Java中萬事萬物皆爲對象之說,那麼靜態變量呢?還有基本數據類型的數據呢?它們也是面向對象的嗎?咱們都知道靜態是屬於類的,不是哪一個類的對象的,基本數據類型是屬於包裝類的,那麼難道類也是對象?答案就是是。Java中的每個類都是java.lang.Class類的對象。框架

1、Class類的使用jvm

      在Java中,每個class都有一個相應的Class對象。換句話說,就是當咱們編寫一個類時,編譯完成後,在生成的.class文件中,就會產生一個Class對象,用於表示這個類的類型信息。也就是說,Class類的實例對象表示java應用程序運行時的類或者接口。虛擬機爲每種類型管理一個獨一無二的Class對象,也就是說,每一個類型都有一個Class對象,運行程序時,jvm首先檢查所要加載的類對應的Class對象是否已經加載,若是沒有加載,jvm就會根據類名查找.class文件並將其Class對象載入。學習

      能夠經過類名.class、類的對象.getClass()、Class.forName()三種方法獲取Class對象。具體的使用以下:測試

 

package Reflect;

public class ClassRefelect {

    public static void main(String[] args){
        //得到Foo的類對象
        Foo foo1 = new Foo();
        /**
         * 得到Foo類對象有三種方法
         * 1.類名.class    每個類都有一個隱含的成員變量,class
         * 2.類對象.getClass();
         * 3.Class.forName(包名+類名);
         * 下面的c1,c2,c3官網稱之爲「Class Type"  類類型
         */

        /**
         * 萬事萬物皆爲對象,那麼類也是對象,類是什麼的對象呢》
         *java.lang.Class的對象
         * 該類中封裝了類的相關操做
         */
        Class  c1 = Foo.class;

        Class c2 = foo1.getClass();

        Class c3 = null;
        try {
            c3 = Class.forName("Reflect.Foo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        /**
         *那麼這三個類類型是否相同呢?
         * 答案是相同,爲何呢?
         * 由於每個類只可能有一種類類型,
         * 就好比每個對象只有一個類同樣,
         * 這個類類型是Class的實例對象
         */

        System.out.println(c1==c2);      //true
        System.out.println(c3==c2);      //true
    }
}

class Foo{
        public void print(){
            System.out.println("建立了Foo類的對象");
        }
}

 

 

        能夠看到上面的代碼中有對三個Class對象的引用變量進行比較,答案固然是true,由於它們都是Foo類對象,而上面咱們也提到了,每個類都有一個獨一無二的Class對象,因此結果應該是true.this

 

       這裏還有一個問題,咱們怎麼區分foo1和c一、c2等呢?咱們知道foo1是類Foo的一個對象,那麼就是Foo的對象,官網上對於c1,c2有一種說法,是Class Type----->類類型,也就是c1,c2是Foo的類類型,其實咱們還能夠這樣區別,c1,c2是Foo對象,而foo1是Foo的對象。spa

       既然咱們已經獲得了Foo的類類型,裏面含有Foo類的相關信息,那麼咱們有一個大膽的想象,可否用Foo類類型獲得Foo的某個對象呢?答案是固然能夠。Class對象有一個newInstance()方法,代碼演示以下:3d

 

package Reflect;

public class ClassRefelect {

    public static void main(String[] args){
        Class  c1 = Foo.class;
   
        try {
            Foo foo2  = (Foo) c1.newInstance();
            foo2.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

class Foo{
        public void print(){
            System.out.println("建立了Foo類的對象");
        }
}

 

這裏的foo2至關於new Foo();建立出來的對象。

下面咱們來聊聊方法的反射。

2、方法的反射

       要想了解方法的反射,咱們首先要得到Class對象,才能經過Class對象得到方法的相關信息,得到Class對象的方法上面已經講過,這裏插播一條廣告,上面不是提到了每種類型都有Class對象麼,那麼基本數據類型以及void類型有木有呢?恩,有的,看下面的代碼

package Reflect;

/**
 * 基本數據類型類類型
 * 包裝類類類型
 *
 */
public class ClassType {

    public static void main(String[] args){
        Class c1 = int.class;                       //int的類類型
        Class c2 = String.class;                    //String類類型
        Class c3 = Double.class;                    //Double包裝類類型
        Class c4 = double.class;                    //double類類型
        Class c5 = void.class;                      //void 的類類型
        System.out.println(c1.getName());            //int
        System.out.println(c2.getName());            //java.lang.String
        System.out.println(c2.getSimpleName());      //String
        System.out.println(c3.getName());            //java.lang.Double
        System.out.println(c4.getName());            //double
        System.out.println(c3.getSimpleName());      //Double
        System.out.println(c5.getName());            //void
    }
}

      既然能夠拿到Class對象,那麼獲取方法的一系列信息就不成問題了。

      方法也是對象,是Method類的對象,該類中封裝了方法的一系列操做,該類是java.lang.reflect包下的類。

     這裏介紹幾個方法,後面會用到

----------------------------------------------------------------------------------------------------------------------------------------------------------------

 1.getMethods()                                          //得到一個類中的全部public修飾的方法對象,包括繼承父類的方法

 2.getDeclaredMethods()                           //得到一個類中的全部本身聲明的方法對象,不問訪問權限,不包括父類的

 3.getName()                                             //得到調用者的名稱

 4.getReturnType()                                    //得到方法的返回值類類型,即若是返回值是int,那麼返回的是int.class

 5.getParameterTypes()                            //得到參數列表的類類型

----------------------------------------------------------------------------------------------------------------------------------------------------------------

package Reflect;

import java.lang.reflect.Method;

public class ClassUtil {

    public static void printMessage(Object obj){

        //得到類的類類型

        /**
         * 若是傳入的參數是Object類型,
         * 則或取的就是Object的類類型,
         * 若是是其子類,則獲取的就是其子類的類類型
         */
        Class c1 = obj.getClass();

        //或取類的名稱
        System.out.println("類的名稱是:"+c1.getName());

        //獲取類中的方法
    
        //獲取類中的方法
        Method[] ms = c1.getMethods();
        for(int i=0;i<ms.length;i++){
            //獲取方法的返回值類型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+" ");
            //獲取方法的名稱
            System.out.print(ms[i].getName()+"(");
            //獲取參數類型----》獲取的是參數列表的類類型
            Class[] parameterType = ms[i].getParameterTypes();
            for (Class class1:parameterType) {
                System.out.print(class1.getName()+",");

            }
            System.out.println(")");
        }
    }
 

這裏傳入的若是是Object類型的對象,那麼就會獲取Object類的方法信息,若是是Object類的子類,那麼獲取的就是
該子類的方法信息。

這裏咱們測試一下:

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){
 
        Integer s = 1;
        ClassUtil.printMessage(s);
    
    }
}

       從這裏咱們能夠看到打印出了Integer類的全部的public方法的信息。

       那麼什麼是方法的反射呢?方法的反射是什麼樣的呢?

       平時咱們使用方法的步驟是什麼呢?是否是先獲取一個類的對象(若是是實例方法),而後對象.方法()對嗎?那麼方法反射剛好相反,是利用方法對象操做類的對象的。

       方法的反射也有幾個步驟:

       0.獲取類類型  

       1.經過類類型獲取某個方法的方法對象 

  2.方法對象.invoke(類的對象,參數列表)

       invoke()方法API文檔上時這樣講的:「對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法」,通俗的說就是能夠利用invoke()方法進行反射。

下面列出反射中須要的方法:

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

****說明一下:如下提到的c是Class對象,某個類的類類型,method是1,2方法返回的方法對象**** 

1.getMethod("Method name","Parameter Type")                           //方法名稱和參數列表能夠惟一肯定一個方法,注意這個方法  只能獲取類中public 聲明的方法,包括繼承自父類的方法

 

用法舉例:public void print(int a,int b){}

                  c.getMethod(「print",int.class,int.class)  或者  c.getMethod("print",new Class[]{int.class,int.class})

2.getDeclaredMethod("Method name","Parameter Type")               //獲取類中本身聲明的方法,不問權限,不包括繼承自父類的方法

用法舉例:public void print(int a,int b){}

                  c.getDeclaredMethod(「print",int.class,int.class)  或者  c.getDeclaredMethod("print",new Class[]{int.class,int.class})

 以上兩個方法都是獲取方法對象的

3.invoke(Object obj,args)                                             //對帶有args參數的obj對象調用由此Method對象表示的底層方法

用法舉例:class A{

        public void print(int a,int b){}

      }

                 method.invoke(new A(),10,20)  或者  method.invoke(new A(), new Object[]{10,20});

invoke方法返回值是null,或者是Object或者Object的子類,當是Object時不存在問題,若是(Object的子類)想要具體的返回值類型,那麼必須進行強制類型轉換,如

 Object o = method.invoke(a1,10,10);

 

 Integer i   = (Integer)method.invoke(a1,10,10);

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下來咱們看具體的代碼:

package Reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodRefelect {
    public static void main(String[] args){
        /**
         * 1.要想獲取一個方法的信息,首先須要獲取類類型
         * 2.獲取了類類型後經過類類型獲得方法對象
         * 3.經過方法對象能夠反射操做方法
         */

        A a  = new A();
        Class c = a.getClass();
        try {
            Method m = c.getMethod("print",new Class[]{int.class,int.class});
            try {
             //   Object o = m.invoke(a,new Object[]{10,20});
                Integer o = (Integer)m.invoke(a,10,20);
                System.out.println(o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
            Method m2 = c.getDeclaredMethod("print",String.class,String.class);
            try {
                Object o = m2.invoke(a,new Object[]{"Hello","ninhao"});
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
          //  Method m3 = c.getDeclaredMethod("print",new Class[]{});
            Method m3 = c.getDeclaredMethod("print");
            try {
               // Object o = m3.invoke(a,new Object[]{});
                Object o = m3.invoke(a);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

class A{

    public void print(){
        System.out.println("hello");
    }
    public int print(int a,int b){
        System.out.println(a+b);
        return a+b;
    }

    public void print(String a,String b){
        System.out.println(a.toUpperCase()+","+b.toLowerCase());
    }
}

 

  方法的反射講完後,咱們說一下Field的反射操做

3、Field的反射

  在講Field的反射以前,先來了解一下如何經過Class對象獲取某個類的Field的信息,若是須要獲取Field的信息,首先須要得到類中全部的Filed,而後依次得到每個Field的數據類型和名稱。

  接下來列出得到Field的信息的方法

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

1.getDeclaredFields()                  //得到一個類中所聲明的全部的Filed,返回值是一個數組

2.getType()                                  //得到調用者的類型

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

  代碼相對來講簡單,以下:

public static void printFiled(Object obj) {
        
        Class c1 = obj.getClass();
       Field[] fs
= c1.getDeclaredFields(); //本身類中聲明的全部的成員變量 for(Field filed: fs){ //根據每個成員變量獲取成員變量的類型 Class returnType = filed.getType(); String returnName = returnType.getName(); //返回每個成員變量的名稱 String FiledName = filed.getName(); System.out.println(returnName+" "+FiledName); } }

 

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){

        Integer s = 1;
        ClassUtil.printFiled(s);
     
    }
}

    測試了一下,結果中列出了Integer類的Field.[C這個是數組類型的反射,這裏不作詳述。

    接着到了咱們Field的反射表演的時間了。

  Field的反射操做步驟以下:

       0.得到類類型

       1.經過類類型得到某個Field

 1.5.通常,Field是private,因此須要setAccessible(true),大概的意思是能夠操做private的數據

  2.對得到的Field進行相關操做

  補充一下:Field(成員變量)也是面向對象的,它是java.lang.reflect.Filed的實例對象

       具體的實踐以下:

package Reflect;

import java.lang.reflect.Field;
public class FieldReflect {
    public static void main(String[] args){
        B b = new B();
        Class c = b.getClass();
        //首先獲取成員變量對象
        try {
            /**
             * 這裏的getField之因此會出錯,
             * 是由於getField獲取的是public的,
             * 而以前我並無指定a的訪問權限
             */
           // Field f = c.getField("a");
            Field f = c.getDeclaredField("a");     //經過getDeclaredField("Field name");得到該屬性對象
            try {
                f.setAccessible(true);             //若是不設置該標誌,將不能訪問私有成員
                f.set(b,12);                       //平時咱們都是b.setF(xxx);這裏經過Field對象f反向操做B類的對象b
                System.out.println(f.get(b));      //12
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }


        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

class B{

    private int a;
    private int b;

    public void setA(int a) {
        this.a = a;
    }

    public void setB(int b) {
        this.b = b;
    }

    public int getA() {
        return a;
    }
    public int getB() {
        return b;
    }
}

 

4、構造方法的反射

  首先咱們仍是來獲取構造方法的信息,構造方法的獲取所須要的方法同上面的普通方法、成員變量大概類似,這裏不作贅述。直接看代碼便可理解:

public static void printConMessage(Object obj){
        //得到類類型
        Class c1 = obj.getClass();
        //得到類中的本身聲明的構造方法
        Constructor[] constructors = c1.getDeclaredConstructors();
        for(Constructor constructor:constructors){
            //得到構造方法的名稱
            System.out.print(constructor.getName()+"(");
            //得到構造方法的參數列表中的參數類型
            Class[] parameterTypes = constructor.getParameterTypes();
            //打印出每個構造方法的參數
            for (Class para: parameterTypes) {
                System.out.print(para.getName()+",");
            }
            System.out.println(")");
        }
    }

 

package Reflect;

public class TestClassUtil {
    public static void main(String[] args){
     
        Integer s = 1;
       ClassUtil.printConMessage(s);
    }
}

 

       構造方法也是對象,是java.lang.reflect.Constructor的實例對象

      反射所須要的步驟:

     1.根據類類型得到構造方法對象

     2.經過構造方法對象建立該類的對象

下面的是構造方法的反射的代碼:

package Reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConRefelect {
    public static void main(String[] args){
        //構造方法的反射操做
        C c = new C();
        Class c1 = c.getClass();
        //得到構造方法
        try {
            Constructor constructor1 = c1.getConstructor();
            Constructor constructor2 = c1.getConstructor(String.class);

            try {
                /**
                 * 類類型.newInstance()能夠建立一個類對象
                 * 類的構造對象.newInstance()也能夠建立一個類對象
                 * 這個的意思是
                 */
                C o = (C)constructor1.newInstance();
      C q = (C)constructor2.newInstance("hello");
                q.print();
                o.print();
          //      System.out.println(c1==constructor1);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

            System.out.println(constructor1.getName());
            System.out.println(constructor2.getName());

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

class C{
    public String a;
    public C(){

    }
    public C(String a){
        this.a = a;
    }

    public void print(){
        System.out.println("構造方法被反射了");
    }
}

 

 

 

經過反射咱們還能夠查看泛型的本質原理:

5、經過反射了解泛型的本質

      泛型指的是集合中的數據只能輸入指定的數據類型的數據。以下所示:

      ArrayList<String> list = new ArrayList<String>();

      這個list中只能輸入String 類型的數據,若是輸入其餘的不兼容的就會報錯。

那麼如今咱們經過反射了解反射:

 

package Reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class TestGeneric {
    public static void main(String[] args){

        ArrayList list = new ArrayList();

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("hello");
        // list1.add(20);
        //下面經過反射來判斷泛型的本質

        Class c = list1.getClass();
        try {
            Method m = c.getMethod("add",new Class[]{Object.class});
            try {
                Object o = m.invoke(list1,20);
                System.out.println(list1.size());           //2
                System.out.println(list1);                  [hello,20]
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

 

       上面的代碼中剛開始時在list1中插入20時編譯報錯,接着咱們經過反射得到方法對象,操做集合的add方法,將20成功插入了集合中。爲何呢?由於反射是運行時進行的,因此上面咱們繞過了編譯將20成功插入了集中由此可得出泛型其實是在編譯時設置了「路障」,在編譯後會驅泛型化,它的存在只是爲了放置錯誤的輸入,只在編譯時有效,繞過編譯則無效。

  反射大概就總結這些,接下來咱們小小的聊聊動態加載類。

6、動態加載類

  類的加載分爲靜態加載和動態加載,靜態加載指的是編譯時加載,動態加載指的是運行時加載。

  new 建立對象時是靜態加載類,在編譯時刻就須要將所須要的全部的類加載進來。這樣有一個問題就是,當咱們有一個功能類,個人功能還不是很完整,可是我須要提早搭建起來框架,可是編譯時發現有一個如今不使用的類不存在,因此我如今這個功能類則編譯不經過。具體的代碼演示以下:

public class test{
    public static void main(String[] args){
            Word word = new Word();
            word.start();
    
           Excel excel = new Excel();
           excel.start();
 }

class Word{
    void start(){};
}

 

 

      如上所示,當我有一個Word類時,我就想要編譯運行這個test類,測試一下個人Word類是不是好的,可是如今沒有Excel類,因此沒法檢測,這就是高耦合,依賴性太強,這在工程中是不會出現的,咱們必須使得咱們的代碼低耦合,高內聚才能符合軟件工程的要求。

  那麼如何改善呢?這個本質的緣由仍是由於類是靜態加載,若是改成動態加載,那麼就不會出現這個問題,我只須要在動態加載時發現導入我須要的類便可。

package Reflect;

public class TestLoose {
    public static void main(String[] args){
        try {
            Class a  = Class.forName(args[0]);     //動態加載類,在編譯時根本不會出現問題,只有

            try {
                Able able  = (Able)a.newInstance();
                able.start();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

interface   Able{
      public void start();
}

class Word implements Able{
    public void start(){}

}

  上面的代碼就是一個低耦合,高內聚的代碼規範,如何說呢。咱們能夠從編譯、運行兩部分來講明,首先在編譯時TestLoose是不會出錯的,由於咱們有一個Able的接口存在,因此編譯經過;當運行時咱們有Word類,因此咱們在運行時輸入Word也是不會報錯的,咱們輸入Excel纔會出錯,這樣就會解決上面的若是存在一個類而不能測的問題,若是咱們後邊須要或者這段代碼由別的程序員來繼續寫,它們只須要實現Able便可,如Excel    implements Able;便可繼續添加。

  這是編寫代碼的一種新思想,從高強大的依賴種脫身而出。

  總結:今天總結關於反射的問題,就到這裏了。這裏沒有提到反射的數組和動態代理等關於反射更多、更復雜的東西,只是基礎的知識。

相關文章
相關標籤/搜索