目錄
- 一、反射
- 1.JDK,JRE,JVM的關系
- 2.什么是反射
- 3. 三種獲取Class對象(類的字節碼)的方式
- 4.Class常用方法
- 5. 獲取類的構造器
- 6.反射獲取成員變量&使用
- 7.反射獲取成員方法
- 8.綜合例子
一、反射
1.JDK,JRE,JVM的關系
三者是Java運行環境的核心組成部分,從包含關系上看:JDK 包含JRE包含JVM,具體作用如下:
-
JDK(Java Development Kit,Java開發工具包)
是Java開發的工具集合,包含 JRE + 編譯器(javac)、調試工具(jdb)、文檔工具(javadoc)等開發必需的工具。如果需要編寫、編譯Java程序,必須安裝JDK。 -
JRE(Java Runtime Environment,Java運行時環境)
是運行Java程序的最小環境,包含 JVM + 運行Java程序必需的類庫(如java.lang包等)。如果只需要運行Java程序(如 .class 或 .jar 文件),安裝JRE即可。 -
JVM(Java Virtual Machine,Java虛擬機)
是運行Java字節碼的虛擬計算機,負責將字節碼翻譯成具體操作系統可執行的機器碼。它是Java“一次編寫,到處運行”(跨平臺)的核心,不同操作系統需要安裝對應的JVM實現。 -
字節碼的運行過程:
編譯階段:開發者編寫的Java源代碼( .java 文件),通過JDK中的編譯器(javac)編譯成字節碼文件( .class 文件),字節碼是一種與平臺無關的二進制指令。
類加載:運行時,JVM的類加載器(ClassLoader)將 .class 文件(磁盤或者其他地方)加載到JVM內存中。
字節碼執行:JVM中的執行引擎(如解釋器或即時編譯器JIT)將字節碼翻譯成當前操作系統可識別的機器碼,最終由操作系統執行機器碼,完成程序功能。
簡單來說,字節碼是Java跨平臺的“中間語言”,通過JVM在不同系統上的適配,實現了“一次編譯,到處運行”。
2.什么是反射
? 反射是允許程序在運行期間可以獲取類的類型( Class
對象(字節碼對象)),成員變量,方法并可以動態的創建對象,訪問屬性,調用方法之類的操作。例如在往spring容器中注入一個bean對象,就是通過指定bean的class類型,然后spring框架在啟動的時候就可以創建出這個bean對象。
具體:
1.“獲取類的類型” 的本質:
這里的 “類型” 其實就是 Class 對象(字節碼對象),它是反射的入口。比如 Spring 中指定 class="com.example.User",框架會通過 Class.forName("com.example.User") 獲取 Class 對象,進而操作這個類。
2.動態性的核心體現:
你提到的 “動態創建對象、訪問屬性、調用方法”,核心是不需要在編譯期寫死具體類名或方法名。例如 Spring 配置文件中改一下 class 屬性的值,無需重新編譯代碼,就能創建不同的對象,這就是反射動態性的價值。
3.框架中的典型流程:
以 Spring 注入 Bean 為例,反射的具體步驟是:
讀取配置文件中的 class 屬性(全類名);
通過 Class.forName() 獲取 Class 對象;
通過 Constructor.newInstance() 動態創建實例(反射創建對象);
若有屬性注入,再通過 Field.set() 動態設置屬性值(反射操作屬性)。
3. 三種獲取Class對象(類的字節碼)的方式
方法一:直接通過一個 class 的靜態變量 class 獲取:
Class cls = String.class;
方法二:如果我們有一個實例變量,可以通過該實例變量提供的 getClass () 方法獲取:
String s = "Hello";
Class cls = s.getClass();
方法三:如果知道一個 class 的完整類名,可以通過靜態方法 Class.forName () 獲取:
Class cls = Class.forName("java.lang.String");
注意:因為 Class 實例在 JVM 中是唯一的,所以,上述方法獲取的 class 實例是同一個實例。可以用 == 或hashcode()比較實例。
eg:
public class Test2 {public static void main(String[] args) throws ClassNotFoundException {Class class1 = Class.forName("第二周.day3.Order");Class<Order> class2 = Order.class;Class class3 = new Order().getClass();System.out.println(class1.hashCode());//2003749087System.out.println(class2.hashCode());//2003749087System.out.println(class3.hashCode());//2003749087}
}
class Order{static {System.out.println("靜態代碼塊中Order已經執行!!!");}
}
4.Class常用方法
獲取父類的Class,獲取實現接口的Class:
public class Test3 {public static void main(String[] args) {Class<String> stringClass = String.class;System.out.println("完全限定類名:" + stringClass.getName());System.out.println("完全限定類名:" + stringClass.getTypeName());System.out.println("僅包含類名:" + stringClass.getSimpleName());System.out.println("所在的包名:" + stringClass.getPackage());// 獲取父類的 Class 對象//String 類繼承自 java.lang.Object,所以返回 Object 類的 Class 實例Class<? super String> superclass = stringClass.getSuperclass();System.out.println("父類:" + superclass);// 獲取類實現的接口(String 類實現了多個接口,如 Serializable、Comparable等)Class[] interfaces = stringClass.getInterfaces();for (Class anInterface : interfaces) {System.out.println("接口:" + anInterface);}}
}
判斷繼承關系:
public class Test7 {public static void main(String[] args) {// instanceof 關鍵字:判斷對象是否是某個類型的實例(被注釋示例)// Integer n = 2;// System.out.println(n instanceof Integer); // true(自身類型)// System.out.println(n instanceof Number); // true(父類)// isAssignableFrom:判斷類型B能否賦值給類型A(Class類方法)// 1. Integer能否賦值給Integer(自身類型)System.out.println(Integer.class.isAssignableFrom(Integer.class)); // true// 2. Number能否賦值給Integer(父類→子類,不成立)System.out.println(Integer.class.isAssignableFrom(Number.class)); // false// 3. Integer能否賦值給Number(子類→父類,成立)System.out.println(Number.class.isAssignableFrom(Integer.class)); // true}
}
判斷Class的類型:
public class Test3 {public static void main(String[] args) {// 獲取不同類型的Class對象Class clz1 = String.class; // 普通類Class clz2 = DayOfWeek.class; // 枚舉類Class clz3 = String[].class; // 數組類型Class clz4 = List.class; // 接口// 調用info方法打印類信息info(clz1);info(clz2);info(clz3);info(clz4);}// 打印類的基本類型信息public static void info(Class clazz) {System.out.println("類信息:" + clazz);System.out.println("是否是接口? " + clazz.isInterface());System.out.println("是否是枚舉? " + clazz.isEnum());System.out.println("是否是數組? " + clazz.isArray());System.out.println("---------------------"); // 分隔線}
}
5. 獲取類的構造器
過字節碼對象獲取構造器,并使用構造器創建對象。
get:獲取
Declared: 有這個單詞表示可以獲取任意一個,沒有這個單詞表示只能獲取一個public修飾的
Constructor: 構造方法的意思
后綴s: 表示可以獲取多個,沒有后綴s只能獲取一個
反射獲取構造器的作用:初始化對象并返回。
eg:
package 第二周.day3;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class Test5 {public static void main(String[] args)throws NoSuchMethodException, // 當找不到指定構造方法時拋出InvocationTargetException, // 構造方法執行過程中拋出異常時包裝此異常InstantiationException, // 當類是抽象類或接口等無法實例化時拋出IllegalAccessException // 構造方法訪問權限不足時拋出{// 獲取Animal類的Class對象// Class對象是反射的核心,包含了類的所有元信息(構造方法、方法、字段等)Class clazz = Animal.class;// 獲取指定參數列表的構造方法// getConstructor()方法參數為構造方法的參數類型對應的Class對象Constructor constructor0 = clazz.getConstructor(); // 獲取無參構造方法Constructor constructor1 = clazz.getConstructor(int.class); // 獲取接收int類型參數的構造方法Constructor constructor2 = clazz.getConstructor(int.class, double.class); // 獲取接收int和double類型參數的構造方法// 通過反射獲取的Constructor對象創建Animal實例// newInstance()方法的參數為實際傳遞給構造方法的參數值System.out.println("===========");Object o1 = constructor0.newInstance(); // 調用無參構造方法創建實例// 打印創建的對象(默認調用Object類的toString()方法,輸出類名@哈希碼)System.out.println(o1);System.out.println("===========");Object o2 = constructor1.newInstance(23); // 調用int參數的構造方法,傳入23System.out.println(o2);System.out.println("===========");Object o3 = constructor2.newInstance(23, 3.14); // 調用int和double參數的構造方法,傳入23和3.14System.out.println(o3);}
}class Animal {// 靜態代碼塊:在類加載階段執行,且只執行一次// 用于驗證類加載的時機(當獲取Class對象時會觸發類加載)static {System.out.println("Animal類被加載!!!!");}// 無參構造方法public Animal() {System.out.println("無參構造方法被執行。");}// 接收int類型參數的構造方法public Animal(int a) {System.out.println("有參構造方法被執行:a = " + a);}// 接收int和double類型參數的構造方法public Animal(int a, double b) {System.out.println("有參構造方法被執行:a = " + a + ",b = " + b);}
}
6.反射獲取成員變量&使用
在Class類中提供了獲取成員變量的方法:
再次強調一下設置值、獲取值的方法時Filed類的需要用Filed類的對象來調用,而且不管是設置值、還是獲取值,都需要依賴于該變量所屬的對象。代碼如下:
設置值:
import java.lang.reflect.Field;// 演示 Java 反射機制:通過 Class 對象操作 Employee 類的私有成員
public class Test8 {public static void main(String[] args) throws Exception {// ========== 1. 獲取 Class 對象(反射入口) ==========// 方式:類名.class,獲取 Employee 類的字節碼元數據Class clazz = Employee.class;// ========== 2. 反射創建對象 ==========// 調用無參構造器實例化對象(要求 Employee 有無參構造)Object obj = clazz.newInstance();// ========== 3. 反射獲取私有字段 ==========// getDeclaredField 可獲取私有字段,需配合 setAccessible(true) 突破封裝Field enoField = clazz.getDeclaredField("eno");Field nameField = clazz.getDeclaredField("realName");Field salaryField = clazz.getDeclaredField("salary");// ========== 4. 暴力反射:開啟私有字段訪問權限 ==========// 即使字段是 private,也能通過此方法賦值enoField.setAccessible(true);nameField.setAccessible(true);salaryField.setAccessible(true);// ========== 5. 反射賦值私有字段 ==========// 等價于:obj.eno = "T001"; enoField.set(obj, "T001");// 等價于:obj.realName = "水產張總";nameField.set(obj, "水產張總");// 等價于:obj.salary = 456.778;salaryField.setDouble(obj, 456.778);// ========== 6. 驗證結果 ==========// 調用 Employee 的 toString() 輸出對象內容System.out.println(obj);}
}// 普通 Java 類,包含私有字段和 toString 方法
class Employee {// 私有字段:體現封裝性private String eno;private String realName;private String phoneNumber;private int level;private double salary;// 重寫 toString,自定義對象打印格式@Overridepublic String toString() {return "Employee{" +"eno='" + eno + '\'' +", realName='" + realName + '\'' +", phoneNumber='" + phoneNumber + '\'' +", level=" + level +", salary=" + salary +'}';}
}
取值:
public class Test9 {public static void main(String[] args) throws IllegalAccessException {// 1. 創建 Employee 對象(通過構造器傳參初始化)Employee emp = new Employee("T002", "孫樂", "1310988442", 8, 4567.8);// 2. 調用 info 方法,反射打印對象字段信息info(emp);}public static void info(Object obj) throws IllegalAccessException {// 3. 獲取對象的 Class 對象(反射入口)Class clazz = obj.getClass();// 4. 獲取類的所有**聲明的字段**(包括 private)Field[] fields = clazz.getDeclaredFields();// 5. 遍歷字段,逐個處理for (Field f : fields) {// 6. 暴力反射:突破 private 限制(允許訪問私有字段)f.setAccessible(true);// 7. 獲取字段的類型(判斷是 int、double 還是其他類型)Class fieldType = f.getType();// 8. 根據字段類型,調用不同的 get 方法if (fieldType == int.class) {// int 類型:用 getInt 獲取值System.out.printf("%s = %s%n", f.getName(), f.getInt(obj));} else if (fieldType == double.class) {// double 類型:用 getDouble 獲取值System.out.printf("%s = %s%n", f.getName(), f.getDouble(obj));} else {// 其他類型(如 String):用 get 獲取值System.out.printf("%s = %s%n", f.getName(), f.get(obj));}}}
}// 員工類:包含私有字段和構造器
class Employee {// 私有字段(體現封裝性)private String eno;private String realName;private String phoneNumber;private int level;private double salary;// 無參構造(未使用,但保留)public Employee() {}// 全參構造:用于初始化對象public Employee(String eno, String realName, String phoneNumber, int level, double salary) {this.eno = eno;this.realName = realName;this.phoneNumber = phoneNumber;this.level = level;this.salary = salary;}// 重寫 toString:自定義對象打印格式(main 中未直接調用,但可用于調試)@Overridepublic String toString() {return "Employee{" +"eno='" + eno + '\'' +", realName='" + realName + '\'' +", phoneNumber='" + phoneNumber + '\'' +", level=" + level +", salary=" + salary +'}';}
}
7.反射獲取成員方法
在Java中反射包中,每一個成員方法用Method對象來表示,通過Class類提供的方法可以獲取類中的成員方法對象。
eg:
import java.lang.reflect.Method;// Class 類型:用來封裝某一個class類的類型信息(類名、構造方法、成員變量[字段]、實例方法)
// Constructor類型:用來封裝一個構造方法
// Field類型:用來封裝一個成員變量[字段]
// Method類型:用于封裝一個方法
public class Test10 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 硬編碼// String s = new String("just do IT");// String result = s.substring(0,4);// System.out.println(result);//1、反射第一步:先獲取到Class對象Class clazz = String.class;// 創建實例對象Constructor constructor = clazz.getConstructor(String.class);Object s = constructor.newInstance("just do IT");// 根據方法名稱,獲取對應的Method對象Method method = clazz.getDeclaredMethod("substring", int.class, int.class);// invoke()執行方法// 硬編碼 String result = s.substring(0,4);Object returnValue = method.invoke(s, 0, 4);System.out.println("返回值:" + returnValue);// 獲取String類的所有定義方法// Method[] methods = clazz.getDeclaredMethods();// for (Method method : methods){// System.out.println(method);// }}
}
eg:靜態方法的調用,傳對象為null
import java.lang.reflect.Method;// 靜態方法的調用
public class Test11 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {// ========== 硬編碼方式(被注釋示例) ==========// 直接調用 Math.log10(567),并輸出結果+1// int result = (int) Math.log10(567);// System.out.println(result + 1);// ========== 反射方式調用靜態方法 ==========// 1. 獲取 Math 類的 Class 對象Class clazz = Math.class;// 2. 獲取靜態方法 log10:參數類型是 doubleMethod method = clazz.getDeclaredMethod("log10", double.class);// 3. 調用靜態方法:因靜態方法屬于類,而非對象,所以 invoke 的第一個參數傳 nullObject returnValue = method.invoke(null, 567.0);// 4. 輸出反射調用結果System.out.println(returnValue);}
}
8.綜合例子
package 第二周.day3;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;public class Test13 {public static void main(String[] args) {Scanner input = new Scanner(System.in);try {while (true) {System.out.print("請輸入目標工具類名:");String className = input.nextLine(); // 第二周.day3.CommandSystem.out.print("請輸入目標方法名稱:");String methodName = input.nextLine(); // growif ("exit".equals(className) && "null".equals(methodName)) {System.out.println("程序退出了");break;}// 判斷方法是否有參數,并執行該方法Class classzz = Class.forName(className);Method[] declaredMethods = classzz.getDeclaredMethods();Method targetmethod = null;for (Method method : declaredMethods) {if (methodName.equals(method.getName())) {targetmethod = method;break;}}if (targetmethod == null) {System.out.println("目標方法" + methodName + "不存在");continue;}Object[] objs = new Object[targetmethod.getParameterCount()];//遍歷的當前方法有幾個參數,就設置幾次容量,傳幾次參數for (int i = 1; i <= targetmethod.getParameterCount(); i++) {System.out.println("請輸入第:" + i + "個參數");objs[i - 1] = input.nextInt();}//創建實例Constructor constructor = classzz.getConstructor();Object o = constructor.newInstance();targetmethod.invoke(o, objs);}} catch (ClassNotFoundException | NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} finally {input.close();}}
}class Command {public Command() {}public void clear() {System.out.println("執行清空操作!");}public void init(int initSize, int initLocation) {System.out.println("執行初始化操作!");System.out.println("初始化長度 = " + initSize);System.out.println("初始化位置 = " + initLocation);}public void grow(int size) {System.out.println("執行擴容操作!");System.out.println("擴容量 = " + size);}
}