Java基礎之反射機制詳解

寫在最前

很高興前一段的文章能給大家帶來幫助,相信大家都清楚基礎知識對于程序員來說是解決問題的根基,筆者的系列文章將從英文定義、思路分析、實例應用一步一步對重要的基礎知識進行講解,沒關注的童鞋趕緊點一波關注,跟筆者一起鞏固你們的基礎吧!!

附前文地址:十個不可忽視的Java基礎知識

上次的文章中提到了Java的反射機制,但有讀者私下反映講解并不透徹,仍然屬于懵圈狀態,因此本文將對其進行詳解。

RTTI和反射?

這是一個經常被混淆的問題。RTTI or Reflection? 相信看過《Thinking in Java》的同學都有這么個疑惑,在Type Information這一章中有這么一段話。

Java allows you to discover information about objects and classes at run time. This takes two forms: "traditional" RTTI, which assumes that you have all the types available at compile time, and the reflection mechanism, which allows you to discover and use class information solely at run time.

這其中,RTTI(Runtime Type Information)是C++ 中的一個概念,B大明顯先學的C++,在這本書之前也是先出版過《Thinking in C++》,因此可能對這個名詞有深厚的感情?總之,千萬不要嘗試去區分RTTI和反射(Reflection)的區別,這兩者就像老人和男人(來自某知乎網友貼切的舉例),分開理解是很蠢的。

這里多說一句,諸如TIJ這種神書,也并不是適合每一個人去理解知識的,在有些篇幅中,如果你發現自己卡住了,一定要多看例子,多查資料,用自己從中悟出的道理去解釋,不要去和死定義糾纏。

反射的目的

很多人在熟練掌握本文所述內容后仍不解RTTI的目的,一般有兩個原因:一是代碼編寫量過小,自己設計的經驗不足,二則是不具備動態的設計思路。我們先來看下面和個例子(出自TIJ):

import java.util.*;

abstract class Shape{
    void draw() {
        System.out.println(this + ".draw() ");
    }
    abstract public String toString();
}

class Circle extends Shape {
    public String toString() {
        return "Circle ";
    }
}

class Square extends Shape {
    public String toString() {
        return "Square ";
    }
}

class Triangle extends Shape {
    public String toString() {
        return "Triangle ";
    }
}

public class Shapes {
    public static void main(String[] args) {
        List<Shape> shapeList = Arrays.asList(
            new Circle(), new Square(), new Triangle()
            );
        for (Shape shape : shapeList) {
            shape.draw();
        }
    }
}

在上例中,我們創建了Shape類,其中Circle, Triangle, 以及Square是他的三個子類。在主方法中我們創建了Shape的list,然后用匿名的方式創建三種子類的引用,不難看出,在這里他們都被強制向上轉換為了Shape。所以輸出的值為:

/* 
Output:
Circle.draw()
Square.draw()
Triangle.draw()
*/

由此可見,我們通過熟悉多態(Polymorphism),也就是在子類中重寫toString()方法,實現了最基礎的在運行中獲取類型信息。利用此種方法,可使你的代碼更易讀寫及維護,設計也更容易被理解,執行或修改。但如果我們不只是需要draw()方法被執行,而需要執行一些并不適用于全部子類的方法,比如rotate(),旋轉,對于Circle來說執行沒有任何意義,那么我們就需要反射機制,我們便可以使Shape調用屬于他子類獨有的方法。

這里光靠定義說明確實是很難理解,舉個通俗的例子吧。比如屏幕上有一大堆各種各樣的水果,蘋果,梨,香蕉。蘋果被點擊時候執行爆炸方法,香蕉被點擊的時候執行剝皮方法,梨被點擊的時候砍成兩半。那么我不知道用戶點擊的是哪個水果,但我希望直接執行這個水果的方法,這時候利用反射機制將會使得我們的代碼簡單已讀且利于修改再開發。

Class類型及對象的獲取方法

在運行時(runtime)承載類型信息的Class對象,你的程序中任何一個類,在你編譯的時候,這個Class對象都會被創建。簡單點說就是編譯后出現的Name.class那個文件,Name是和你的類名相同的名字(創建這個對象的是JVM的一個子系統叫做"class loader")。Java中獲取Class類型的方法有如下三種:

  • .class

所有的引用數據類型我們都可以在類名后加上".class"來獲取Class類型,這種方法是靜態的。這里要多說一句的是,若是基本類型,我們同樣可以使用“封裝類.TYPE”的方法,比如"int.class"對應的是"Integer.TYPE"。

  • Class.forName()

通過一個類的String名來獲取Class類型,這種方法同上一樣為靜態,且必須拋出ClassNotFoundException。

  • getClass()

這種方法是通過實例名來獲取Class類型,是動態的,這點與前兩種不同,注意區分。

綜合來看上述三種方法,我們用一個表格來簡單來區分他們:

方法名 獲取形式
.class 獲取聲明時的類
getClass() 獲取運行時的類
Class.forName() 通過類名來獲得類

在獲得類型之后,在后面加一個newInstance()方法,便能創建對象,例如:

Class cc = Class.forName("Daniel");

Object oo = cc.newInstance();
//這里調用的是關于cc的無參數構造方法

類中屬性的獲取方法

有了Class對象后,我們當然希望對其的方法和參數進行執行或修改,此處我們最常見的便是getDeclaredFields()方法了。例如:

Class cc = Class.forName("Daniel");

Field[] field = cc.getDeclaredFields();  

需要注意的是,類中又很多方法,所以我們應該定義一個Field數組。在這里常用的方法我將列在下面的這個表格當中,需要用時我們只需套用特定的方法即可,理解起來非常容易。

方法 作用
getDeclaredMethods() 獲取所有的方法
getReturnType() 獲得方法的返回類型
getParameterTypes() 獲得方法的傳入參數類型
getDeclaredMethod("",.class,……) 獲得特定的方法
getDeclaredConstructors() 獲取所有的構造方法
getDeclaredConstructor(.class,……) 獲取特定的構造方法
getSuperclass() 獲取某類的父類
getInterfaces() 獲取某類實現的接口

用以上方法進行反編譯,使我們的代碼變得更加靈活,這也是OO類型編程的一個終極目標。

說到這里,我們再一起來看一下上次文章中提到的來自CSDN上的例子:

import java.lang.reflect.Array;     
import java.lang.reflect.Constructor;     
import java.lang.reflect.Field;     
import java.lang.reflect.Method;     
    
    
/**   
 * <a  class='replace_word' title="Java 知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>Reflection Cookbook   
 *   
 * @author Michael Lee   
 * @since 2006-8-23   
 * @version 0.1a   
 */    
    
public class Reflection {     
    /**   
     * 得到某個對象的公共屬性   
     *   
     * @param owner, fieldName   
     * @return 該屬性對象   
     * @throws Exception   
     *   
     */    
    public Object getProperty(Object owner, String fieldName) throws Exception {     
        Class ownerClass = owner.getClass();     
    
        Field field = ownerClass.getField(fieldName);     
    
        Object property = field.get(owner);     
    
        return property;     
    }     
    
    /**   
     * 得到某類的靜態公共屬性   
     *   
     * @param className   類名   
     * @param fieldName   屬性名   
     * @return 該屬性對象   
     * @throws Exception   
     */    
    public Object getStaticProperty(String className, String fieldName)     
            throws Exception {     
        Class ownerClass = Class.forName(className);     
    
        Field field = ownerClass.getField(fieldName);     
    
        Object property = field.get(ownerClass);     
    
        return property;     
    }     
    
    
    /**   
     * 執行某對象方法   
     *   
     * @param owner   
     *            對象   
     * @param methodName   
     *            方法名   
     * @param args   
     *            參數   
     * @return 方法返回值   
     * @throws Exception   
     */    
    public Object invokeMethod(Object owner, String methodName, Object[] args)     
            throws Exception {     
    
        Class ownerClass = owner.getClass();     
    
        Class[] argsClass = new Class[args.length];     
    
        for (int i = 0, j = args.length; i < j; i++) {     
            argsClass[i] = args[i].getClass();     
        }     
    
        Method method = ownerClass.getMethod(methodName, argsClass);     
    
        return method.invoke(owner, args);     
    }     
    
    
      /**   
     * 執行某類的靜態方法   
     *   
     * @param className   
     *            類名   
     * @param methodName   
     *            方法名   
     * @param args   
     *            參數數組   
     * @return 執行方法返回的結果   
     * @throws Exception   
     */    
    public Object invokeStaticMethod(String className, String methodName,     
            Object[] args) throws Exception {     
        Class ownerClass = Class.forName(className);     
    
        Class[] argsClass = new Class[args.length];     
    
        for (int i = 0, j = args.length; i < j; i++) {     
            argsClass[i] = args[i].getClass();     
        }     
    
        Method method = ownerClass.getMethod(methodName, argsClass);     
    
        return method.invoke(null, args);     
    }     
    
    
    
    /**   
     * 新建實例   
     *   
     * @param className   
     *            類名   
     * @param args   
     *            構造函數的參數   
     * @return 新建的實例   
     * @throws Exception   
     */    
    public Object newInstance(String className, Object[] args) throws Exception {     
        Class newoneClass = Class.forName(className);     
    
        Class[] argsClass = new Class[args.length];     
    
        for (int i = 0, j = args.length; i < j; i++) {     
            argsClass[i] = args[i].getClass();     
        }     
    
        Constructor cons = newoneClass.getConstructor(argsClass);     
    
        return cons.newInstance(args);     
    
    }     
    
    
         
    /**   
     * 是不是某個類的實例   
     * @param obj 實例   
     * @param cls 類   
     * @return 如果 obj 是此類的實例,則返回 true   
     */    
    public boolean isInstance(Object obj, Class cls) {     
        return cls.isInstance(obj);     
    }     
         
    /**   
     * 得到數組中的某個元素   
     * @param array 數組   
     * @param index 索引   
     * @return 返回指定數組對象中索引組件的值   
     */    
    public Object getByArray(Object array, int index) {     
        return Array.get(array,index);     
    }     
}

再一次回看我們會發現,以上不過只是將各種方法的應用簡單寫出,并沒有過多的邏輯。簡單來說,這種類型的代碼對于我們來說只是字典型的材料,在設計的過程中即查即用,沒必要逐一去背誦。

結語

關于Java反射部分的知識,本文中也涉及得差不多了。對于反射機制,比起了解語法,更重要的是要去理解它在設計模式中發揮的作用,而設計模式是Java編程思想中相當重要的一環,有關內容在后續的文章中可能還會提及,希望大家持續關注。

文章中若有錯誤或不盡人意之處還請大家指出,歡迎大家私信作者的微博:LightningDC或直接在文章下方回復進行交流。筆者還會繼續為大家更新,歡迎關注,如果文章對你有用,就隨手點個贊吧哈哈,碼字不易。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,155評論 25 708
  • 1. Java中的多態性理解(注意與C++區分) Java中除了static方法和final方法(private方...
    小敏紙閱讀 1,456評論 0 19
  • 其實想要孩子學習好,學校發的習題就夠孩子練得了,一定要學會舉一反三。不然做再多的題目也沒有用,學校留的作業已經夠孩...
    海倫不哭閱讀 181評論 0 0
  • 一直在下雨的北京,穿了一件毛衣還是有些陰冷。昨天才返校,今天卻覺得已經過了很久的樣子。晚自習,看著民法總論的筆記,...
    Skirtz閱讀 204評論 3 2
  • 昨天老公的摯友Q大規模宴請賓客,以慶祝他老家新宅的喬遷,為他喜悅又為自己的淡定喜悅。 不知何時起,已...
    水深流緩閱讀 204評論 1 0