Java 反射教程


這是我的第一篇博客,以此紀念下。

最近在重新梳理Java的基礎知識,這篇文章記一下Java的反射機制

 

1.什么是反射

  “反射(Reflection)能夠讓運行於JVM中的程序檢測和修改運行時的行為。”這個概念常常會和內省(Introspection)混淆,以下是這兩個術語在Wikipedia中的解釋:

    1. 內省用於在運行時檢測某個對象的類型和其包含的屬性;  
    2. 反射用於在運行時檢測和修改某個對象的結構及其行為。

 

  內省示例:instanceof 運算符用於檢測某個對象是否屬於特定的類。

if (obj instanceof Dog) {
    Dog d = (Dog) obj;
    d.bark();
}

 

  反射示例:Class.forName()方法可以通過類或接口的名稱(一個字符串或完全限定名)來獲取對應的Class對象。forName方法會觸發類的初始化。

// 使用反射
Class<?> c = Class.forName("classpath.and.classname");
Object dog = c.newInstance();
Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
m.invoke(dog);

  在Java中,反射更接近於內省,因為你無法改變一個對象的結構。雖然一些API可以用來修改方法和屬性的可見性,但並不能修改結構。

2.為何需要反射?

  反射能夠讓我們:

  • 在運行時檢測對象的類型;
  • 動態構造某個類的對象;
  • 檢測類的屬性和方法;
  • 任意調用對象的方法;
  • 修改構造函數、方法、屬性的可見性;
  • 以及其他。

  反射也是框架中常用的方法。

 

  例如,JUnit通過反射來遍歷包含 @Test 注解的方法,並在運行單元測試時調用它們。

  對於Web框架,開發人員在配置文件中定義他們對各種接口和類的實現。通過反射機制,框架能夠快速地動態初始化所需要的類。

  例如,Spring框架使用如下的配置文件:

<bean id="someID" class="com.programcreek.Foo">
    <property name="someField" value="someValue" />
</bean>

  當Spring容器處理<bean>元素時,會使用Class.forName("com.programcreek.Foo")來初始化這個類,並再次使用反射獲取<property>元素對應的setter方法,為對象的屬性賦值。

  Servlet也會使用相同的機制:

<servlet>
    <servlet-name>someServlet</servlet-name>
    <servlet-class>com.programcreek.WhyReflectionServlet</servlet-class>
<servlet>

3.如何使用反射?

  示例1:獲取對象的類型名稱。

  

package myreflection;
import java.lang.reflect.Method;
 
public class ReflectionHelloWorld {
    public static void main(String[] args){
        Foo f = new Foo();
        System.out.println(f.getClass().getName());         
    }
}
 
class Foo {
    public void print() {
        System.out.println("abc");
    }
}

  輸出: myreflection.Foo

  

  示例2:調用未知對象的方法

  在下列代碼中,設想對象的類型是未知的。通過反射,我們可以判斷它是否包含print方法,並調用它。

  

package myreflection;
import java.lang.reflect.Method;
 
public class ReflectionHelloWorld {
    public static void main(String[] args){
        Foo f = new Foo();
 
        Method method;
        try {
            method = f.getClass().getMethod("print", new Class<?>[0]);
            method.invoke(f);
        } catch (Exception e) {
            e.printStackTrace();
        }           
    }
}
 
class Foo {
    public void print() {
        System.out.println("abc");
    }
}
//輸出 : abc

 

  示例3:創建對象

  

package myreflection;
 
public class ReflectionHelloWorld {
    public static void main(String[] args){
        // 創建Class實例
        Class<?> c = null;
        try{
            c=Class.forName("myreflection.Foo");
        }catch(Exception e){
            e.printStackTrace();
        }
 
        // 創建Foo實例
        Foo f = null;
 
        try {
            f = (Foo) c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }   
 
        f.print();
    }
}
 
class Foo {
    public void print() {
        System.out.println("abc");
    }
}

 

  示例4:獲取構造函數,並創建對象。

package myreflection;
 
import java.lang.reflect.Constructor;
 
public class ReflectionHelloWorld {
    public static void main(String[] args){
        // 創建Class實例
        Class<?> c = null;
        try{
            c=Class.forName("myreflection.Foo");
        }catch(Exception e){
            e.printStackTrace();
        }
 
        // 創建Foo實例
        Foo f1 = null;
        Foo f2 = null;
 
        // 獲取所有的構造函數
        Constructor<?> cons[] = c.getConstructors();
 
        try {
            f1 = (Foo) cons[0].newInstance();
            f2 = (Foo) cons[1].newInstance("abc");
        } catch (Exception e) {
            e.printStackTrace();
        }   
 
        f1.print();
        f2.print();
    }
}
 
class Foo {
    String s; 
 
    public Foo(){}
 
    public Foo(String s){
        this.s=s;
    }
 
    public void print() {
        System.out.println(s);
    }
}

//輸出:
// null
// abc

 

  此外,還可以通過Class實例來獲取該類實現的接口、父類、聲明的屬性等

 

  示例5:通過反射來修改數組的大小

package myreflection;
 
import java.lang.reflect.Array;
 
public class ReflectionHelloWorld {
    public static void main(String[] args) {
        int[] intArray = { 1, 2, 3, 4, 5 };
        int[] newIntArray = (int[]) changeArraySize(intArray, 10);
        print(newIntArray);
 
        String[] atr = { "a", "b", "c", "d", "e" };
        String[] str1 = (String[]) changeArraySize(atr, 10);
        print(str1);
    }
 
    // 修改數組的大小
    public static Object changeArraySize(Object obj, int len) {
        Class<?> arr = obj.getClass().getComponentType();
        Object newArray = Array.newInstance(arr, len);
 
        // 復制數組
        int co = Array.getLength(obj);
        System.arraycopy(obj, 0, newArray, 0, co);
        return newArray;
    }
 
    // 打印
    public static void print(Object obj) {
        Class<?> c = obj.getClass();
        if (!c.isArray()) {
            return;
        }
 
        System.out.println("\nArray length: " + Array.getLength(obj));
 
        for (int i = 0; i < Array.getLength(obj); i++) {
            System.out.print(Array.get(obj, i) + " ");
        }
    }
}

  輸出:

Array length: 10
1 2 3 4 5 0 0 0 0 0 
Array length: 10
a b c d e null null null null null

總結

  上述示例僅僅是展示了Java反射機制很小的一部分功能。總之,反射機制是框架技術的原理和核心部分。通過反射機制我們可以動態的通過改變配置文件(以后是XML文件)的方式來加載類、調用類方法,以及使用類屬性。這樣的話,對於編碼和維護帶來相當大的便利。在程序進行改動的時候,也只會改動相應的功能就行了,調用的方法是不用改的。更不會一改就改全身。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM