Java--反射機制(一)——反射 API

一、概述

1、Java反射機制(Java-Reflect):

在運行狀態中,對于任意一個類,都能夠知道這個類中的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java的反射機制。

反射是Java開發中一個非常重要的概念,掌握了反射的知識,才能更好的學習Java高級課程.

2、Java 反射機制的功能
  1. 在運行時判斷任意一個對象所屬的類。

  2. 在運行時構造任意一個類的對象。

  3. 在運行時判斷任意一個類所具有的成員變量和方法。

  4. 在運行時調用任意一個對象的方法。

  5. 生成動態代理。

3、Java 反射機制的應用場景
  1. 逆向代碼 ,例如反編譯

  2. 與注解相結合的框架 例如Retrofit

  3. 單純的反射機制應用框架 例如EventBus

  4. 動態生成類框架 例如Gson

二、通過Java反射查看類信息

1、獲得Class對象

每個類被加載之后,系統就會為該類生成一個對應的Class對象。通過該Class對象就可以訪問到JVM中的這個類。

在Java程序中獲得Class對象通常有如下三種方式:

  1. 使用 Class 類的forName(String clazzName)靜態方法。該方法需要傳入字符串參數,該字符串參數的值是某個類的全限定名(必須添加完整包名)。

  2. 調用某個類的class屬性來獲取該類對應的 Class 對象。

  3. 調用某個對象的getClass()方法。該方法是java.lang.Object類中的一個方法。

//第一種方式 通過Class類的靜態方法——forName()來實現
class1 = Class.forName("com.lvr.reflection.Person");
//第二種方式 通過類的class屬性
class1 = Person.class;
//第三種方式 通過對象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();

對于方式一和方式二都是直接根據類來取得該類的 Class 對象,相比之下,方式二有如下的兩種優勢:

  • 代碼跟安全。程序在編譯階段就能夠檢查需要訪問的 Class 對象是否存在。
  • 線程性能更好。因為這種方式無須調用方法,所以性能更好。

可以通過類的類類型創建該類的對象實例。

Class.newInstance();
//
Foot foot = (Foot) c1.newInstance();
2、從 Class 中獲取信息

一旦獲得了某個類所對應的Class 對象之后,就可以調用 Class 對象的方法來獲得該對象的和該類的真實信息了。

  1. 獲取 Class 對應類的成員變量
    Field[] getDeclaredFields(); // 獲取 Class 對象對應類的所有屬性,與成員變量的訪問權限無關。
    Field[] getFields(); // 獲取 Class 對象對應類的所有 public 屬性。
    Field getDeclaredField(String name); // 獲取 Class 對象對應類的指定名稱的屬性,與成員變量的訪問權限無關。
    Field getField(String name); // 獲取 Class 對象對應類的指定名稱的 public 屬性。

  2. 獲取 Class 對應類的方法
    Method[] getDeclaredMethods(); // 獲取 Class 對象對應類的所有聲明方法,于方法的訪問權限無關。
    Method[] getMethods(); // 獲取 Class 對象對應類的所有 public 方法,包括父類的方法。
    Method getMethod(String name, Class<?>...parameterTypes); // 返回此 Class 對象對應類的、帶指定形參列表的 public 方法。
    Method getDeclaredMethod(String name, Class<?>...parameterTypes); // 返回此 Class 對象對應類的、帶指定形參列表的方法,與方法的訪問權限無關。

  3. 獲取 Class 對應類的構造函數
    Constructor<?>[] getDeclaredConstructors(); // 獲取 Class 對象對應類的所有聲明構造函數,于構造函數的訪問權限無關。
    Constructor<?>[] getConstructors(); // 獲取 Class 對象對應類的所有 public 構造函數。
    Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes); // 返回此 Class 對象對應類的、帶指定形參列表的構造函數,與構造函數的訪問權限無關。
    Constructor<T> getConstructor(Class<?>...parameterTypes); // 返回此 Class 對象對應類的、帶指定形參列表的 public 構造函數。

  4. 獲取 Class 對應類的 Annotation(注釋)
    <A extends Annotation>A getAnnotation(Class<A> annotationClass); // 嘗試獲取該 Class 對對象對應類存在的、指定類型的 Annotation;如果該類型的注解不存在,則返回 null。
    <A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass); // 這是Java8新增的方法,該方法嘗試獲取直接修飾該 Class 對象對應類、指定類型的Annotation;如果該類型的注解不存在,則返回 null。
    Annotation[] getAnnotations(); // 返回修飾該 Class 對象對應類存在的所有Annotation。
    Annotation[] getDeclaredAnnotations(); // 返回直接修飾該 Class 對應類的所有Annotation。
    <A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass); // 獲取修飾該類的、指定類型的多個Annotation。
    <A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass); // 獲取直接修飾該類的、指定類型的多個Annotation。

  5. 獲取 Class 對應類的內部類
    Class<?>[] getDeclaredClasses(); // 返回該 Class 對象對應類包含的全部內部類。

  6. 獲取 Class 對應類的外部類
    Class<?> getDeclaringClass(); // 返回該 Class 對象對應類所在的外部類。

  7. 獲取 Class 對應類所實現的接口
    Class<?>[] getInterfaces();

  8. 獲取 Class 對應類所繼承的父類
    Class<? super T> getSuperClass();

  9. 獲取 Class 對應類的修飾符、所在包、類名等基本信息
    int getModifiers(); // 返回此類或接口的所有修飾符。修飾符由 public、protected、private、final、static、abstract 等對應的常量組成,返回的整數應使用 Modifier 工具類的方法來解碼,才可以獲取真實的修飾符。
    Package getPackage() // 獲取該類的包。
    String getName() // 以字符串的形式返回此 Class 對象所表示的類的名稱。
    String getSimpleName() // 以字符串的形式返回此 Class 對象所表示的類的簡稱。

  10. 判斷該類是否為接口、枚舉、注解類型等
    boolean isAnnotation() // 返此 Class 對象是否是一個注解類型(由@interface定義)。
    boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 判斷此 Class 對象是否使用了Annotation修飾。
    boolean isAnonymousClass() // 此 Class 對象是否是一個匿名類。
    boolean isArray() //此 Class 對象是否是一個數組。
    boolean isEnum() // 此 Class 對象是否是一個枚舉(由 enum 關鍵字定義)。
    boolean isInterface() // 此 Class 對象是否是一個接口(由 interface 關鍵字定義)。
    boolean isInstance(Object obj) // 判斷 obj 是否是此 Class 對象的實例。該方法完全可以替代 instanceof 操作符。

3、Java 8 新增的方法——參數反射

Java 8 在java.lang.reflect包下新增了一個 Executable 抽象基類,該對象代表可執行的類成員,該類派生了 Constructor、Method 兩個子類。

Executable 基類提供了大量的方法來獲取修飾該方法或構造器的注解信息;還提供了
isVarArgs() // 用于判斷該方法或構造器是否包含數量可變的形參。
getModifiers() // 獲取該方法或構造器的修飾符。

此外,Executable 提供了如下兩個方法:
int ParamenterCount() // 獲取該構造器或方法的形參個數。
Paramenter[] getParamenters() // 獲取該構造器或方法的所有形參。

上面的第二個方法返回了一個 Paramenter[] 數組,Paramenter也是 Java 8 新增的API,每個 Paramenter 對象代表方法或構造器的一個參數。

Paramenter 提供了大量方法來獲取聲明該參數的泛型信息,還提供了如下常用的方法來獲取參數信息:
getModifiers() // 獲取修飾該形參的修飾符。
String getName() // 獲取形參名。
Type getParamenterizedType() // 獲取帶泛型的形參類型。
Class<?> getType() // 獲取形參類型。
boolean isNamePresent() // 返回該類的 class 文件中是否包含了方法的形參名信息。
boolean isVarArgs() // 用于判斷該參數是否為個數可變的形參。

三、使用反射生成并操作對象

Class 對象可以獲得該類里的方法(由 Method 對象表示)、構造器(由 Constructor 對象表示)、成員變量(由 Field 對象表示),這三個類都位于 java.lang.reflect 包下。

程序可以通過 Method 對象來執行對應的方法,
通過 Constructor 對象來調用對應的構造器創建實例,
通過 Field 對象直接訪問并修改對象的成員變量值。

1、創建對象

通過反射來生成對象的兩種方式:

  1. 使用 Class 對象的 newInstance() 方法來創建該 Class 對象對應類的實例。
    要求:Class 對象的對應類要有默認構造器,而執行 newInstance() 方法實際上是利用默認構造器來創建該類的實例。

  2. 先使用 Class 對象獲取指定的 Constructor 對象,再調用 Constructor 對象的 newInstance() 方法來創建該 Class 對象對應類的實例。
    這種方式可以選擇使用指定的構造器來創建實例。

通過第一種方式來創建對象是比較常見的情形,在很多的 JavaEE 框架中都需要根據配置文件信息來創建Java對象。從配置文件中讀取的只是某個類的字符串類名,程序需要根據該字符串來創建對應的實例,就必須使用到反射。

先建一個配置文件,obj.properties

a=java.util.Date
b=javax.swing.JFrame
/**
 * 功能:實現一個簡單的對象處
 * 思路:該對象池會根據配置文件讀取 key-value 對,然后創建這些對象,
 *       并將這些對象放入到一個 HashMap 中。
 * @author Administrator
 *
 */
public class ObjectPoolFactory {
    
    // 定義一個對象池,<對象名,實際對象>
    private Map<String, Object> objectPool = new HashMap<>();
    
    /**
     * 創建對象
     * @param className 字符串類名
     * @return 返回對應的 Java 對象
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object createObject(String className) 
            throws ClassNotFoundException
            , InstantiationException, IllegalAccessException {
        
        // 獲取對應的 Class 對象
        Class<?> clazz = Class.forName(className);
        // 使用對應類的默認構造器創建實例
        return clazz.newInstance();
    }
    
    /**
     * 根據指定文件來初始化對象池
     * @param fileName 配置文件名
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public void initPool(String fileName)
            throws ClassNotFoundException
            , InstantiationException, IllegalAccessException {
        
        try(FileInputStream fis = new FileInputStream(fileName)) {
            
            Properties props = new Properties();
            props.load(fis);
            for (String name : props.stringPropertyNames()) {
                objectPool.put(name, createObject(props.getProperty(name)));
            }
        } catch (IOException e) {
            System.out.println("讀取" + fileName + "異常!");
        }
    }
    
    /**
     * 根據指定的 name 值來獲取對應的對象
     * @param name 
     * @return
     */
    public Object getObject(String name) {
        return objectPool.get(name);
    }
}

測試文件:

public class Test {

    public static void main(String[] args)
            throws ClassNotFoundException
            , InstantiationException, IllegalAccessException {
        
        ObjectPoolFactory opf = new ObjectPoolFactory();
        opf.initPool("obj.properties");
        System.out.println(opf.getObject("a"));
        System.out.println(opf.getObject("b"));
        
    }

}

運行的結果:

這種使用配置文件來配置對象,然后由程序根據配置文件來創建對象的方式非常有用,如 Spring 框架就采用這種方式大大簡化了 JavaEE 應用的開發,當然,Spring采用的是 XML 配置文件——因為 XML 配置文件能配置的信息更加的豐富。

第二種方式,利用指定的構造器來創建 Java 對象。

  1. 獲取該類的 Class 對象。
  2. 利用 Class 對象的 getConstrustor() 方法來獲取指定的構造器。
  3. 調用 Construstor 的 newInstance() 方法來創建 Java 對象。
public class CreateObject {
    
    public static void main(String[] args) throws Exception {
        
        Class<?> jframeClass = Class.forName("javax.swing.JFrame");
        Constructor<?> ctor = jframeClass.getConstructor(String.class);
        Object obj = ctor.newInstance("測試窗口");
        
        System.out.println(obj);
        
    }
    
}

實際上,只有當程序需要動態創建某個類的對象時才會考慮使用反射,通常在開發通用性比較廣的框架、基礎平臺時可能會大量使用反射。

2、調用方法

當獲得某個類對應的 Class 對象后,就可以通過該 Class 對象的方法:
getMethods() // 獲取全部方法。
getMethod() // 獲取指定方法。
這兩個方法的返回值是 Method 數組,或者 Method 對象。

每個 Method 對象對應一個方法,獲得 Method 對象后,程序就可通過該 Method 來調用它對應的方法。在 Method 里包含一個 invoke() 方法:
Object invoke(Object obj, Object... args) // 該方法中的 obj 是執行該方法的主調,后面的 args 是執行該方法時傳入該方法的實參。

// 生成新的對象:用newInstance()方法
Object obj = class1.newInstance();
// 首先需要獲得與該方法對應的Method對象
Method method = class1.getMethod("setAge", int.class);
// 調用指定的函數并傳遞參數
method.invoke(obj, 28);

當通過Method的invoke()方法來調用對應的方法時,Java會要求程序必須有調用該方法的權限。如果程序確實需要調用某個對象的 private 方法,則可以先調用以下方法:
setAccessible(boolean flag) // 值為true,指示該 Method 在使用時應該取消Java語言的訪問權限檢查;值為false,則知識該Method在使用時要實施Java語言的訪問權限檢查。

Method 、 Constructor 、Field 都可以調用該方法,從而實現通過反射來調用 private 方法、private 構造器、private 成員變量。

3、訪問成員變量值

通過Class對象的:
getFields() // 獲取全部成員變量,返回 Field數組或對象。
getField() // 獲得指定成員變量,返回 Field數組或對象。

Field 提供了兩組方法來讀取或設置成員變量的值:
getXXX(Object obj) // 獲取obj對象的該成員變量的值。
setXXX(Object obj, XXX val) // 將 obj 對象的該成員變量設置成val值。

//生成新的對象:用newInstance()方法 
Object obj = class1.newInstance();
//獲取age成員變量
Field field = class1.getField("age");
//將obj對象的age的值設置為10
field.setInt(obj, 10);
//獲取obj對象的age的值
field.getInt(obj);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,933評論 18 139
  • 一、概述 Java反射機制定義 Java反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類中的所有屬性和方法...
    CoderZS閱讀 1,650評論 0 26
  • 你是不是感覺金錢永遠不夠? 你是不是被世俗的要求和社會的競爭追趕著,感到身心疲憊? 你是不是想擺脫金錢帶來的匱乏感...
    瀞好如琳閱讀 3,572評論 0 0
  • @夏雨半支煙:買川投能源好過買國投電力,川投能源沒那么瘋狂擴張,資金充足,管理費用低。國投每年都會買火電站,賣火電...
    新興市場的小邏輯閱讀 115評論 0 0