首頁>科技>

什麼是Java反射機制?

Java反射機制是 Java 語言的一個重要特性,它在伺服器程式和中介軟體程式中得到了廣泛運用。在伺服器端,往往需要根據客戶的請求,動態呼叫某一個物件的特定方法。此外,在 ORM 中介軟體的實現中,運用 Java 反射機制可以讀取任意一個 JavaBean 的所有屬性,或者給這些屬性賦值

通過反射機制,可以在執行的時候訪問到物件的屬性、方法、構造方法等等

哪些地方用到反射機制?

其實我們都用過反射機制,只是並不知道它是反射機制而已。比如我們使用JDBC連線資料庫的時候(Class.forName() 這個相信大家都用過),只是當時並沒有在意那麼多(心裡想能用就行,管它呢),使用到反射機制的框架有(Spring、SpringMVC、Mybatis、Hibernate、Structs)

反射機制提供了哪些功能?

在執行時判定任意一個物件所屬的類

在執行時構造任意一個類的物件

在執行時判定任意一個類所具有的成員變數和方法

在執行時呼叫任意一個物件的方法

生成動態代理

如何實現反射機制?

先來個實體類 Student

/**

* @author Woo_home

* @create by 2019/12/10

*/

public class Student {

private int id;

private String name;

public Student(){}

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "Student{" +

"id=" + id +

", name='" + name + '\\'' +

'}';

}

}

反射的三種實現方式

一、通過物件的getClass()方法

getClass()方法是Object類的方法,因為所有類都繼承自Object類,所以可以直接使用getClass()方法

public class ReflectDemo {

public static void main(String[] args) {

Student student = new Student();

Class studentClass = student.getClass();

System.out.println(studentClass);

}

}

二、通過類的.class屬性

直接獲取一個類的class

public class ReflectDemo {

public static void main(String[] args) {

Class<?> studentClass = Student.class;

System.out.println(studentClass);

}

}

三、通過Class類的forName()方法(常用)

Class.forName() 是一個靜態方法

public class ReflectDemo {

public static void main(String[] args) {

// 使用 try、catch 處理異常

try {

Class<?> studentClass = Class.forName("com.example.app.demo.Student");

System.out.println(studentClass);

}catch (ClassNotFoundException e){

e.printStackTrace();

}

}

}

public class ReflectDemo {

// 使用 throws 處理異常

public static void main(String[] args) throws ClassNotFoundException{

Class<?> studentClass = Class.forName("com.example.app.demo.Student");

System.out.println(studentClass);

}

}

三種方法列印的結果都是一樣的,如下圖所示

注意: 這裡的Class.forName()方法需要使用try、catch語句括住或者在方法或類中丟擲ClassNotFoundException 異常,不然會報錯

有些人就問了,為什麼需要使用try、catch語句括住呢,來看下forName方法原始碼:

可以看到forName需要丟擲一個 ClassNotFoundException 異常,自然而然地你使用forName()方法也自然要丟擲 / 處理日常了

@CallerSensitive

public static Class<?> forName(String className)

throws ClassNotFoundException {

Class<?> caller = Reflection.getCallerClass();

return forName0(className, true, ClassLoader.getClassLoader(caller), caller);

}

判斷一個類是否為某個類的例項

1、instanceof

public class ReflectDemo {

public static void main(String[] args) {

HashMap<Integer,String> map = new HashMap<>();

if (map instanceof Map){

System.out.println("HashMap is Map instance");

}

}

}

輸出:HashMap is Map instance

2、isInstance

public class ReflectDemo {

public static void main(String[] args) {

HashMap<Integer,String> map = new HashMap<>();

if (Map.class.isInstance(map)){

System.out.println("HashMap is Map Instance");

}

}

}

輸出:HashMap is Map Instance

利用反射建立物件例項

1、通過Class物件的 newInstance() 方法

public class ReflectDemo {

public static void main(String[] args) throws IllegalAccessException, InstantiationException {

// 這裡的Student是使用上面一開始的Student類

Class<?> studentClass = Student.class;

// 使用newInstance建立例項

Student student = (Student)studentClass.newInstance();

student.setId(1);

student.setName("John");

System.out.println(student);

}

}

輸出:John

2、通過Constructor物件的 getConstructor() 方法

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

Class<?> classes = List.class;

Constructor constructor = classes.getConstructor(List.class);

List result = (List) constructor.newInstance("John");

System.out.println(result);

}

}

輸出:John

Field

根據欄位名獲取欄位(只能獲取public的)

public class ReflectDemo {

public static void main(String[] args) throws NoSuchFieldException {

Class<?> classes = Student.class;

Field id = classes.getField("id");

Field name = classes.getField("name");

System.out.println(id);

System.out.println(name);

}

}

class Student{

public int id;

public String name;

}

輸出:

以下為輸出結果

public int com.example.app.demo.Student.id

public java.lang.String com.example.app.demo.Student.name

如果獲取的欄位是被private修飾的,那麼將會丟擲 NoSuchFieldException 異常

以下為輸出結果

Exception in thread “main” java.lang.NoSuchFieldException: id

at java.lang.Class.getField(Class.java:1703)

at com.example.app.demo.ReflectDemo.main(ReflectDemo.java:12)

為什麼會這樣呢?我們朝著這個問題看下原始碼是怎麼實現的

getField(String name)

根據名稱獲取公有的(public)類成員

@CallerSensitive

public Field getField(String name)

throws NoSuchFieldException, SecurityException {

checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);

// 可以看到在getField傳入的name實際是呼叫getField0來實現的

Field field = getField0(name);

if (field == null) {

throw new NoSuchFieldException(name);

}

return field;

}

getField0(String name)

而getField0主要判斷依據是searchFields,searchFields根據privateGetDeclaredFields判斷

private Field getField0(String name) throws NoSuchFieldException {

Field res;

// Search declared public fields

if ((res = searchFields(privateGetDeclaredFields(true), name)) != null) {

return res;

}

// 省略部分程式碼...

return null;

}

privateGetDeclaredFields(boolean publicOnly)

可以看到當privateGetDeclaredFields傳入的值是true的時候表示使用的是 declaredPublicFields ,也就是說是public宣告的欄位,當傳入的值為false的時候使用的是 declaredFields(所有宣告欄位)

private Field[] privateGetDeclaredFields(boolean publicOnly) {

checkInitted();

Field[] res;

ReflectionData<T> rd = reflectionData();

if (rd != null) {

// 判斷輸入的布林值是true還是false

res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;

if (res != null) return res;

}

// 省略部分程式碼...

return res;

}

getFields(String name)

獲取所有 public 修飾的欄位

public class ReflectDemo {

public static void main(String[] args) throws NoSuchFieldException {

Class<?> classes = Student.class;

Field[] allField = classes.getFields();

for (Field field : allField) {

System.out.println(field);

}

}

}

class Student{

private int id;

public String name;

}

輸出:

以下為輸出結果

public java.lang.String com.example.app.demo.Student.name

可以看到我們的Student類中聲明了一個private修飾的id和一個public修飾的name,根據輸出結果可以知道getFields()方法只能獲取public修飾的欄位

看下實現原始碼:

在getFields中返回了一個copyFields方法,該方法中呼叫了 privateGetPublicFields 方法

@CallerSensitive

public Field[] getFields() throws SecurityException {

checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);

return copyFields(privateGetPublicFields(null));

}

來看下privateGetPublicFields方法可以知道在該方法中呼叫了 privateGetDeclaredFields(true) 這個方法,關於這個方法上面已經講述過

private Field[] privateGetPublicFields(Set<Class<?>> traversedInterfaces) {

// Local fields

Field[] tmp = privateGetDeclaredFields(true);

addAll(fields, tmp);

// 省略部分程式碼...

return res;

}

getDeclaredField(String name)

根據名稱獲取已宣告的類成員。但不能得到其父類的類成員

public class ReflectDemo {

public static void main(String[] args) throws NoSuchFieldException {

Class<?> classes = Student.class;

Field allField = classes.getDeclaredField("name");

System.out.println(allField);

}

}

class Student{

private int id;

private String name;

}

Class Student{

public int id;

public String name;

}

輸出:可以看到getDeclaredField()方法即可以獲取public型別的欄位也能獲取private型別的欄位

以下為輸出結果:

private java.lang.String com.example.app.demo.Student.name

public java.lang.String com.example.app.demo.Student.name

來看下原始碼的原理

@CallerSensitive

public Field getDeclaredField(String name)

throws NoSuchFieldException, SecurityException {

checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);

Field field = searchFields(privateGetDeclaredFields(false), name);

if (field == null) {

throw new NoSuchFieldException(name);

}

return field;

}

可以看到 getDeclaredField() 方法呼叫的也是 privateGetDeclaredFields 方法,上面已經講述過

getDeclaredFields()

獲取所有已宣告的類成員變數

public class ReflectDemo {

public static void main(String[] args) {

Class<?> classes = Student.class;

Field[] allField = classes.getDeclaredFields();

for (Field field : allField) {

System.out.println(field);

}

}

}

class Student{

public int id;

public String name;

}

輸出:

以下為輸出結果:

public int com.example.app.demo.Student.id

public java.lang.String com.example.app.demo.Student.name

而且 getDeclaredFields 還可以獲取private修飾的欄位

public class ReflectDemo {

public static void main(String[] args) {

Class<?> classes = Student.class;

Field[] allField = classes.getDeclaredFields();

for (Field field : allField) {

System.out.println(field);

}

}

}

class Student{

private int id;

private String name;

}

輸出:

以下為輸出結果:

private int com.example.app.demo.Student.id

private java.lang.String com.example.app.demo.Student.name

來看下原始碼 getDeclaredFields() 方法的原始碼

可以看到 getDeclaredFields() 方法中也是呼叫 privateGetDeclaredFields 方法的,並且這裡的 privateGetDeclaredFields傳入的值是 false ,也就是獲取所有已宣告的欄位

@CallerSensitive

public Field[] getDeclaredFields() throws SecurityException {

checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);

return copyFields(privateGetDeclaredFields(false));

}

Method

getMethod(String name)

返回類或介面的特定方法。其中第一個引數為方法名稱,後面的引數為方法引數對應 Class 的物件的方法名

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException {

// 傳入CaseMethod的方法名

Method methods = CaseMethod.class.getMethod("caseOfMethod");

System.out.println(methods);

}

}

class CaseMethod{

public void caseOfMethod(){

System.out.println("case");

}

}

輸出:

以下為輸出結果:

public void com.example.app.demo.CaseMethod.caseOfMethod()

getMethods()

獲取類或介面的所有 public 方法,包括其父類的 public 方法

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException {

// 獲取所有方法,使用Method陣列接收

Method[] methods = CaseMethod.class.getMethods();

for (Method method : methods) {

System.out.println(method);

}

}

}

class CaseMethod{

public void caseOfMethod(){

System.out.println("case");

}

}

輸出:

可以看到輸出結果中第一個就是我們自定義的CaseMethod中的方法,其餘的都是Object類的方法

以下為輸出結果:

public void com.example.app.demo.CaseMethod.caseOfMethod()

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

getDeclaredMethod(String name, Class<?>… parameterTypes)

獲取類或介面的特定宣告方法。其中第一個引數為方法名稱,後面的引數為方法引數對應 Class 的物件

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException {

Method methods = CaseMethod.class.getDeclaredMethod("caseOfMethod",null);

System.out.println(methods);

}

}

class CaseMethod{

public void caseOfMethod(){

System.out.println("case");

}

private void caseOf(){

System.out.println("case1");

}

}

getDeclaredMethods()

獲取類或介面宣告的所有方法,包括 public、protected、預設(包)訪問和 private 方法,但不包括繼承的方法

public class ReflectDemo {

public static void main(String[] args) {

Method[] methods = CaseMethod.class.getDeclaredMethods();

for (Method method : methods) {

System.out.println(method);

}

}

}

class CaseMethod{

public void caseOfMethod(){

System.out.println("case");

}

private void caseOf(){

System.out.println("case1");

}

}

輸出:

以下為輸出結果:

public void com.example.app.demo.CaseMethod.caseOfMethod()

private void com.example.app.demo.CaseMethod.caseOf()

使用invoke方法

當獲取一個物件之後就可以使用invoke方法,invoke方法示例如下:

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

Method methods = CaseMethod.class.getMethod("caseOfMethod");

CaseMethod caseMethod = new CaseMethod();

methods.invoke(caseMethod);

}

}

class CaseMethod{

public void caseOfMethod(){

System.out.println("case");

}

private void caseOf(){

System.out.println("case1");

}

}

輸出:case

那麼invoke是如何實現的呢?來看下原始碼

boolean override;

@CallerSensitive

public Object invoke(Object obj, Object... args)

throws IllegalAccessException, IllegalArgumentException,

InvocationTargetException

{

// 判斷override是否為true

if (!override) {

// 判斷modifiers(方法的修飾符)是否為public

if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {

// 通過方法的修飾符(protected、private、)或宣告類(如子類可以訪問父類的protected方法)與caller之間的關係

Class<?> caller = Reflection.getCallerClass();

// 判斷caller是否有許可權方法該方法

checkAccess(caller, clazz, obj, modifiers);

}

}

MethodAccessor ma = methodAccessor; // read volatile

if (ma == null) {

ma = acquireMethodAccessor();

}

return ma.invoke(obj, args);

}

注意: invoke方法如果提供了錯誤的引數,會丟擲一個異常,所以要提供一個異常處理器。建議在有必要的時候才使用invoke方法,有如下原因:

1、invoke方法的引數和返回值必須是Object型別,意味著必須進行多次型別轉換

2、通過反射呼叫方法比直接呼叫方法要明顯慢一些

Constructor

getConstructor(Class<?>… parameterTypes)

獲取類的特定 public 構造方法。引數為方法引數對應 Class 的物件

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(int.class, String.class);

System.out.println(constructor);

}

}

class CaseMethod{

private int id;

private String name;

private int cap;

public CaseMethod(int id,String name){

this.id = id;

this.name = name;

}

public CaseMethod(int cap){

}

}

輸出:

以下為輸出結果:

public com.example.app.demo.CaseMethod(int,java.lang.String)

getConstructors()

獲取類的所有 public 構造方法

public class ReflectDemo {

public static void main(String[] args) {

Constructor<?>[] constructors = CaseMethod.class.getConstructors();

for (Constructor<?> constructor : constructors) {

System.out.println(constructor);

}

}

}

class CaseMethod{

private int id;

private String name;

private int cap;

public CaseMethod(int id,String name){

this.id = id;

this.name = name;

}

public CaseMethod(int cap){

}

}

輸出:

以下為輸出結果:

public com.example.app.demo.CaseMethod(int,java.lang.String)

public com.example.app.demo.CaseMethod(int)

getDeclaredConstructor(Class<?>… parameterTypes)

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException {

// 注意:由於private修飾的CaseMethod建構函式沒有引數,所以getDeclaredConstructor()可以為空

// 預設的getDeclaredConstructor(Class<?>... parameterTypes)方法是要傳引數型別的

Constructor<CaseMethod> constructor = CaseMethod.class.getDeclaredConstructor();

Constructor<CaseMethod> declaredConstructor = CaseMethod.class.getDeclaredConstructor(int.class, String.class);

System.out.println(constructor);

System.out.println(declaredConstructor);

}

}

class CaseMethod{

private int id;

private String name;

private int cap;

private CaseMethod(){

}

public CaseMethod(int id,String name){

this.id = id;

this.name = name;

}

}

輸出:

以下為輸出結果:

private com.example.app.demo.CaseMethod()

public com.example.app.demo.CaseMethod(int,java.lang.String)

getDeclaredConstructors()

獲取類的所有構造方法

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException {

Constructor<?>[] constructors = CaseMethod.class.getDeclaredConstructors();

for (Constructor<?> constructor : constructors) {

System.out.println(constructor);

}

}

}

class CaseMethod{

private int id;

private String name;

private int cap;

private CaseMethod(){

}

public CaseMethod(int id,String name){

this.id = id;

this.name = name;

}

}

輸出:

以下為輸出結果:

private com.example.app.demo.CaseMethod()

public com.example.app.demo.CaseMethod(int,java.lang.String)

使用newInstance建立例項

public class ReflectDemo {

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(String.class);

CaseMethod caseMethod = constructor.newInstance("Lisa");

System.out.println(caseMethod);

}

}

class CaseMethod{

private String name;

public CaseMethod(String name){

this.name = name;

}

@Override

public String toString() {

return "CaseMethod{" +

"name='" + name + '\\'' +

'}';

}

}

輸出:

CaseMethod{name=‘Lisa’}

總結

Java獲得Class物件的引用的方法中,Class.forName() 方法會自動初始化Class物件,而 .class 方法不會,.class 的初始化被延遲到靜態方法或非常數靜態域的首次引用

最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 意想不到,你手機上的這個App也會成為武器