Java 中的 反射機制
概念明確
什麼是類的物件?
類的物件就是:基於某個類 new 出來的物件,也稱為例項物件。這個很容易理解,就不過多闡述了。
什麼是類物件?
類物件就是:類載入的產物,封裝了一個類的所有資訊(類名、父類、介面、屬性、方法、構造方法)。
包含類資訊的.class檔案被JVM載入到記憶體後,一個個的類就變成了一個個的 java.long.Class 物件,每個類有且只有一個java.long.Class物件,這些物件包含了類的全部資訊,這些物件儲存在方法區中。
每一個物件例項和建立它的類物件之間都有一條型別引用,代表著此例項是這個型別的。
反射機制
什麼是反射機制?
在 Java 中要使用一個類首先要將該類載入到記憶體中,系統會為該類生成一個java.lang.Class的例項。這個 Class 物件的作用很大,通過它系統可以訪問到JVM中該類的資訊,同時 Class 物件也是實現 Java 反射機制的核心要素。
反射(Reflection)是指:
程式可以訪問、檢測和修改它本身狀態或行為的一種能力,並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
上面的解釋並不太通俗易懂,我們看看下面的闡述:
在高階語言中,允許改變程式結構或變數型別的語言稱為動態語言,例如Perl、Python、Ruby等就是動態語言,而像C、C++、Java這類語言在程式編譯時就確定了程式的結構和變數的型別,因此不是動態語言。儘管如此,Java還是為開發者提供了一個非常有用的與動態相關的機制-反射(Reflection)。
運用反射機制可以在執行時載入和使用編譯期間未知的型別。也就是說,Java程式可以載入在執行時才得知類名的class,並生成其物件實體,或訪問其屬性,或喚起其成員方法。通俗點講,所謂Java的反射機制,就是在Java程式執行時動態地載入並使用在編譯期並不知道的類。
反射機制的作用
① 獲取一個類的資訊。
② 通過型別的名稱動態生成並操作物件。
獲取一個類的Class物件
通過一個類的Class物件可以獲取該類的資訊,包括類的建構函式、屬性、方法等,那麼如何來獲取一個類的Class物件呢?
在Java中獲取一個類的Class物件的方法有3種:
➷ Class.forName(className)。『推薦使用』
➷ 呼叫某個類的class屬性獲取該類對應的Class物件。
➷ 呼叫物件的getClass()方法獲取該物件所屬類對應的Class物件。
程式碼演示:
// Person 類 public class Person implements Serializable, Cloneable{ private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public void eat() { System.out.println(name + "正在吃東西。。。"); } public void eat(String food) { System.out.println(name + "正在吃" + food + "。。。"); } private void privateMethod() { System.out.println("這是一個私有方法"); } public static void staticMethod() { System.out.println("這是一個靜態方法"); } }
public class Test { public static void main(String[] args) throws Exception { // 1.建立類的物件 Person zhangsan = new Person("張三", 18); System.out.println(zhangsan.toString()); // 2.獲取類物件 // 方式一:通過類的物件,獲取類物件 Person s = new Person(); Class<?> c = s.getClass(); System.out.println(c.toString()); // 方式二:通過類名獲取類物件 Class<?> c1 = Person.class; System.out.println(c1.toString()); // 方式三:通過靜態方法獲取類物件 [推薦使用] Class<?> c2 = Class.forName("com.ruoli.reflect.Person"); System.out.println(c2.toString()); } }
執行結果:
一般情況下生成一個類的物件是不需要使用反射的。但是有些特殊的情況必須用到反射才能實現類的例項化。比如要對一個類進行操作,但是這個類的類名需要從配置檔案中讀取,事先並不知道該類的類名等資訊。這樣在編譯時就無法確定該類的類名,也就無法按照傳統的方法構建類的物件了。所以可以先讀取配置檔案並拿到這個類的全類名,然後利用反射生成該類的物件,並進行相應的操作。一個很好的例子就是設計模式中的簡單工廠模式。
綜上所述,所以大多數情況下都是使用Class.forName(className)這種方法來獲取類物件的,也推薦大家在日常程式設計中多實用這種方法。
獲取類的名字,包名,父類,介面
程式碼演示:
public class Test { public static void main(String[] args) throws Exception { // 1.獲取類物件 Class<?> c2 = Class.forName("com.ruoli.reflect.Person"); // 2.獲取類的名字 System.out.println("類名:" + c2.getName()); // 3.獲取類的包名 System.out.println("包名:" + c2.getPackage().getName()); // 4.獲取類的父類 System.out.println("父類:" + c2.getSuperclass().getName()); // 5.獲取類的介面 Class<?>[] classes = c2.getInterfaces(); System.out.println("介面:" + Arrays.toString(classes)); // 6.獲取類的簡稱 System.out.println("簡稱:" + c2.getSimpleName()); } }
執行結果:
獲取類的構造方法,並建立物件
程式碼演示:
public class Test { public static void main(String[] args) throws Exception { // 1.獲取類物件 Class<?> c2 = Class.forName("com.ruoli.reflect.Person"); // 2.獲取類的構造方法 // 2.1獲取全部的構造方法 // Constructor<?>[] cons = c2.getConstructors(); // for (Constructor<?> constructor : cons) { // System.out.println(constructor.toString()); // } // 2.2獲取無參的構造方法,建立物件 // Constructor<?> con = c2.getConstructor(); // System.out.println(con.toString()); // // Person zhangsan = (Person)con.newInstance(); // System.out.println(zhangsan.toString()); // 簡單方法:實際上就是呼叫上面的無參構造 // Person xiaoming = (Person)c2.newInstance(); // System.out.println(xiaoming.toString()); // 2.3獲取帶參的構造方法,建立物件 Constructor<?> con = c2.getConstructor(String.class, int.class); System.out.println(con.toString()); Person xiaoming = (Person)con.newInstance("小明", 19); System.out.println(xiaoming.toString()); } }
執行結果:
獲取類中的方法,並呼叫方法
程式碼演示:
public class Test { public static void main(String[] args) throws Exception { // 1.獲取類物件 Class<?> c2 = Class.forName("com.ruoli.reflect.Person"); // 2.獲取類的方法 // 2.1 getMethods() 只能獲取公開的方法,包括從父類繼承的方法 // Method[] methods = c2.getMethods(); // for (Method method : methods) { // System.out.println(method.toString()); // } // 2.2 getDeclaredMethods() 獲取類中的所有方法,包括私有、預設、保護的方法,不包含繼承的 // Method[] methods = c2.getDeclaredMethods(); // for (Method method : methods) { // System.out.println(method.toString()); // } // 2.3獲取單個方法 // 獲取無參、無返回值的方法eat(): Method eatMethod = c2.getMethod("eat"); System.out.println(eatMethod.toString()); Person zhangsan = (Person)c2.newInstance(); eatMethod.invoke(zhangsan); // 獲取無參、有返回值的方法toString(): Method toStringMethod = c2.getMethod("toString"); System.out.println(toStringMethod.toString()); Object result = toStringMethod.invoke(zhangsan); System.out.println(result); // 獲取帶參、無返回值的方法eat(String food): Method eatMethod2 = c2.getMethod("eat", String.class); System.out.println(eatMethod2.toString()); eatMethod2.invoke(zhangsan, "雞腿"); // 獲取私有方法privateMethod() Method privateMethod = c2.getDeclaredMethod("privateMethod"); System.out.println(privateMethod.toString()); // 此時呼叫方法(私有方法),沒有訪問許可權 // 設定訪問許可權無效 privateMethod.setAccessible(true); privateMethod.invoke(zhangsan); // 獲取靜態方法staticMethod() Method staticMethod = c2.getMethod("staticMethod"); System.out.println(staticMethod.toString()); staticMethod.invoke(null); } }
執行結果:
這裡需要注意的是,當我們通過反射獲取一個類的私有方法時,我們是沒有許可權來使用的,因此需要設定訪問許可權,然後才能使用這個私有方法。
獲取類中的屬性
程式碼演示:
public class Test { public static void main(String[] args) throws Exception { // 1.獲取類物件 Class<?> c2 = Class.forName("com.ruoli.reflect.Person"); // 2.獲取屬性 公開的欄位,父類繼承的欄位 // Field[] fields = c2.getFields(); // System.out.println(fields.length); // 2.獲取屬性 獲取所有的屬性,但不包含繼承的 // Field[] fields = c2.getDeclaredFields(); // System.out.println(fields.length); // for (Field field : fields) { // System.out.println(field); // } // 2.獲取單個屬性 Field nameField = c2.getDeclaredField("name"); nameField.setAccessible(true); // 賦值 Person zhangsan = (Person)c2.newInstance(); nameField.set(zhangsan, "張三"); // 獲取值 System.out.println(nameField.get(zhangsan)); } }
執行結果:
反射機制總結
反射機制
所謂Java的反射機制,就是在Java程式執行時動態地載入並使用在編譯期並不知道的類。
反射機制的作用
➷ 反射機制可以動態獲取一個類的資訊,包括該類的屬性和方法,這個功能可應用於對class檔案進行反編譯。
➷ 反射機制也可以通過型別的名稱動態生成物件,並呼叫物件中的方法。
反射機制的優缺點
✔ 優點:可以在執行時獲取一個類的例項,大大提高了系統的靈活性和可擴充套件性。
✘ 缺點:效能較差,安全性不高,破壞了類的封裝性。
&n