秒懂系列,深入理解Java反射機制

所以知識體系文章GitHub已收錄,歡迎Star!

GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual

大家記得給我一個Star!筆芯!

搜索關注微信公衆號“碼出Offer”,Z哥送你學習福利資源!

深入理解Java反射機制

一、Java反射概述

Java的反射(reflection)機制是指在程序的運行狀態中,可以構造任意一個類的對象,可以瞭解任意一個對象所屬的類,可以瞭解任意一個類的成員變量和方法,可以調用任意一個對象的屬性和方法。這種動態獲取程序信息以及動態調用對象的功能稱爲Java語言的反射機制。Java反射機制被稱爲框架設計的靈魂。

二、Java創建對象思想傳遞

首先在Java反射機制的概述部分,多次強調了任意一個是爲什麼呢?

這裏先想想我們的創建對象過程,創建對象過程需要寫一個對象實體類,之後去new這個對象實體類,最後編譯、運行文件。而這些過程,我們可以定義爲三個重要的階段。

此三階段爲 Source源碼階段 -> Class類加載階段 -> Runtime運行時階段

這三個階段是如何變化的呢?很簡單。

  • 首先,我們去寫一個Person類,也就是創建一個Person.java文件。其次再去寫一個Test測試類,去new一個Person對象。

  • 我們知道,.java文件是需要有一個編譯、運行的過程的。所以,我們使用javac命令去對它進行編譯操作,這就使得.java文件編譯出一個.class字節碼文件並存入硬盤中。而這個時期叫做Source源碼階段。

  • 源碼階段過後,我們編譯好的.class文件會通過類加載器(ClassLoader)加載到內存中。所以在內存中會描述這個字節碼文件爲Class類對象。而我們的.class文件中存儲的成員變量、構造方法和成員方法等,它們分別有着不同的作用,比如成員變量可以去設置和獲取值、構造方法可以去用它創建對象、成員方法可以去運行執行它。而它們會分別裝在Filed[]、Constructor[]和Method[]中,那麼爲什麼它們以數組形式存儲的呢?那是因爲.class文件中寫入內存的成員變量、構造方法和成員方法有很多個,所以在內存中的存儲方式是數組。

  • 類加載過後,就可以通過類對象的這些行爲去創建真正的Person對象了。

這就是一個完整的創建對象的過程。至於上述多此強調任意一個是因爲Java反射機制可以在程序的運行過程中(Runtime運行時階段)操作這些對象,比如:操作FIled、Constructor和Method等對象。此時,反射機制的好處還不只這一個,它還有解耦的好處,可以大大降低程序的緊密程度和耦合性來提高程序的可擴展性。

Source源碼階段
image-20200530200512660 image-20200530200620787 image-20200530195610793
Class類對象階段
image-20200530195610793 image-20200530200727497 image-20200530203126841
Runtime運行時階段
image-20200530203126841 image-20200530201237748 image-20200530201620960

三、類對象

3.1 類對象和類的對象

根據上述的創建對象思想,我們可以得出反射機制就是程序運行時使用Class類對象封裝的方法操作各個對象。所以在這裏我們要介紹兩個概念類對象類的對象

  • 類對象是類加載的產物,封裝了一個類的所有信息(類名、父類、接口、屬性、方法、構造方法)
  • 類的對象是基於某個類new出來的對象,也稱爲實例對象
類對象中封裝的對象 描述
Filed 成員變量(屬性)
Method 方法
Constructor 構造方法
PackageName 包名
ClassName 類名
SuperClass 實體類的父類Class
Interface 接口

3.2 三種獲取類對象的方法

返回值 方法名稱 描述
static Class<?> forName(String className) 返回與帶有給定字符串名的類或接口相關聯的 Class 對象。
static Class<?> forName(String name, boolean initialize, ClassLoader loader) 使用給定的類加載器,返回與帶有給定字符串名的類或接口相關聯的 Class 對象。
  • 通過類的對象,獲取類對象
    • Person person = new Person(); Class clazz = person.getClass();
    • 場景:常用於對象的字節碼獲取方式
  • 通過類名獲取類對象
    • Class clazz = 類名.class;
    • 場景:常用於參數的傳遞
  • 以靜態方法通過全類名獲取類對象
    • Class clazz = Claa.forName("全類名");
    • 場景:常用於讀取配置文件,將類名定義在配置文件中。讀取文件,加載類對象。

以下代碼是使用三種方式來獲取類對象,然後將三種方式獲取的類對象分別比較地址,最後比較完地址後我們發現返回的都是true。這就可以映射出同一個字節碼文件(*.class)在一次程序運行過程中,只會類加載一次,不管使用哪一種方式獲取的類對象都是同一個。

最後我又將以靜態方法通過全類名的方式獲取類對象封裝成了方法,這樣就可以靈活的使用全類名獲取類對象了。

package com.mylifes1110.java.bean;

class Person {
}

public class TestReflect {
    public static void main(String[] args) throws ClassNotFoundException {
        //通過類的對象獲取類對象
        Person person = new Person();
        Class<? extends Person> clazz1 = person.getClass();

        //通過類名獲取類對象
        Class<Person> clazz2 = Person.class;

        //以靜態方法通過全類名獲取類對象
        Class<?> clazz3 = Class.forName("com.mylifes1110.java.bean.Person");

        System.out.println(clazz1 == clazz2);   //true
        System.out.println(clazz1 == clazz3);   //true
        System.out.println(clazz2 == clazz3);   //true
    }

    /**
     * @param className 全類名
     * @return 以靜態方法通過全類名獲取類對象
     */
    public static Class getClassObject(String className) {
        Class clazz = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return clazz;
    }
}

四、Class對象的常用方法

4.1 獲取成員變量

返回值 方法名稱 描述
Field getField(String name) 獲取 public 修飾的指定名稱的成員變量。
Field[] getFields() 獲取 public 修飾的所有成員變量。
Field getDeclaredField(String name) 獲取指定名稱的成員變量,不受修飾符限制。
Field[] getDeclaredFields() 獲取所有成員變量,不受修飾符限制。
Object get(Object obj) 通過成員變量獲取指定對象的值
void set(Object obj, Object value) 通過成員變量設置指定對象的成員變量值
setAccessible(true); 默認爲false;true則忽略成員變量訪問修飾符的安全檢查

如下代碼我寫了詳細的使用註釋和結果註釋,都是通過上述方法實現的。

注意: 有一個特殊使用就是訪問private修飾的成員變量,需要setAccessible(true); 做略成員變量訪問修飾符的安全檢查處理。如果不做處理的話,就會飄紅報錯。如下信息:

Exception in thread "main" java.lang.IllegalAccessException: Class com.mylifes1110.java.bean.TestReflectFiled can not access a member of class com.mylifes1110.java.bean.Person with modifiers "private"

該報錯信息表示:我們無法訪問private修飾的成員變量!

package com.mylifes1110.java.bean;

import java.lang.reflect.Field;

class Person {
    private String   name;
    public Integer   age;
    protected Double score;

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


public class TestReflectFiled {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        //獲取 public 修飾的所有成員變量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);      //public java.lang.Integer com.mylifes1110.java.bean.Person.age
        }

        //獲取 public 修飾的指定名稱的成員變量
        Field age = personClass.getField("age");
        System.out.println(age);            //public java.lang.Integer com.mylifes1110.java.bean.Person.age
        //獲取成員變量age的值
        Object ageValue = age.get(person);
        System.out.println(ageValue);       //null
        //設置age的值
        age.set(person, 18);
        System.out.println(person);         //Person{name='null', age=18, score=null}

        //獲取所有成員變量,不被修飾符限制
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
		/*
          private java.lang.String com.mylifes1110.java.bean.Person.name
          public java.lang.Integer com.mylifes1110.java.bean.Person.age
          protected java.lang.Double com.mylifes1110.java.bean.Person.score
         */

        //獲取指定名稱的成員變量,不被修飾符限制
        Field name = personClass.getDeclaredField("name");
        //忽略訪問修飾符的安全檢查
        name.setAccessible(true);           //暴力反射
        Object nameValue = name.get(person);
        System.out.println(nameValue);      //null
    }
}

4.2 獲取構造方法

返回值 方法名稱 描述
Constructor<T> getConstructor(Class<?>… parameterTypes) 獲取 public 修飾的指定參數的構造方法(可以不指定參數,也就是獲取無參構造)
Constructor<?>[] getConstructors() 獲取 public 修飾的所有構造方法
Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes) 獲取指定參數的構造方法,不受修飾符限制。(可以不指定參數,也就是獲取無參構造)
Constructor<?>[] getDeclaredConstructors() 獲取所有構造方法,不受修飾符限制。
newInstance() 通過構造方法創建此 Class 對象所表示的類的一個新實例
setAccessible(true); 默認爲false;true則忽略構造方法訪問修飾符的安全檢查

如下代碼我寫了詳細的使用註釋和結果註釋,都是通過上述方法實現的。

注意: 因爲方法中帶Declared字段的方法,就是可以獲取private修飾的構造方法,如果通過該構造方法創建實例對象,就需要忽略構造方法訪問修飾符的安全檢查。此操作是與獲取成員變量的解決方式相同,需要setAccessible(true);。所以,在此我就沒有演示!

package com.mylifes1110.java.bean;

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

class Person {
    private String   name;
    public Integer   age;
    protected Double score;

    public Person() {}

    public Person(String name, Integer age, Double score) {
        this.name  = name;
        this.age   = age;
        this.score = score;
    }

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

public class TestReflectConstructor {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        //獲取 public 修飾的無參構造
        Constructor<? extends Person> constructor = personClass.getConstructor();
        System.out.println(constructor);    //public com.mylifes1110.java.bean.Person()
        //通過無參構造創建實例對象
        Person p1 = constructor.newInstance();
        System.out.println(p1);             //Person{name='null', age=null, score=null}

        Constructor<? extends Person> constructor2 = personClass.getConstructor(String.class, Integer.class, Double.class);
        System.out.println(constructor2);   //public com.mylifes1110.java.bean.Person(java.lang.String,java.lang.Integer,java.lang.Double)
        Person p2 = constructor2.newInstance("Ziph", 18, 100.00);
        System.out.println(p2);             //Person{name='Ziph', age=18, score=100.0}


        //獲取 public 修飾的所有構造方法
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor<?> constructor1 : constructors) {
            System.out.println(constructor1);
        }
        /*
          public com.mylifes1110.java.bean.Person()
          public com.mylifes1110.java.bean.Person(java.lang.String,java.lang.Integer,java.lang.Double)
         */
    }
}

4.3 獲取方法

返回值 方法名稱 描述
Method getMethod(String name, Class<?>… parameterTypes) 獲取 public 修飾的指定名稱的方法
Method[] getMethods() 獲取 public 修飾的所有方法
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 獲取指定名稱的方法,不受修飾符限制。
Method[] getDeclaredMethods() 獲取所有方法,不受修飾符限制。
Object invoke(Object obj, Object… args) 通過對象和有參數或無參數執行方法並返回其方法對象
String getName() 通過方法對象獲取方法名
setAccessible(true); 默認爲false;true則忽略方法訪問修飾符的安全檢查

如下代碼我寫了詳細的使用註釋和結果註釋,都是通過上述方法實現的。

注意: 因爲方法列表中帶Declared字段的方法,就是可以獲取private修飾的方法,如果通過該方法獲取的方法對象並執行,就需要忽略方法訪問修飾符的安全檢查。此操作是與獲取成員變量的解決方式相同,需要setAccessible(true);。所以,在此我也沒有演示!

package com.mylifes1110.java.bean;

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

class Person {
    private String   name;
    public Integer   age;
    protected Double score;

    public void jump() {
        System.out.println("跳起來。");
    }

    public void run(Integer meters) {
        System.out.println("我今天跑步跑了" + meters + "米。");
    }

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


public class TestReflectMethod {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        //獲取 public 修飾的jump無參方法
        Method jump = personClass.getMethod("jump");
        //執行獲取到的jump無參方法
        jump.invoke(person);                //跳起來。

        //獲取 public 修飾的run有參方法
        Method run = personClass.getMethod("run", Integer.class);
        //執行獲取到的run有參方法
        run.invoke(person, 2000);    	    //我今天跑步跑了2000米。

        //獲取 public 修飾的所有方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
			//獲取方法名
            System.out.println(method.getName());
        }
        /*
            public void com.mylifes1110.java.bean.Person.run(java.lang.Integer)
            run
            public java.lang.String com.mylifes1110.java.bean.Person.toString()
            toString
            public void com.mylifes1110.java.bean.Person.jump()
            jump
            public final void java.lang.Object.wait() throws java.lang.InterruptedException
            wait
            public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
            wait
            public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
            wait
            public boolean java.lang.Object.equals(java.lang.Object)
            equals
            public native int java.lang.Object.hashCode()
            hashCode
            public final native java.lang.Class java.lang.Object.getClass()
            getClass
            public final native void java.lang.Object.notify()
            notify
            public final native void java.lang.Object.notifyAll()
            notifyAll
         */
    }
}

4.4 獲取類名

返回值 方法名稱 描述
String getName() 通過類對象獲取全類名
public class TestReflectClassName {
    public static void main(String[] args) {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        //獲取全類名
        String className = personClass.getName();
        System.out.println(className);      //com.mylifes1110.java.bean.Person
    }
}

4.5 獲取接口

返回值 方法名稱 描述
Class<?>[] getInterfaces() 確定此對象所表示的類或接口實現的接口。
boolean isInterface() 判斷是否是一個接口類型
public class TestReflectInterface {
    public static void main(String[] args) {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        Class<?>[] interfaces = personClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            //獲取接口
            System.out.println(anInterface);        		//interface com.mylifes1110.java.bean.A
            //判斷是否爲接口類型
            System.out.println(anInterface.isInterface());  //true
        }
    }
}

4.6 獲取包名

返回值 方法名稱 描述
Package getPackage() 獲取類所在包名(不含類本身)
package com.mylifes1110.java.bean;

class Person {
    
}

public class TestReflectPackage {
    public static void main(String[] args) {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        Package aPackage = personClass.getPackage();
        System.out.println(aPackage);               //package com.mylifes1110.java.bean
    }
}

4.7 獲取父類

返回值 方法名稱 描述
Class<? super T> getSuperclass() 獲取表示此 Class 所表示的實體(類、接口、基本類型或 void)的超類的 Class
public class TestReflectSuperClass {
    public static void main(String[] args) {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

        Class<?> superclass = personClass.getSuperclass();
        System.out.println(superclass);             //class com.mylifes1110.java.bean.Animal
    }
}

class Animal {

}

class Person extends Animal {
    
}

4.8 判斷註解

返回值 方法名稱 描述
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定類型的註釋存在於此元素上,則返回 true,否則返回 false。

注意: 在判斷註解是否在某個元素上時,如果時自定義註解,必須將註解的生命週期設置在Runtime運行時期@Retention(RetentionPolicy.RUNTIME),如果不設置該註解無效,判斷時則返回false。

public class TestReflectAnnotation {
    public static void main(String[] args) {
        Person person = new Person();
        Class<? extends Person> personClass = person.getClass();

		//判斷abc註解是否在Person類上
        boolean flag = personClass.isAnnotationPresent(abc.class);
        System.out.println(flag);           //true
    }
}

@abc
class Person {
    
}
//自定義註解
package com.mylifes1110.java.bean;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface abc {
}

五、對象和方法的代工廠

5.1 創建對象的代工廠

使用Java反射機制實現通過全類名來創建實例對象

/**
 * 創建對象的工廠模式
 */
public class TestFactory {
    public static void main(String[] args) {
        Object o = createObject("com.mylifes1110.java.bean.A");
        System.out.println(o.toString());
    }

    /*創建對象的工廠*/
    public static Object createObject(String className) {
        try {
            Class c = Class.forName(className);
            return c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

class A {

}

5.2 創建對象並執行方法的代工廠

使用Java反射機制來創建對象並封裝一個具有普適性的可以獲取方法並執行方法的代工廠

public class TestInvokeAnything {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Object o = createObject("com.mylifes1110.java.bean.Students");
        
        /*創建Students對象並調用exam三參方法*/
        invokeAnything(o, "exam", new Class[] {int.class, double.class, String.class}, 10, 100, "Ziph");

        /*創建Students對象並調用study無參方法*/
        invokeAnything(o, "study", null, null);
        
        /*創建Students對象並調用study一參方法*/
        invokeAnything(o, "study", new Class[] {int.class}, 100);
    }

    /**
     * 利用反射底層技術執行任何方法的通用編程(可以調用每一個方法)
     * @param obj 對象
     * @param methodName 方法名稱
     * @param types 方法的形參列表(注意:數組接收)
     * @param args 需要傳入的實參(注意:可變長參數)
     */
    public static void invokeAnything(Object obj, String methodName, Class[] types, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        /*類對象*/
        Class c = obj.getClass();
        /*獲取方法的對象Method*/
        Method method = c.getDeclaredMethod(methodName, types);
        /*忽略修飾符安全檢查*/
        method.setAccessible(true);
        /*執行方法*/
        method.invoke(obj, args);
    }

    /**
     * 創建對象的工廠模式
     * @param className 需要傳入的全類名
     * @return
     */
    public static Object createObject(String className) {
        try {
            Class c = Class.forName(className);
            return c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

class Students {
    String name;
    Integer age;
    String sex;
    Double score;

    public Students() {
    }

    public Students(String name, Integer age, String sex, Double score) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.score = score;
    }

    public void study() {
        System.out.println("正在學習...");
    }

    public int study(int hours) {
        System.out.println("學習了" + hours + "個小時");
        return 0;
    }

    public void exam(int hours, double score, String name) {
        System.out.println(name + "做了" + hours + "小時的測驗,考了" + score + "分");
    }

    public void calc() {
        System.out.println("Ziph正在計算...");
    }
}

在這裏插入圖片描述

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