夯實Java基礎(二十一)——Java反射機制

一、反射機制概述

Java反射機制是指程序在運行狀態中,對於任何一個類,咱們都可以知道這個類的全部屬性和方法(包括private、protected等)。對於任何一個對象,咱們都可以對它的屬性和方法進行調用。咱們把這種動態獲取對象信息和調用對象方法的功能稱之爲反射機制。html

在程序運行時,當一個類加載完成以後,在堆內存的方法區中就產生了一個Class類型的對象(一個類只會對應一個Class對象,絕對不會產生第二個),這個Class對象就包含了完整的類的結構信息。咱們建立該類的對象後就能夠經過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構。因此,咱們形象的稱之爲:反射。這麼說很是的抽象,不能理解,可是經過後面代碼的體現就會變得容易理解,暫時先了解一下便可。java

到後面還會學習到框架的知識(如Spring、Hibernate等等),框架的組成結構能夠理解爲這個公式:框架=反射+註解+設計模式。其中反射就是框架的靈魂所在。因此反射在Java的學習中很是重要。設計模式

那麼Java反射機制主要提供的功能有哪些:數組

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具備的成員變量和方法
  • 在運行時調用任意一個對象的成員變量和方法
  • 在運行時獲取泛型信息
  • 在運行時處理註解
  • 生成動態代理

java反射API中經常使用的類介紹:框架

  • java.lang.Class類:表明一個類
  • java.lang.reflect.Field類:表明類的成員變量(成員變量也稱爲類的屬性)
  • java.lang.reflect.Method類:表明類的方法
  • java.lang.reflect.Constrctor類:表明類的構造方法

二、關於Class類

Class類是用來描述類的類,它是一個十分特殊的類,沒有構造方法。Class對象的加載過程以下:當程序運行時,咱們編寫的每個類,編譯完成後,都會生成 類名.class 文件,當咱們咱們new對象或者類加載器加載的時候,JVM就會加載咱們的 類名.class 文件,而且加載到內存中,而後在將 類名.class 文件讀取而且同時會生成該類惟一的一個Class對象,用於表示該類的全部信息。ide

Class表明一個類,在運行的Java應用程序中表示類或接口。在Class類中提供了不少有用的方法,這裏對他們簡單的介紹。此處參考學習

一、得到類相關的方法:測試

  • asSubclass(Class<U> clazz):把傳遞的類的對象轉換成表明其子類的對象
  • Cast:把對象轉換成表明類或是接口的對象
  • getClassLoader():得到類的加載器
  • getClasses():返回一個數組,數組中包含該類中全部公共類和接口類的對象
  • getDeclaredClasses():返回一個數組,數組中包含該類中全部類和接口類的對象
  • forName(String className):根據類名返回類的對象
  • getName():得到類的完整路徑名字
  • newInstance():建立類的實例,它調用的是此類的默認構造方法(沒有默認無參構造器會報錯)
  • getPackage():得到類的包
  • getSimpleName():得到類的名字
  • getSuperclass():得到當前類繼承的父類的名字
  • getInterfaces():得到當前類實現的類或是接口

二、得到類中屬性相關的方法:ui

  • getField(String name):得到某個公有的屬性對象
  • getFields():得到全部公有的屬性對象
  • getDeclaredField(String name):得到某個屬性對象
  • getDeclaredFields():得到全部屬性對象

三、得到類中註解相關的方法:this

  • getAnnotation(Class<A> annotationClass):返回該類中與參數類型匹配的公有註解對象
  • getAnnotations():返回該類全部的公有註解對象
  • getDeclaredAnnotation(Class<A> annotationClass):返回該類中與參數類型匹配的全部註解對象
  • getDeclaredAnnotations():返回該類全部的註解對象

四、得到類中構造器相關的方法:

  • getConstructor(Class...<?> parameterTypes):得到該類中與參數類型匹配的公有構造方法
  • getConstructors():得到該類的全部公有構造方法
  • getDeclaredConstructor(Class...<?> parameterTypes):得到該類中與參數類型匹配的構造方法
  • getDeclaredConstructors():得到該類全部構造方法

五、得到類中方法相關的方法:

  • getMethod(String name, Class...<?> parameterTypes):得到該類某個公有的方法
  • getMethods():得到該類全部公有的方法
  • getDeclaredMethod(String name, Class...<?> parameterTypes):得到該類某個方法
  • getDeclaredMethods():得到該類全部方法

六、類中其餘重要的方法:

  • isAnnotation():若是是註解類型則返回true
  • isAnnotationPresent(Class<? extends Annotation> annotationClass):若是是指定類型註解類型則返回true
  • isAnonymousClass():若是是匿名類則返回true
  • isArray():若是是一個數組類則返回true
  • isEnum():若是是枚舉類則返回true
  • isInstance(Object obj):若是obj是該類的實例則返回true
  • isInterface():若是是接口類則返回true
  • isLocalClass():若是是局部類則返回true
  • isMemberClass():若是是內部類則返回true

Class類中的方法還有有不少,這裏只列舉了部分,須要學習更多的能夠自行去查看Class的API文檔。

注意:Class並非只有普通類或接口才能獲取,其中基本數據類型、數組、枚舉、註解、void等均可以獲取其Class對象,甚至Class這個類自己也能夠獲取Class對象,簡單舉例:

        Class<Object> c1 = Object.class;
        Class<String> c2 = String.class;
        Class<Integer> c3 = int.class;
        Class<int[]> c4 = int[].class;
        Class<int[][]> c5 = int[][].class;
        Class<ElementType> c6 = ElementType.class;
        Class<Override> c7 = Override.class;
        Class<Class> c8 = Class.class;
        Class<Void> c9 = void.class;

        int arr[]=new int[10];
        int arr1[]=new int[100];
        Class<? extends int[]> c10 = arr.getClass();
        Class<? extends int[]> c11 = arr1.getClass();
        //只要元素類型和維度相同,就是同一個Class
        System.out.println(c10==c11);

三、獲取Class的幾種方式

既然反射機制必定會用到Class這個類,那麼就必須先獲取它,獲取Class對象有四種方式。注意:反射這裏全部舉例都用Person類來做爲演示,因此先建立一個Person類,後面會一直用這個。

public class Person {
    //公共的成員變量
    public String name;
    //default成員變量
    int age;
    //私有的成員變量
    private String address;

    public Person() {
    }
    //私有構造方法
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    getter/setter方法省略

    //公共方法
    public void display1(){
        System.out.println("公共方法...");
    }
    //私有方法
    private void display2(){
        System.out.println("私有方法...");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

四種方式分別是:

①、經過Object類中的getClass方法獲取。這種方式要先建立類的對象,這樣再使用反射就畫蛇添足了,不推薦。

②、經過類名.class 直接獲取。這種方式須要導入相應類的包,依賴性較強,不推薦。

③、使用Class類中靜態方法forName(String className)獲取。因此這種方式最經常使用。

④、使用類加載器ClassLoader來獲取。這種方式瞭解便可,用的不多。

代碼演示以下:

public class Reflection {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一種方式:getClass
        Person person = new Person();
        Class<? extends Person> clazz1 = person.getClass();
        System.out.println("getClass獲取:"+clazz1);

        //第二種方式:類名.class
        Class<Person> clazz2 = Person.class;
        System.out.println("類名.class獲取:"+clazz2);

        //第三種方式:forName
        try {
            Class<?> clazz3 = Class.forName("com.thr.Person");
            System.out.println("forName()方法獲取:"+clazz3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //第四種方式:類加載器ClassLoader
        ClassLoader classLoader = Reflection.class.getClassLoader();
        Class<?> clazz4 = classLoader.loadClass("com.thr.Person");
        System.out.println("類加載器ClassLoader獲取:"+clazz4);

        //能夠發現返回都是true,說明引用的同一個對象
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz4);
    }
}

image

這四種方式推薦使用Class類中的靜態方法forName(String className)來獲取。並且不管使用哪一種方式獲取,結果都是同一個類。

四、獲取成員變量

在咱們正常建立對象的實例時,其內部的私有元素是不能訪問的,可是使用反射則能夠輕易的獲取,前面建立的Person類中屬性、方法和構造器都有被private關鍵字所修飾。因此接下來演示一下怎麼獲取成員變量、方法和構造器。

獲取屬性使用到的是Field這個類,它內部的方法主要是獲取、設置某個屬性的方法,例如:getXXX()、setXXX();

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

        //獲取Person的Class對象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、經過反射獲取全部public的成員變量,private、protected、default是不能獲取到的
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field.getName());
        }
        System.out.println("------------------------");

        //二、經過反射單個獲取public成員變量
        Field name = clazz.getField("name");
        System.out.println(name);
        Field age = clazz.getDeclaredField("age");
        System.out.println(age);
        Field address = clazz.getDeclaredField("address");
        address.setAccessible(true);
        System.out.println(address);
        System.out.println("------------------------");

        //三、經過反射獲取全部變量,包括private、protected、default。
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //四、獲取成員變量名字
            System.out.println(field.getName());
            //五、獲取權限修飾:0是default、1是public、2是private、4是protected。或者使用Modfier.toString(modifiers)輸出
            int modifiers = field.getModifiers();
            System.out.println(modifiers);
            //六、獲取成員變量數據類型
            Class<?> type = field.getType();
            System.out.println(type);
            System.out.println("---------------");
        }
    }
}

五、獲取方法而且調用

獲取方法用到的是Method類,內部方法也是一些getXXX()和setXXX的方法,這些方法就不演示了,和上面獲取Field大同小異,能夠參考上面的例子。而後咱們主要來演示一些與上面不同的方法。

爲了測試獲取方法上面的Annotation方法,我建立了一個MyAnnotation註解,而後讓其做用在Person類的display1()方法上面。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String[] value() default "tang_hao";
}

須要注意的是@Retention必需要設置爲RUNTIME類型,不然是獲取不到的。

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

        //獲取Person的Class對象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、經過反射獲取全部方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            //二、獲取方法的名稱
            System.out.println("名稱:"+method.getName());

            //三、獲取方法上面的註解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println("註解:"+annotation);
            }

            //四、獲取方法返回值類型
            Class<?> returnType = method.getReturnType();
            System.out.println("返回值類型:"+returnType);

            //五、獲取形參列表
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (!(parameterTypes==null||parameterTypes.length==0)){
                for (Class<?> parameterType : parameterTypes) {
                    System.out.println("形參列表:"+parameterType.getName());
                }
            }
            System.out.println("----------------");
        }
    }
}

若是咱們還想要使用反射來調用方法,那麼就須要使用到這個方法:

  • invoke(Object obj, Object... args):傳遞object對象及參數而後調用該對象所對應的方法
public class Reflection {
    public static void main(String[] args) throws Exception {

        //獲取Person的Class對象
        Class<?> clazz = Class.forName("com.thr.Person");
        Object o = clazz.newInstance();
        Method display1 = clazz.getDeclaredMethod("display1");
        display1.invoke(o);
        Method display2 = clazz.getDeclaredMethod("display2");
        display2.setAccessible(true);
        display2.invoke(o);
    }
}

六、獲取構造器而且建立實例

獲取方法用到的是Constructor類,構造器的獲取比較的簡單,簡單演示一下:

public class Reflection {
    public static void main(String[] args) throws Exception {
        //獲取Person的Class對象
        Class<?> clazz = Class.forName("com.thr.Person");

        //一、獲取public的構造器
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("-----------------------");

        //二、獲取全部的構造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("-----------------------");

        //三、使用反射建立實例,默認構造器
        Constructor<?> constructor = clazz.getConstructor();
        System.out.println(constructor.getName());
        Object o = constructor.newInstance();
        Person p= (Person) o;
        System.out.println(p.toString());

        //有參構造器
        Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class,String.class);
        Object o1 = constructor1.newInstance("tang_hao",20,"China");
        Person p1= (Person) o1;
        System.out.println(p1.toString());
    }
}

其實後面還有獲取父類、泛型、接口、所在包等等其餘的就很少說了,其實都是大同小異,理解了上面這些,後面這些應該也會了。

七、小結

從前面學到這裏咱們大概對反射也應該有一個簡單的瞭解了,利用反射能夠獲取成員變量、方法、構造器、註解等屬性,甚至是私有的方法,這在普通建立實例的方式是不可能的。雖然咱們在平時寫代碼不多用到反射技術,可是咱們要知道後面會學習框架知識,而框架的靈魂就是反射,因此學好反射基礎對咱們來講是很是有必要的。

相關文章
相關標籤/搜索