反射
一、反射
通過Class實例獲取class
信息的方法稱為反射(Reflection)
-
JVM為每個加載的
class
及interface
創建了對應的Class
實例來保存class
及interface
的所有信息 -
獲取一個
class
對應的Class
實例后,就可以獲取該class
的所有信息;
二、Class類及其實例
1、Class類
Java程序在運行時,Java運行時系統一直對所有的對象進行所謂的運行時類型標識,即所謂的RTTI。這項信息紀錄了每個對象所屬的類。虛擬機通常使用運行時類型信息選准正確方法去執行,用來保存這些類型信息的類是Class類。Class類封裝一個對象和接口運行時的狀態,當裝載類時,Class類型的對象自動創建。
簡單解釋上面一段話:
1、Class類也是類的一種,只是名字和class關鍵字高度相似。Java是大小寫敏感的語言。
2、Class類的對象內容是你創建的類的類型信息,比如你創建一個shapes類,那么,Java會生成一個內容是shapes的Class類的對象。
3、Class類的對象不能像普通類一樣,以 new shapes() 的方式創建,它的對象只能由JVM創建,因為這個類沒有public構造函數。
4、Class類的作用是運行時提供或獲得某個對象的類型信息。
2、獲取Class實例
1、Class類的靜態方法forName
// 如果知道一個class的完整類名,可以通過靜態方法Class.forName()獲取
Class cls = Class.forName( String className);
Class cls = Class.forName("java.lang.String");
2、對象的getClass()方法
// 如果我們有一個實例變量,可以通過該實例變量提供的getClass()方法獲取
Student student = new Student();
Class<? extends Student> aClass1 = student.getClass();
3、使用類名加.class
// 直接通過一個class的靜態變量class獲取
Class classes = Student.class;
3、獲取基本信息
public class Main {
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
System.out.println("---------------------------------------------");
}
}
三、Class實例操作class的成員變量
public class Student{
private String name;
private int age;
public Student(){}
public Student(String name) {
this.name = name;
}
}
public class Main{
public static void main(String[] args){
...
}
}
1、獲取類的變量
注意:
getField(name)
與getDeclaredField(name)
中 name 可能不存在,源碼中用了throws來拋出可能出現的異常。所以在使用時,必須用try...catch...
來捕獲異常
Field getField(name) // 根據字段名獲取包括父類字段在內的某個public的field()
Field getDeclaredField(name) // 根據字段名獲取當前類的某個field(不包括父類)
Field[] getFields() // 獲取包括父類在內的所有public的field,返回值為數組
Field[] getDeclaredFields() // 獲取當前類的所有field(不包括父類),返回值為數組
Class cls = Student.class;
try{
Field f = cls.getDeclaredField("name");
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
2、獲取變量的屬性
getName() //返回字段名稱
getType() //返回字段類型,也是一個Class實例
getModifiers() // 返回字段的修飾符,它是一個int,不同的bit表示不同的含義。
Class cls = Student.class;
try{
Field f = cls.getDeclaredField("name");
f.getName(); // name
f.getType(); // String
f.getModifiers();
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
3、獲取、修改變量的值
get(obj); // 獲取obj對象xx變量值
set(obj, value); // 設置obj對象的xx變量的值為value
// 獲取s對象name的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
try{
Field f = c.getDeclaredField("name");
f.setAccessible(true); // 由於name為私有變量,外部無法訪問,通過setAccessible(true)可以允許訪問任何權限變量
Object value = f.get(s); // 獲取值
System.out.println(value); // "Xiao Ming"
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
// 設置s對象的age變量的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
Field f = c.getDeclaredField("age");
f.setAccessible(true);
f.set(s, 18) // 設置值
四、Class實例操作class的方法
public class Student{
private String name;
private int age;
public Student(){}
public Student(String name) {
this.name = name;
}
public void setName(String name){
this.name = name
}
public static void add(int a){
a += 1;
}
private void check(){
...
}
}
public class Main{
public static void main(String[] args){
...
}
}
1、獲取類的方法
注意:
getMethod(name)
與getDeclaredMethod(name)
中 name 可能不存在,源碼中用了throws來拋出可能出現的異常,所以在使用時,必須用try...catch...
來捕獲異常
Method getMethod(name, Class...) // 獲取包括父類方法在內的某個public的Method
Method getDeclaredMethod(name, Class...) // 獲取當前類的某個Method(不包括父類)
Method[] getMethods() // 獲取包括父類方法在內的所有public的Method,返回一個數組
Method[] getDeclaredMethods() // 獲取當前類的所有Method(不包括父類),返回一個數組
Class cls = Student.class;
try {
Method method = cls1.getDeclaredMethod("setName", String.class);
}catch (Exception e){
...
}
2、獲取方法的屬性
getName() // 返回方法名稱
getReturnType() // 返回方法返回值類型,也是一個Class實例
getParameterTypes() // 返回方法的參數類型,是一個Class數組
getModifiers() // 返回方法的修飾符,它是一個int,不同的bit表示不同的含義。
Class cls = Student.class;
try {
// 獲取setName方法,參數為String
Method method = cls1.getDeclaredMethod("setName", String.class);
System.out.println(method.getName()); // setName
}catch (Exception e){
...
}
3、調用方法
invoke() // 調用執行方法
(1)調用public非靜態方法
invoke(obj, 參數) // 對Method實例調用invoke就相當於調用該方法,
// invoke的第一個參數是對象實例,即在哪個實例上調用該方法,后面的可變參數要與方法參數一致,否則將報錯。
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 獲取setName方法,參數為String
Method method = cls1.getDeclaredMethod("setName", String.class);
method.invoke(s, "john")
}catch (Exception e){
...
}
(2)調用public靜態方法
調用靜態方法時,由於無需指定實例對象,所以invoke
方法傳入的第一個參數永遠為null
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 獲取setName方法,參數為String
Method method = cls1.getDeclaredMethod("add", int.class);
method.invoke(null, 5)
}catch (Exception e){
...
}
(3)調用 非public 方法
對於非public方法,我們雖然可以通過Class.getDeclaredMethod()
獲取該方法實例,但直接對其調用將得到一個IllegalAccessException
。為了調用非public方法,我們通過Method.setAccessible(true)
允許其調用。
注意:
setAccessible(true)
可能會失敗。如果JVM運行期存在SecurityManager
,那么它會根據規則進行檢查,有可能阻止setAccessible(true)
。例如,某個SecurityManager
可能不允許對java
和javax
開頭的package
的類調用setAccessible(true)
,這樣可以保證JVM核心庫的安全
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 獲取setName方法,參數為String
Method method = cls1.getDeclaredMethod("check");
Method.setAccessible(true)
method.invoke(s)
}catch (Exception e){
...
}
4、多態
使用反射調用方法時,仍然遵循多態原則:即總是調用實際類型的覆寫方法(如果存在)。
即先從子類中找有無方法,沒有再去父類
五、調用構造方法
注意:
getConstructor(name)
與getDeclaredConstructor(name)
中 name 可能不存在,源碼中用了throws
來拋出可能出現的異常,所以在使用時,必須用try...catch...
來捕獲異常
getConstructor(Class...) // 獲取某個public的Constructor
getDeclaredConstructor(Class...) // 獲取某個Constructor
getConstructors() // 獲取所有public的Constructor
getDeclaredConstructors() // 獲取所有Constructor
public class Main {
public static void main(String[] args) throws Exception {
// 獲取構造方法Integer(int)
Constructor cons1 = Integer.class.getConstructor(int.class);
// 調用構造方法,實例化產生實例
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);
// 獲取構造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
六、獲取繼承關系
Class i = Integer.class;
// 獲取class的父類
Class n = i.getSuperclass();
// 獲取class的所有接口
Class[] is = s.getInterfaces();
// 通過Class對象的isAssignableFrom()方法可以判斷一個向上轉型是否可以實現。
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因為Integer可以賦值給Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因為Number不能賦值給Integer