Java 反射基礎

1、什麼是反射

反射 (Reflection) 是 Java 的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。java

總而言之,經過反射,咱們能夠在運行時得到程序或程序集中每個類型的成員和成員的信息。程序中通常的對象的類型都是在編譯期就肯定下來的,而 Java 反射機制能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。mysql

反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。sql

1.1 反射機制主要提供如下功能

  • 運行時判斷任意一個對象所屬的類。
  • 運行時構造任意一個類的對象。
  • 運行時判斷任意一個類所具備的成員變量和方法。
  • 運行時調用任意一個對象的方法

當咱們的程序在運行時,須要動態的加載一些類,這些類可能以前用不到因此不用加載到jvm,而是在運行時根據須要才加載。數據庫

例如咱們的項目底層有時是用mysql,有時用oracle,須要動態地根據實際狀況加載驅動類,這個時候反射就有用了,假設 com.java.dbtest.mySqlConnection,com.java.dbtest.oracleConnection這兩個類咱們要用,這時候咱們的程序就寫得比較動態化,經過Class tc = Class.forName("com.java.dbtest.mySqlConnection");經過類的全類名讓jvm在服務器中找到並加載這個類,而若是是oracle則傳入的參數就變成另外一個了。這時候就能夠看到反射的好處了,這個動態性就體現出java的特性了!數組

2、反射的主要用途

不少人都認爲反射在實際的 Java 開發應用中並不普遍,其實否則。當咱們在使用 IDE時,咱們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裏就會用到反射。bash

反射最重要的用途就是開發各類通用框架。不少框架(好比 Spring)都是配置化的(好比經過 XML 文件配置 Bean),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射,運行時動態加載須要加載的對象。服務器

3、反射的基本運用

上面咱們提到了反射能夠用於判斷任意對象所屬的類,得到Class對象,構造任意一個對象以及調用一個對象。oracle

3.1 Java 的反射機制的實現藉助於4個類:class,Constructor,Field,Method;

其中class表明的是類對象,Constructor-類的構造器對象,Field-類的屬性對象,Method-類的方法對象,經過這四個對象咱們能夠粗略的看到一個類的各個組成部分。其中最核心的就是Class類,它是實現反射的基礎,它包含的方法咱們在第一部分已經進行了基本的闡述。應用反射時咱們最關心的通常是一個類的構造器、屬性和方法,下面咱們主要介紹Class類中針對這三個元素的方法:框架

3.2 得到Class對象

Class類的實例表示Java應用運行時的類(class and enum)或接口(interface and annotation)(每一個Java類運行時都在JVM裏表現爲一個Class對象,可經過類名.class,類型.getClass(),Class.forName("類名")等方法獲取Class對象)。基本類型boolean,byte,char,short,int,long,float,double和關鍵字void一樣表現爲Class對象。jvm

聲明普通的Class對象,在編譯器並不會檢查Class對象的確切類型是否符合要求,若是存在錯誤只有在運行時才得以暴露出來。可是經過泛型聲明指明類型的Class對象,編譯器在編譯期將對帶泛型的類進行額外的類型檢查,確保在編譯期就能保證類型的正確性。

使用 Class 類的 forName 靜態方法:

public static Class<?> forName(String className)

好比在 JDBC 開發中經常使用此方法加載數據庫驅動:
Class.forName(driver);
複製代碼

直接獲取某一個對象的 class

Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;

複製代碼

調用某個對象的 getClass() 方法

StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
複製代碼

3.3 判斷是否爲某個類的實例

通常地,咱們用 instanceof 關鍵字來判斷是否爲某個類的實例。同時咱們也能夠藉助反射中 Class 對象的 isInstance() 方法來判斷是否爲某個類的實例,它是一個 native 方法:

public native boolean isInstance(Object obj);
複製代碼

3.4 建立實例

使用Class對象的newInstance()方法來建立Class對象對應類的實例

classObj.newInstance() 只可以調用public類型的無參構造函數,此方法是過期的

Class<?> c = String.class;
Object str = c.newInstance();
複製代碼

先經過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來建立實例。

能夠根據傳入的參數,調用任意構造函數,在特定狀況下,能夠調用私有的構造函數,此方法是推薦使用的

//獲取String所對應的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器建立實例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
複製代碼

3.5 獲取方法

getMethods()

返回某個類的全部public方法,包括本身聲明和從父類繼承的

getDeclaredMethods()

獲取全部本類本身的方法,不問訪問權限,不包括從父類繼承的方法

getMethod(String name, Class<?>... parameterTypes)

方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象。

Method getDeclaredMethod(String name, Class<?>... params)

方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象

操做私有方法

/**
 * 訪問對象的私有方法
 * 爲簡潔代碼,在方法上拋出總的異常,實際開發別這樣
 */
private static void getPrivateMethod() throws Exception{
    //1. 獲取 Class 類實例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

    //2. 獲取私有方法
    //第一個參數爲要獲取的私有方法的名稱
    //第二個爲要獲取方法的參數的類型,參數爲 Class...,沒有參數就是null
    //方法參數也可這麼寫 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);

    //3. 開始操做方法
    if (privateMethod != null) {
        //獲取私有方法的訪問權
        //只是獲取訪問權,並非修改實際權限
        privateMethod.setAccessible(true);

        //使用 invoke 反射調用私有方法
        //privateMethod 是獲取到的私有方法
        //testClass 要操做的對象
        //後面兩個參數傳實參
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

複製代碼

3.6 獲取構造函數

Constructor[] getConstructors()

得到類的全部公共構造函數

Constructor getConstructor(Class[] params)

得到使用特殊的參數類型的公共構造函數,

Constructor[] getDeclaredConstructors()

得到類的全部構造函數

Constructor getDeclaredConstructor(Class[] params)

得到使用特定參數類型的構造函數

3.7 獲取成員變量字段

Field[] getFields()

得到類的全部公共字段

Field getField(String name)

得到命名的公共字段

Field[] getDeclaredFields()

得到類聲明的全部字段

Field getDeclaredField(String name)

得到類聲明的命名的字段

修改私有變量

**
 * 修改對象私有變量的值
 * 爲簡潔代碼,在方法上拋出總的異常
 */
private static void modifyPrivateFiled() throws Exception {
    //1. 獲取 Class 類實例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

    //2. 獲取私有變量
    Field privateField = mClass.getDeclaredField("MSG");

    //3. 操做私有變量
    if (privateField != null) {
        //獲取私有變量的訪問權
        privateField.setAccessible(true);

        //修改私有變量,並輸出以測試
        System.out.println("Before Modify:MSG = " + testClass.getMsg());

        //調用 set(object , value) 修改變量的值
        //privateField 是獲取到的私有變量
        //testClass 要操做的對象
        //"Modified" 爲要修改爲的值
        privateField.set(testClass, "Modified");
        System.out.println("After Modify:MSG = " + testClass.getMsg());
    }
}
複製代碼

3.8 調用方法

當咱們從類中獲取了一個方法後,咱們就能夠用 invoke() 方法來調用這個方法。invoke 方法的原型爲:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
複製代碼

下面是一個實例

public class test1 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> klass = methodClass.class;
        //建立methodClass的實例
        Object obj = klass.newInstance();
        //獲取methodClass類的add方法
        Method method = klass.getMethod("add",int.class,int.class);
        //調用method對應的方法 => add(1,4)
        Object result = method.invoke(obj,1,4);
        System.out.println(result);
    }
}
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}
複製代碼

3.9 利用反射建立數組

數組在Java裏是比較特殊的一種類型,它能夠賦值給一個Object Reference。下面咱們看一看利用反射建立數組的例子:

public static void testArray() throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls,25);
        //往數組裏添加內容
        Array.set(array,0,"hello");
        Array.set(array,1,"Java");
        Array.set(array,2,"fuck");
        Array.set(array,3,"Scala");
        Array.set(array,4,"Clojure");
        //獲取某一項的內容
        System.out.println(Array.get(array,3));
    }
複製代碼

其中的Array類爲java.lang.reflect.Array類。咱們經過Array.newInstance()建立數組對象,它的原型是:

public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
    }
複製代碼

而 newArray 方法是一個 native 方法

private static native Object newArray(Class<?> componentType, int length)
        throws NegativeArraySizeException;
複製代碼

3.10 反射修改常量值

判斷可不能夠修改常量
相關文章
相關標籤/搜索