一、什麼是反射?
反射(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
五、反射的優缺點
優點
- 靈活性高:可以在運行時動態操作類和對象,不受編譯期限制
- 可擴展性強:通過配置文件即可擴展功能,無需修改源代碼
- 框架基礎:是許多優秀框架(Spring、MyBatis)的底層實現基礎
- 代碼複用:可以編寫通用代碼處理不同類型的對象
缺點
- 性能開銷:反射操作比直接調用慢,因為需要解析字節碼
- 安全性問題:可以突破訪問權限限制,可能破壞封裝性
- 代碼可讀性差:反射代碼相對複雜,不如直接調用直觀
- 編譯期檢查缺失:反射調用的錯誤只能在運行時發現
六、反射使用注意事項
- 性能優化:
- 緩存Class對象、Method對象等反射對象
- 避免在循環中使用反射
- 必要時使用
setAccessible(true)提高訪問速度
- 安全性處理:
- 謹慎使用
setAccessible(true),避免破壞封裝 - 對於JDK核心類庫的私有成員,可能需要添加VM參數(如
--add-opens)
- 異常處理:
- 反射操作會拋出多種受檢異常,必須妥善處理
- 常見異常:
ClassNotFoundException、NoSuchMethodException、IllegalAccessException等
- 版本兼容性:
- 反射依賴類的結構,如果類發生變更(如方法名修改),反射代碼可能失效
七、總結
反射是Java中一種強大的機制,它賦予程序在運行時操作類和對象的能力,是許多高級特性和框架的基礎。掌握反射不僅能幫助我們理解框架的工作原理,還能在需要動態處理類和對象的場景中發揮重要作用。
然而,反射也是一把雙刃劍,它在帶來靈活性的同時也引入了性能開銷和安全隱患。因此,在使用反射時需要權衡利弊,只在必要的場景下使用,並做好相應的優化和安全處理。
通過本文的學習,相信你已經對Java反射機制有了全面的瞭解,能夠在實際開發中靈活運用反射解決問題。