Java-基礎-反射

張鐵牛發表於2021-09-25

1. 簡介

JAVA反射機制是在執行狀態中

對於任意一個類,都能夠知道這個類的所有屬性和方法

對於任意一個物件,都能夠呼叫它的任意一個方法和屬性

這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

2. Class

Class類其實也是一個Java類,存在於JDK的java.lang包中。

java在執行時的類資訊就是通過Class物件表示的。它包含了類的所有資訊。

所有的類都是在對其第一次使用時,動態載入到JVM中的(懶載入)。當程式建立第一個對類的靜態成員的引用時,就會載入這個類。使用new建立類物件的時候也會被當作對類的靜態成員的引用。因此java程式程式在它開始執行之前並非被完全載入,其各個類都是在必需時才載入的。

所以,當我們寫的.java檔案被載入到JVM的後,會在方法區生成該類的Class例項。此例項包含了該類的所有資訊。

2.1 如何獲取Class例項

  1. 型別.class
  2. 類例項.getClass()
  3. Class.forName(類的許可權定類名)
public class Test {
   public static void main(String[] args) throws ClassNotFoundException {
      Class<Test> testClass = Test.class;
      Test test = new Test();
      Class<? extends Test> testClass1 = test.getClass();
      Class<Test> testClass2 = (Class<Test>) Class.forName("com.ldx.test.Test");
   }
}

2.2 使用Class例項

建立一個User

package com.ldx.test;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class User {
   private String name;

   private Integer age;

   public boolean isAdmin(String name) {
      if("admin".equals(name)) {
         return true;
      }
      return false;
   }
}

建立一個測試類Test

package com.ldx.test;

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<User> userClass = User.class;
      Field[] declaredFields = userClass.getDeclaredFields();
      log.info("User類的名稱:{}", userClass.getSimpleName());
      log.info("User類的許可權定類名:{}", userClass.getName());
      log.info("User類的欄位有:{}", Arrays.toString(declaredFields));
      log.info("User類的方法有:{}", Arrays.toString(userClass.getDeclaredMethods()));
      Method isAdmin = userClass.getMethod("isAdmin", String.class);
      log.info("User::isAdmin方法返回值為:" + isAdmin.invoke(userClass.newInstance(), "admin"));
   }
}

輸出內容如下:

com.ldx.test.Test - User類的名稱:User
com.ldx.test.Test - User類的許可權定類名:com.ldx.test.User
com.ldx.test.Test - User類的欄位有:[private java.lang.String com.ldx.test.User.name, private java.lang.Integer com.ldx.test.User.age]
com.ldx.test.Test - User類的方法有:[public boolean com.ldx.test.User.isAdmin(java.lang.String), public java.lang.Integer com.ldx.test.User.getAge(), public void com.ldx.test.User.setAge(java.lang.Integer), public java.lang.String com.ldx.test.User.toString(), public java.lang.String com.ldx.test.User.getName(), public void com.ldx.test.User.setName(java.lang.String)]
com.ldx.test.Test - User::isAdmin方法返回值為:true

2.3 常用方法摘要

返回值 方法說明
<U> Class<? extends U> asSubclass(Class<U> clazz)
強制轉換該 Class 物件,以表示指定的 class 物件所表示的類的一個子類。
static Class<?> forName(String className)
返回與帶有給定字串名的類或介面相關聯的 Class 物件。
<A extends Annotation>A getAnnotation(Class<A> annotationClass)
如果存在該元素的指定型別的註解,則返回這些註解,否則返回 null。
Annotation[] getAnnotations()
返回此元素上存在的所有註解。
ClassLoader getClassLoader()
返回該類的類載入器。
Class<?> getComponentType()
返回表示陣列元件型別的 Class
Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的所有註解。
Class<?> getDeclaringClass()
如果此 Class 物件所表示的類或介面是另一個類的成員,則返回的 Class 物件表示該物件的宣告類。
T[] getEnumConstants()
如果此 Class 物件不表示列舉型別,則返回列舉類的元素或 null。
Type[] getGenericInterfaces()
返回表示某些介面的 Type,這些介面由此物件所表示的類或介面直接實現。
Type getGenericSuperclass()
返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的直接超類的 Type
Class<?>[] getInterfaces()
確定此物件所表示的類或介面實現的介面。
int getModifiers()
返回此類或介面以整數編碼的 Java 語言修飾符。
String getName()
String 的形式返回此 Class 物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱。
String getCanonicalName()
返回 Java Language Specification 中所定義的底層類的規範化名稱。主要用於輸出(toString)或log列印,大多數情況下和getName()一樣,但是在內部類、陣列等型別的表示形式就不同了。不能用getCanonicalName()去載入類物件,必須用getName()
Package getPackage()
獲取此類的包。
InputStream getResourceAsStream(String name)
查詢具有給定名稱的資源。
String getSimpleName()
返回原始碼中給出的底層類的簡稱。
Class<? super T> getSuperclass()
返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的超類的 Class
boolean isAnnotation()
如果此 Class 物件表示一個註解型別則返回 true。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
如果指定型別的註解存在於此元素上,則返回 true,否則返回 false。
boolean isAnonymousClass()
當且僅當底層類是匿名類時返回 true
boolean isArray()
判定此 Class 物件是否表示一個陣列類。
boolean isAssignableFrom(Class<?> cls)
判定此 Class 物件所表示的類或介面與指定的 Class 引數所表示的類或介面是否相同,或是否是其超類或超介面。
boolean isEnum()
當且僅當該類宣告為原始碼中的列舉時返回 true。
boolean isInstance(Object obj)
如果obj是這個類的一個例項此方法返回true。
boolean isInterface()
判定指定的 Class 物件是否表示一個介面型別。
boolean isLocalClass()
當且僅當這個類是區域性類此方法返回true。
boolean isMemberClass()
當且僅當底層類是成員類時返回 true
boolean isPrimitive()
判定指定的 Class 物件是否表示一個基本型別。
T newInstance()
建立此 Class 物件所表示的類的一個新例項。

3. new Instance

想通過反射建立物件大概有以下幾種方式:

  1. 通過Class.newInstance()直接建立物件。
  2. 通過Class例項獲取到Constructor(構造器),通過構造器建立物件。

獲取構造方法的途徑有以下幾種:

返回值 方法說明
Constructor getConstructor(Class<?>... parameterTypes)
返回一個 Constructor 物件,它反映此 Class 物件所表示的類的public構造方法。
Constructor<?>[] getConstructors()
返回所有 Constructor 物件,它反映此 Class 物件所表示的類的public構造方法。
Constructor getDeclaredConstructor(Class<?>... parameterTypes)
返回一個 Constructor 物件,該物件反映此 Class 物件所表示的類的public/private構造方法。
Constructor<?>[] getDeclaredConstructor()
返回所有 Constructor 物件,該物件反映此 Class 物件所表示的類的public/private構造方法。

示例程式碼如下:

@Getter
@Setter
@ToString
public class User {
   private String name;
   private Integer age;
   public User() {
      this.name = "張三";
      this.age = 24;
   }
   public boolean isAdmin(String name) {
      if("admin".equals(name)) {
         return true;
      }
      return false;
   }
}
@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<User> userClass = User.class;
      User user = userClass.newInstance();
      Constructor<User> constructor = userClass.getConstructor();
      User user1 = constructor.newInstance();
      Constructor<User> declaredConstructor = userClass.getDeclaredConstructor();
      // 如果構造器為private,則可以設定訪問許可權為true,即可newInstance
      declaredConstructor.setAccessible(true);
      User user2 = declaredConstructor.newInstance();

      log.info(user.toString());
      log.info(user1.toString());
      log.info(user2.toString());
   }
}

輸出內容如下:

15:59:01.653 [main] INFO com.ldx.test.Test - User(name=張三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=張三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=張三, age=24)

4. Method

返回值 方法說明
Method getMethod(String name, Class<?>... parameterTypes)
返回一個 Method 物件,它反映此 Class 物件所表示的類的public方法。(可以獲取父類的方法)
Method[] getMethods()
返回所有 Method 物件,它反映此 Class 物件所表示的類的public方法。(可以獲取父類的方法)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一個 Method 物件,該物件反映此 Class 物件所表示的類的public/private方法。(只獲取當前類的方法)
Method[] getDeclaredMethods()
返回所有 Method 物件,它反映此 Class 物件所表示的類的public/private方法。(只獲取當前類的方法)

示例程式碼如下:

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<Man> manClass = Man.class;
      Man man = manClass.newInstance();
      Method getHairColor = manClass.getMethod("getHairColor");
      Method getHairColor1 = manClass.getDeclaredMethod("getHairColor");
      // 如果方法為private,則可以設定訪問許可權為true,即可newInstance
      getHairColor1.setAccessible(true);
      log.info(getHairColor.invoke(man).toString());
      log.info(getHairColor1.invoke(man).toString());
      // 可以呼叫到父類方法
      Method isAdmin = manClass.getMethod("isAdmin", String.class);
      log.info(isAdmin.invoke(man, "admin").toString());
      // 獲取不到父類方法
      Method isAdmin1 = manClass.getDeclaredMethod("isAdmin", String.class);
      log.info(isAdmin1.invoke(man, "admin").toString());

   }
}

class Man extends User {
   public String getHairColor() {
      return "一頭黑色秀髮";
   }
}

輸出內容如下:

16:30:49.809 [main] INFO com.ldx.test.Test - 一頭黑色秀髮
16:30:49.818 [main] INFO com.ldx.test.Test - 一頭黑色秀髮
16:30:49.818 [main] INFO com.ldx.test.Test - true
Exception in thread "main" java.lang.NoSuchMethodException: com.ldx.test.Man.isAdmin(java.lang.String)
	at java.lang.Class.getDeclaredMethod(Class.java:2130)
	at com.ldx.test.Test.main(Test.java:31)

Method類除了有上面的invoke方法,常用方法還有如下:

返回值 方法說明
<T extends Annotation>T getAnnotation(Class<T> annotationClass)
如果存在該元素的指定型別的註解,則返回這些註釋,否則返回 null。
Annotation[] getDeclaredAnnotations()
返回直接存在於此元素上的所有註解。
Class<?> getDeclaringClass()
返回表示宣告由此 Method 物件表示的方法的類或介面的 Class 物件。
Type[] getGenericParameterTypes()
按照宣告順序返回 Type 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別的。
Type getGenericReturnType()
返回表示由此 Method 物件所表示方法的正式返回型別的 Type 物件。
int getModifiers()
以整數形式返回此 Method 物件所表示方法的 Java 語言修飾符。
String getName()
String 形式返回此 Method 物件表示的方法名稱。
Annotation[][] getParameterAnnotations()
返回表示按照宣告順序對此 Method 物件所表示方法的形參進行註解的那個陣列的陣列。
Class<?>[] getParameterTypes()
按照宣告順序返回 Class 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別。
Class<?> getReturnType()
返回一個 Class 物件,該物件描述了此 Method 物件所表示的方法的正式返回型別。
Object invoke(Object obj, Object... args)
對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。
boolean isVarArgs()
如果將此方法宣告為帶有可變數量的引數,則返回 true;否則,返回 false
String toGenericString()
返回描述此 Method 的字串,包括型別引數。
void setAccessible(boolean flag)
將此物件的 accessible 標誌設定為指示的布林值,即設定其可訪問性。

其中:getReturnType/getGenericReturnType都是獲取Method物件表示的方法的返回型別,只不過前者返回的Class型別後者返回的Type,Type就是一個介面而已,在Java8中新增一個預設的方法實現,返回的就引數型別資訊。getParameterTypes/getGenericParameterTypes亦是如此。

其返回結果和Class.getTypeName()放回結果相同,就是輸出返回值的類的name(帶包名)。

public interface Type {
    default String getTypeName() {
        return toString();
    }
}

5. Field

返回值 方法說明
Field getField(String name)
返回一個 Field 物件,它反映此 Class 物件所表示的類的public屬性。(可以獲取父類的方法)
Field[] getFields()
返回所有Field屬性,它反映此 Class 物件所表示的類的public屬性。(可以獲取父類的方法)
Field getDeclaredField(String name)
返回一個 Field 物件,該物件反映此 Class 物件所表示的類的public/private屬性。(只獲取當前類的方法)
Field[] getDeclaredFields()
返回所有Field屬性,該物件反映此 Class 物件所表示的類的public/private屬性。(只獲取當前類的方法)

示例程式碼如下:

@Slf4j
public class Test {
   public static void main(String[] args) throws Exception {
      Class<Man> manClass = Man.class;
      Man man = manClass.newInstance();
      Field hair = manClass.getDeclaredField("hair");
      hair.setAccessible(true);
      hair.set(man, "濃密的黑髮");
      log.info(hair.get(man).toString());
      log.info(man.getHair());
      Class<?> type = hair.getType();
      Class<?> declaringClass = hair.getDeclaringClass();
      log.info(type.getCanonicalName());
      log.info(declaringClass.getCanonicalName());
   }
}

@Getter
class Man extends User {
   private String hair;
}

輸出內容如下:

17:13:17.903 [main] INFO com.ldx.test.Test - 濃密的黑髮
17:13:17.909 [main] INFO com.ldx.test.Test - 濃密的黑髮
17:13:17.909 [main] INFO com.ldx.test.Test - java.lang.String
17:13:17.909 [main] INFO com.ldx.test.Test - com.ldx.test.Man

Field類除了有上面寫到的方法,常用方法還有如下:

返回值 方法說明
Object get(Object obj)
返回指定物件上此 Field 表示的欄位的值。
<T extends Annotation>T getAnnotation(Class<T> annotationClass)
如果存在該元素的指定型別的註解,則返回這些註釋,否則返回 null。
boolean getBoolean(Object obj)
獲取一個靜態或例項 boolean 欄位的值。
byte getByte(Object obj)
獲取一個靜態或例項 byte 欄位的值。
char getChar(Object obj)
獲取 char 型別或另一個通過擴充套件轉換可以轉換為 char 型別的基本型別的靜態或例項欄位的值。
Annotation[] getDeclaredAnnotations()
返回直接存在於此元素上的所有註解。
Class<?> getDeclaringClass()
返回表示類或介面的 Class 物件,該類或介面宣告由此 Field 物件表示的欄位。
double getDouble(Object obj)
獲取 double 型別或另一個通過擴充套件轉換可以轉換為 double 型別的基本型別的靜態或例項欄位的值。
float getFloat(Object obj)
獲取 float 型別或另一個通過擴充套件轉換可以轉換為 float 型別的基本型別的靜態或例項欄位的值。
Type getGenericType()
返回一個 Type 物件,它表示此 Field 物件所表示欄位的宣告型別。
int getInt(Object obj)
獲取 int 型別或另一個通過擴充套件轉換可以轉換為 int 型別的基本型別的靜態或例項欄位的值。
long getLong(Object obj)
獲取 long 型別或另一個通過擴充套件轉換可以轉換為 long 型別的基本型別的靜態或例項欄位的值。
int getModifiers()
以整數形式返回由此 Field 物件表示的欄位的 Java 語言修飾符。
String getName()
返回此 Field 物件表示的欄位的名稱。
short getShort(Object obj)獲取 short 型別或另一個通過擴充套件轉換可以轉換為 short 型別的基本型別的靜態或例項欄位的值。
Class<?> getType()
返回一個 Class 物件,它標識了此 Field 物件所表示欄位的宣告型別。
boolean isEnumConstant()
如果此欄位表示列舉型別的元素,則返回 true;否則返回 false
void set(Object obj, Object value)
將指定物件變數上此 Field 物件表示的欄位設定為指定的新值。
void setBoolean(Object obj, boolean z)
將欄位的值設定為指定物件上的一個 boolean 值。
void setByte(Object obj, byte b)
將欄位的值設定為指定物件上的一個 byte 值。
void setChar(Object obj, char c)
將欄位的值設定為指定物件上的一個 char 值。
void setDouble(Object obj, double d)
將欄位的值設定為指定物件上的一個 double 值。
void setFloat(Object obj, float f)
將欄位的值設定為指定物件上的一個 float 值。
void setInt(Object obj, int i)
將欄位的值設定為指定物件上的一個 int 值。
void setLong(Object obj, long l)
將欄位的值設定為指定物件上的一個 long 值。
void setShort(Object obj, short s)
將欄位的值設定為指定物件上的一個 short 值。
String toGenericString()
返回一個描述此 Field(包括其一般型別)的字串。
void setAccessible(boolean flag)
將此物件的 accessible 標誌設定為指示的布林值,即設定其可訪問性。

6. 反射機制的執行流程

相關文章