一、什麼是反射?

反射(Reflection)是Java語言的一種高級特性,它允許程序在運行時獲取類的信息、創建對象、調用方法和操作屬性,而不需要在編譯期知道具體的類信息。這種動態性使得Java具備了類似動態編程語言的靈活性。

反射的核心價值

  • 打破編譯期的類型束縛,實現動態操作類和對象
  • 提高代碼的通用性和可擴展性
  • 是許多框架(如Spring、MyBatis)的底層實現基礎

沒有反射的世界

在傳統編程模式中,我們必須在編譯期就確定要使用的類:

// 定義一個學生類
package com.example.reflection.basic;

public class Student {
    private int id;
    private String name;

    public Student() {}
    
    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // getter和setter方法
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "'}";
    }
}
// 傳統方式使用類
package com.example.reflection.demo;

import com.example.reflection.basic.Student;

public class NoReflectionDemo {
    public static void main(String[] args) {
        // 編譯期必須知道Student類的存在
        Student student = new Student();
        student.setId(1);
        student.setName("張三");
        System.out.println(student);
    }
}

這種方式的侷限性很明顯:如果編譯時Student類不存在,代碼會直接編譯失敗。

有反射的世界

使用反射,我們可以在運行時動態操作類:

// 反射方式使用類
package com.example.reflection.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 類名可以通過配置文件或用户輸入動態獲取
        Class<?> clazz = Class.forName("com.example.reflection.basic.Teacher");
        
        // 創建對象
        Constructor<?> constructor = clazz.getConstructor();
        Object teacher = constructor.newInstance();
        
        // 操作屬性
        Field idField = clazz.getDeclaredField("id");
        idField.setAccessible(true);  // 突破訪問權限限制
        idField.set(teacher, 1001);
        
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(teacher, "李老師");
        
        System.out.println(teacher);
    }
}

對應的Teacher類:

package com.example.reflection.basic;

public class Teacher {
    private int id;
    private String name;

    public Teacher() {}
    
    @Override
    public String toString() {
        return "Teacher{id=" + id + ", name='" + name + "'}";
    }
}

關鍵區別:使用反射時,Teacher類在編譯期可以不存在,只要運行時能被正確加載即可。

二、獲取Class對象的四種方式

Class對象是反射的入口,每種數據類型在JVM中都有且只有一個對應的Class對象。獲取Class對象有四種方式:

1. 類型名.class

適用於所有數據類型(基本類型、引用類型、void等):

package com.example.reflection.classobject;

public class ClassObjectDemo1 {
    public static void main(String[] args) {
        Class<?> c1 = int.class;              // 基本類型
        Class<?> c2 = String.class;           // 類類型
        Class<?> c3 = int[].class;            // 數組類型
        Class<?> c4 = Runnable.class;         // 接口類型
        Class<?> c5 = void.class;             // void類型
        Class<?> c6 = Override.class;         // 註解類型
        
        System.out.println(c1.getName());  // int
        System.out.println(c2.getName());  // java.lang.String
        System.out.println(c3.getName());  // [I
    }
}

2. 對象.getClass()

適用於所有引用類型的對象,返回對象的運行時類型:

package com.example.reflection.classobject;

public class ClassObjectDemo2 {
    public static void main(String[] args) {
        Object str = "Hello";
        Object num = 123;  // 自動裝箱為Integer
        
        Class<?> c1 = str.getClass();
        Class<?> c2 = num.getClass();
        
        System.out.println(c1.getName());  // java.lang.String
        System.out.println(c2.getName());  // java.lang.Integer
    }
}

3. Class.forName(“全類名”)

適用於所有引用類型,通過類的全限定名獲取Class對象:

package com.example.reflection.classobject;

public class ClassObjectDemo3 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加載java核心類庫
        Class<?> c1 = Class.forName("java.util.ArrayList");
        // 加載自定義類
        Class<?> c2 = Class.forName("com.example.reflection.basic.Student");
        
        System.out.println(c1.getName());  // java.util.ArrayList
        System.out.println(c2.getName());  // com.example.reflection.basic.Student
    }
}

4. 類加載器.loadClass(“全類名”)

通過類加載器獲取Class對象:

package com.example.reflection.classobject;

public class ClassObjectDemo4 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 獲取系統類加載器
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        
        Class<?> c1 = classLoader.loadClass("java.lang.String");
        Class<?> c2 = classLoader.loadClass("com.example.reflection.basic.Teacher");
        
        System.out.println(c1.getName());  // java.lang.String
        System.out.println(c2.getName());  // com.example.reflection.basic.Teacher
    }
}

重要結論

  • 同一類型的Class對象是唯一的:String.class == "hello".getClass() 結果為true
  • 不同類型的Class對象一定不同:int.class != Integer.class
  • 數組的Class對象由元素類型和維度共同決定:int[].class != int[][].class

三、反射的核心應用

3.1 查看類的詳細信息

通過反射可以獲取類的各種元信息,包括包名、父類、接口、字段、方法、構造器等。

package com.example.reflection.introspection;

import java.lang.reflect.*;

public class ClassInfoDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 獲取String類的Class對象
        Class<?> clazz = Class.forName("java.lang.String");
        
        // 1. 獲取包信息
        Package pkg = clazz.getPackage();
        System.out.println("包名: " + pkg.getName());
        
        // 2. 獲取類修飾符
        int modifiers = clazz.getModifiers();
        System.out.println("修飾符: " + Modifier.toString(modifiers));  // public final
        
        // 3. 獲取父類
        Class<?> superClass = clazz.getSuperclass();
        System.out.println("父類: " + superClass.getName());  // java.lang.Object
        
        // 4. 獲取實現的接口
        Class<?>[] interfaces = clazz.getInterfaces();
        System.out.println("實現的接口:");
        for (Class<?> iface : interfaces) {
            System.out.println("  - " + iface.getName());
        }
        
        // 5. 獲取成員變量
        Field[] fields = clazz.getDeclaredFields();
        System.out.println("\n成員變量:");
        for (Field field : fields) {
            System.out.println("  - " + Modifier.toString(field.getModifiers()) + 
                             " " + field.getType().getSimpleName() + 
                             " " + field.getName());
        }
        
        // 6. 獲取構造器
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        System.out.println("\n構造器:");
        for (Constructor<?> c : constructors) {
            System.out.println("  - " + c);
        }
        
        // 7. 獲取方法
        Method[] methods = clazz.getDeclaredMethods();
        System.out.println("\n方法 (前5個):");
        for (int i = 0; i < Math.min(5, methods.length); i++) {
            System.out.println("  - " + methods[i].getName() + "()");
        }
    }
}

3.2 反射創建對象

通過反射可以使用類的構造器創建對象,即使構造器是私有的。

情況1:訪問公有的構造器
package com.example.reflection.objectcreation;

import com.example.reflection.basic.Student;
import java.lang.reflect.Constructor;

public class CreateObjectDemo1 {
    public static void main(String[] args) throws Exception {
        // 1. 獲取Class對象
        Class<?> clazz = Class.forName("com.example.reflection.basic.Student");
        
        // 2. 獲取無參構造器並創建對象
        Constructor<?> constructor1 = clazz.getDeclaredConstructor();
        Student student1 = (Student) constructor1.newInstance();
        student1.setId(1);
        student1.setName("張三");
        System.out.println(student1);
        
        // 3. 獲取有參構造器並創建對象
        Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class);
        Student student2 = (Student) constructor2.newInstance(2, "李四");
        System.out.println(student2);
    }
}
情況2:訪問私有的構造器
package com.example.reflection.objectcreation;

import java.lang.reflect.Constructor;

// 測試類:包含私有構造器
class PrivateConstructorClass {
    private int value;
    
    // 私有構造器
    private PrivateConstructorClass(int value) {
        this.value = value;
    }
    
    @Override
    public String toString() {
        return "PrivateConstructorClass{value=" + value + "}";
    }
}

public class CreateObjectDemo2 {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = PrivateConstructorClass.class;
        
        // 獲取私有構造器
        Constructor<?> constructor = clazz.getDeclaredConstructor(int.class);
        
        // 關鍵:設置可訪問性,突破private限制
        constructor.setAccessible(true);
        
        // 創建對象
        PrivateConstructorClass obj = (PrivateConstructorClass) constructor.newInstance(100);
        System.out.println(obj);  // 輸出: PrivateConstructorClass{value=100}
    }
}

3.3 反射操作屬性

通過反射可以讀寫對象的屬性,包括私有屬性。

package com.example.reflection.fieldoperation;

import com.example.reflection.basic.Student;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class FieldOperationDemo {
    public static void main(String[] args) throws Exception {
        // 1. 創建對象
        Class<?> clazz = Student.class;
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        Student student = (Student) constructor.newInstance();
        
        // 2. 操作實例屬性
        // 獲取私有屬性id
        Field idField = clazz.getDeclaredField("id");
        idField.setAccessible(true);  // 突破訪問限制
        idField.set(student, 1001);   // 設置屬性值
        
        // 獲取私有屬性name
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(student, "王五");
        
        // 讀取屬性值
        System.out.println("id: " + idField.get(student));
        System.out.println("name: " + nameField.get(student));
        System.out.println(student);
        
        // 3. 操作靜態屬性
        // 假設有一個帶靜態屬性的類
        Class<?> staticClass = StaticFieldClass.class;
        Field staticField = staticClass.getDeclaredField("count");
        staticField.setAccessible(true);
        
        // 靜態屬性不需要對象實例,用null代替
        System.out.println("修改前: " + staticField.get(null));
        staticField.set(null, 10);
        System.out.println("修改後: " + staticField.get(null));
    }
}

// 帶靜態屬性的測試類
class StaticFieldClass {
    private static int count = 0;
}

3.4 反射調用方法

通過反射可以調用對象的方法,包括私有方法和靜態方法。

package com.example.reflection.methodinvocation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

// 測試用的方法類
class MethodDemoClass {
    // 公有實例方法
    public int add(int a, int b) {
        return a + b;
    }
    
    // 私有實例方法
    private String concat(String s1, String s2) {
        return s1 + s2;
    }
    
    // 靜態方法
    public static void printInfo() {
        System.out.println("這是一個靜態方法");
    }
}

public class MethodInvocationDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = MethodDemoClass.class;
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        MethodDemoClass obj = (MethodDemoClass) constructor.newInstance();
        
        // 1. 調用公有實例方法
        Method addMethod = clazz.getDeclaredMethod("add", int.class, int.class);
        Object result1 = addMethod.invoke(obj, 10, 20);
        System.out.println("10 + 20 = " + result1);
        
        // 2. 調用私有實例方法
        Method concatMethod = clazz.getDeclaredMethod("concat", String.class, String.class);
        concatMethod.setAccessible(true);  // 突破私有限制
        Object result2 = concatMethod.invoke(obj, "Hello", " World");
        System.out.println("字符串拼接: " + result2);
        
        // 3. 調用靜態方法
        Method staticMethod = clazz.getDeclaredMethod("printInfo");
        staticMethod.invoke(null);  // 靜態方法不需要實例,用null
    }
}

四、反射的實際應用場景

4.1 ORM框架核心原理(對象關係映射)

ORM框架(如MyBatis、Hibernate)利用反射將數據庫表記錄自動映射為Java對象:

package com.example.reflection.orm;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

// 模擬數據庫查詢結果映射
public class ORMDemo {
    // 模擬數據庫查詢結果
    static class DBResult {
        String[] columnNames = {"id", "name", "age"};
        Object[][] data = {
            {1, "張三", 20},
            {2, "李四", 22}
        };
    }
    
    // Java實體類
    static class User {
        private int id;
        private String name;
        private int age;
        
        @Override
        public String toString() {
            return "User{id=" + id + ", name='" + name + "', age=" + age + "}";
        }
    }
    
    // 將查詢結果映射為對象列表
    public static <T> List<T> mapResult(Class<T> clazz, DBResult result) throws Exception {
        List<T> list = new ArrayList<>();
        Constructor<T> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        
        // 遍歷每條記錄
        for (Object[] row : result.data) {
            T obj = constructor.newInstance();
            
            // 遍歷每個字段
            for (int i = 0; i < result.columnNames.length; i++) {
                String columnName = result.columnNames[i];
                Object value = row[i];
                
                // 查找對應的屬性並賦值
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(obj, value);
            }
            
            list.add(obj);
        }
        
        return list;
    }
    
    public static void main(String[] args) throws Exception {
        DBResult result = new DBResult();
        List<User> users = mapResult(User.class, result);
        
        for (User user : users) {
            System.out.println(user);
        }
    }
}

4.2 動態代理模式

動態代理是AOP(面向切面編程)的基礎,而動態代理的實現依賴反射:

package com.example.reflection.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定義接口
interface Animal {
    void move();
}

// 2. 實現類
class Dog implements Animal {
    @Override
    public void move() {
        System.out.println("狗在跑");
    }
}

class Bird implements Animal {
    @Override
    public void move() {
        System.out.println("鳥在飛");
    }
}

// 3. 代理處理器
class LogInvocationHandler implements InvocationHandler {
    private Object target;  // 被代理的對象
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    // 代理方法:在目標方法前後添加日誌
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法開始執行: " + method.getName());
        Object result = method.invoke(target, args);  // 反射調用目標方法
        System.out.println("方法執行結束: " + method.getName());
        return result;
    }
}

// 4. 動態代理測試
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 創建被代理對象
        Animal dog = new Dog();
        
        // 創建代理對象
        Animal dogProxy = (Animal) Proxy.newProxyInstance(
            dog.getClass().getClassLoader(),  // 類加載器
            dog.getClass().getInterfaces(),   // 實現的接口
            new LogInvocationHandler(dog)     // 代理處理器
        );
        
        // 通過代理對象調用方法
        dogProxy.move();
        
        System.out.println("-------------------");
        
        // 對另一個實現類創建代理
        Animal bird = new Bird();
        Animal birdProxy = (Animal) Proxy.newProxyInstance(
            bird.getClass().getClassLoader(),
            bird.getClass().getInterfaces(),
            new LogInvocationHandler(bird)
        );
        birdProxy.move();
    }
}

運行結果:

方法開始執行: move
狗在跑
方法執行結束: move
-------------------
方法開始執行: move
鳥在飛
方法執行結束: move

4.3 框架配置文件解析

Spring等框架通過配置文件+反射創建對象,實現控制反轉(IOC):

package com.example.reflection.ioc;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.util.Properties;

// 模擬Spring IOC容器
public class SimpleIOC {
    private Properties props = new Properties();
    
    // 加載配置文件
    public void load(String configFile) throws Exception {
        props.load(new FileInputStream(configFile));
    }
    
    // 根據ID獲取Bean
    public Object getBean(String id) throws Exception {
        // 從配置文件獲取類名
        String className = props.getProperty(id);
        if (className == null) {
            throw new RuntimeException("未找到ID為" + id + "的Bean配置");
        }
        
        // 反射創建對象
        Class<?> clazz = Class.forName(className);
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        return constructor.newInstance();
    }
    
    public static void main(String[] args) throws Exception {
        SimpleIOC container = new SimpleIOC();
        container.load("src/main/resources/beans.properties");
        
        // 從容器獲取對象
        Object userService = container.getBean("userService");
        Object orderDao = container.getBean("orderDao");
        
        System.out.println("獲取的對象: " + userService);
        System.out.println("獲取的對象: " + orderDao);
    }
}

// 測試用的服務類
class UserService {
    // 業務邏輯...
}

class OrderDao {
    // 數據訪問邏輯...
}

配置文件beans.properties

userService=com.example.reflection.ioc.UserService
orderDao=com.example.reflection.ioc.OrderDao

五、反射的優缺點

優點

  1. 靈活性高:可以在運行時動態操作類和對象,不受編譯期限制
  2. 可擴展性強:通過配置文件即可擴展功能,無需修改源代碼
  3. 框架基礎:是許多優秀框架(Spring、MyBatis)的底層實現基礎
  4. 代碼複用:可以編寫通用代碼處理不同類型的對象

缺點

  1. 性能開銷:反射操作比直接調用慢,因為需要解析字節碼
  2. 安全性問題:可以突破訪問權限限制,可能破壞封裝性
  3. 代碼可讀性差:反射代碼相對複雜,不如直接調用直觀
  4. 編譯期檢查缺失:反射調用的錯誤只能在運行時發現

六、反射使用注意事項

  1. 性能優化
  • 緩存Class對象、Method對象等反射對象
  • 避免在循環中使用反射
  • 必要時使用setAccessible(true)提高訪問速度
  1. 安全性處理
  • 謹慎使用setAccessible(true),避免破壞封裝
  • 對於JDK核心類庫的私有成員,可能需要添加VM參數(如--add-opens
  1. 異常處理
  • 反射操作會拋出多種受檢異常,必須妥善處理
  • 常見異常:ClassNotFoundExceptionNoSuchMethodExceptionIllegalAccessException
  1. 版本兼容性
  • 反射依賴類的結構,如果類發生變更(如方法名修改),反射代碼可能失效

七、總結

反射是Java中一種強大的機制,它賦予程序在運行時操作類和對象的能力,是許多高級特性和框架的基礎。掌握反射不僅能幫助我們理解框架的工作原理,還能在需要動態處理類和對象的場景中發揮重要作用。

然而,反射也是一把雙刃劍,它在帶來靈活性的同時也引入了性能開銷和安全隱患。因此,在使用反射時需要權衡利弊,只在必要的場景下使用,並做好相應的優化和安全處理。

通過本文的學習,相信你已經對Java反射機制有了全面的瞭解,能夠在實際開發中靈活運用反射解決問題。