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文件進行反編譯。
➷ 反射機制也可以通過類型的名稱動態生成對象,並調用對象中的方法。
反射機制的優缺點
✔ 優點:可以在運行時獲取一個類的實例,大大提高了系統的靈活性和可擴展性。
✘ 缺點:性能較差,安全性不高,破壞了類的封裝性。