不藏了,這些Java反射用法總結都告訴你們

華為雲開發者社群發表於2021-07-08
摘要:Java反射是一種非常強大的機制,它可以在同一個系統中去檢測內部的類的欄位、方法和建構函式。它非常多的Java框架中,都大量應用了反射技術,如Hibernate和Spring。可以說,反射機制的特徵讓Java可以構建異常強大,具備柔性的系統。

本文分享自華為雲社群《JAVA程式設計不可不知的反射用法總結丨【奔跑吧!JAVA】》,原文作者:jackwangcumt 。

Java反射是一種非常強大的機制,它可以在同一個系統中去檢測內部的類的欄位、方法和建構函式。它非常多的Java框架中,都大量應用了反射技術,如Hibernate和Spring。可以說,反射機制的特徵讓Java可以構建異常強大,具備柔性的系統。

雖然Java反射機制存在效率低、速度慢和安全性不高等弊端,但在很多場景下,這些特徵並不是主要的因素,或者可以通過快取或者JVM優化等來逐步提升執行效率。

根據網上的說法,反射技術能夠檢查或修改在JVM中應用程式在執行時的行為,這是一個比較高階的語言特性和一種強大的技術,反射可以使應用程式實現本來不可能的操作。

下面對Java反射的基礎知識進行說明和總結:

首先定義一個MyBase類,其中有私有欄位,也有公有欄位。同時也有公有方法和私有方法。MyBase類示例如下:

package com.hwdev.demo;
/**
 * 基類示例
 * @author wangming
 */
public class MyBase {
    //公有欄位  
    public int version  = 1;
    //私有欄位
    private String date = "2021-05-18" ;
    //公有方法   
    public void say2(String msg){
    System.out.println("Hello " + msg);
    }
    //私有方法
    private String getData(){
        return this.date;
    }
}

這裡再定義一個Hello類,它繼承自MyBase類,通過繼承主要用於驗證一下反射對於父類、子類的反射用法。

package com.hwdev.demo;
/**
 *
 * @author wangming
 */
public class Hello extends MyBase {
 
    public String author = "JackWang" ;
    public int version  = 1;
    private String company = "kzcloud" ;
 
    public void say(String msg){
         System.out.println("Hello " + msg);
    }
    public void setAuthor(String author){
        this.author = author;
    }
    public String getAuthor(){
        return this.author;
    }
    private int getVersion(){
        return this.version;
    }
}

關於Java反射,功能強大的就是可以通過字串配置來動態從系統中呼叫方法或者修改其中某個物件的欄位值,而Class.forName方法即可以通過傳入類全路徑字串名稱來獲取對應的Class物件,非常的方便。另外通過getField方法和GetMethod方法可以獲取指定欄位和方法,並動態呼叫。

package com.hwdev.demo;
import java.lang.reflect.*;
import java.util.Arrays;
/**
 * 反射第一種用法 Class.forName
 * @author wangming
 */
public class ReflectDemo01 {
 
     public static void Test() {
        try
        {
            //通過字串全路徑類名查詢Class
            Class helloC = Class.forName("com.hwdev.demo.Hello"); 
            //獲取所有公有的欄位陣列,私有的無法獲取   
            Field [] fields = helloC.getFields();
            //列印欄位陣列內容
            System.out.println(Arrays.toString(fields));
            //[public java.lang.String com.hwdev.demo.Hello.author, public int com.hwdev.demo.Hello.version]
            //例項化
            Object obj = helloC.newInstance();
            //獲取特定欄位,比遍歷Field[]效率更高
            Field f = helloC.getField("author");
            if (f != null){
                //關閉安全檢查,提高效率
                f.setAccessible(true);
                //獲取欄位author內容
                String author = (String)f.get(obj);
                System.out.println("author=" + author);
                //author=JackWang
            }
            //獲取所有公有的方法陣列,私有的無法獲取 
            Method [] methods = helloC.getMethods();
            //列印方法陣列內容,子類等方法也可以獲取到
            System.out.println(Arrays.toString(methods));
            //本類所有方法
            Method [] methods2 = helloC.getDeclaredMethods();
            //列印方法陣列內容
            System.out.println(Arrays.toString(methods2));
            //獲取特定方法,第二個引數String.class為say方法的引數型別
            //say(java.lang.String)
            Method m = helloC.getDeclaredMethod("say",String.class); 
            if (m != null){
                //關閉安全檢查,提高效率
                m.setAccessible(true);
                //獲取欄位author內容
                Object returnValue = m.invoke(obj, new Object[]{"Java"});
                //Hello Java
                if (returnValue!=null){
                    System.out.println("returnValue =" + returnValue);    
                }
            }
 
        }catch(ClassNotFoundException | SecurityException ex){
            ex.printStackTrace();
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }    
}

這裡需要注意:xxx.getMethods()方法預設情況下,會返回本類、父類、父介面的公有方法,而xxx.getDeclaredMethods()返回本類的 所有方法,包括私有的方法。同理,反射API中其他getXXX和getDeclaredXXX的用法類似。

package com.hwdev;
import com.hwdev.demo.ReflectDemo01;
/**
 * 
 * @author wangming
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        //反射第一種用法 Class.forName
        ReflectDemo01.Test();
    }    
}

執行程式,輸出的結果如下:

[public java.lang.String com.hwdev.demo.Hello.author, public int com.hwdev.demo.Hello.version, public int com.hwdev.demo.MyBase.version]
author=JackWang
[public void com.hwdev.demo.Hello.say(java.lang.String), public void com.hwdev.demo.Hello.setAuthor(java.lang.String), public java.lang.String com.hwdev.demo.Hello.getAuthor(), public void com.hwdev.demo.MyBase.say2(java.lang.String), 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()]
[public void com.hwdev.demo.Hello.say(java.lang.String), public void com.hwdev.demo.Hello.setAuthor(java.lang.String), public java.lang.String com.hwdev.demo.Hello.getAuthor(), private int com.hwdev.demo.Hello.getVersion()]
Hello Java

從輸出結果上來看,Field [] fields = helloC.getFields();不但可以獲取Hello類的公有欄位,還可以獲取到父類MyBase的公有欄位:com.hwdev.demo.MyBase.version

而Method [] methods2 = helloC.getDeclaredMethods();則可以獲取本類,即Hello類所有的方法,包括公有的方法和私有的方法。因此,Java反射可以訪問類的私有欄位和方法,從而暴露內部的資訊,這也是Java反射有安全問題的原因。

由於Java方法支援過載,因此同名的方法可以存在多個,即引數不同,因此在用反射呼叫方法時,需要指定方法的引數型別,這樣就可以明確調到的具體是哪個方法簽名,如Method m = helloC.getDeclaredMethod("say",String.class); 呼叫的是public void com.hwdev.demo.Hello.say(java.lang.String) 。

除了可以用Class.forName來進行反射外,還可以通過如下方式來獲取反射物件:

Hello hello = new Hello();
Class helloC = hello.getClass();
Field [] fields = helloC.getFields();
//////////////////////////////////////////
Class helloC = Hello.class; 
Field [] fields = helloC.getFields();

下面介紹一下如何用Java反射修改私有欄位和呼叫私有方法的示例:

package com.hwdev.demo;
import java.lang.reflect.*;
/**
 * 反射訪問私有欄位和方法
 * @author wangming
 */
public class ReflectDemo02 {
 
     public static void Test() {
        try
        {
            //通過已有類查詢Class         
            Class helloC = Hello.class; 
            //例項化
            Object obj = helloC.newInstance();
            //獲取特定私有欄位
            Field f = helloC.getDeclaredField("company");
            if (f != null){
                //私有必須開啟
                f.setAccessible(true);
                //設定私有欄位值
                f.set(obj, "newKZ");
                //獲取欄位author內容
                String fv = (String)f.get(obj);
                System.out.println("company=" + fv);
                //company=newKZ
            } 
            //獲取私有方法
            Method m = helloC.getDeclaredMethod("getVersion", null); 
            if (m != null){
                //私有必須開啟
                m.setAccessible(true);
                Object returnValue = m.invoke(obj, null);
                if (returnValue!=null){
                    //returnValue =1
                    System.out.println("returnValue =" + returnValue);    
                }
            }
 
        }catch(SecurityException ex){
            ex.printStackTrace();
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }    
}

另外,Java反射可以獲取註解資訊,這個對於ORM框架來講,用的非常多。

package com.hwdev.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 註解示例
 * @author wangming
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ORMAnnotation {
    public String FieldName();
    public String FieldType();
}

其中,@Retention(RetentionPolicy.RUNTIME)表示註解可以在執行時通過反射訪問。@Target(ElementType.FIELD) 表示這個註解只能用在欄位上面。同理,可以把FIELD改為Type或者Method等。

package com.hwdev.demo;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
/**
 * 反射或者欄位註解
 * @author wangming
 */
public class ReflectDemo03 {
 
     public static void Test() {
        try
        {
 
            Class helloC = Class.forName("com.hwdev.demo.HelloAnnotation");      
            Field[] fields = helloC.getDeclaredFields();
            for(Field f : fields){
                 //關閉安全檢查,提高效率
                f.setAccessible(true);
                Annotation ann = f.getAnnotation(ORMAnnotation.class);
                if(ann instanceof ORMAnnotation){
                    ORMAnnotation ormAnn = (ORMAnnotation) ann;
                    System.out.println("FieldName=" + ormAnn.FieldName());
                    System.out.println("FieldType=" + ormAnn.FieldType());
                }
 
            }          
 
        }catch(ClassNotFoundException | SecurityException ex){
            ex.printStackTrace();
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }    
}

執行此示例,則輸出如下:

FieldName=f_author
FieldType=varchar(50)
FieldName=f_ver
FieldType=int

再次,介紹一下如何用反射獲取方法引數個數和型別,其中包含泛型的資訊獲取:

package com.hwdev.demo;

import java.util.ArrayList;
import java.util.List;

/**
 * 泛型示例
 * @author wangming
 */
public class GenericCls {
   protected List<String> myList = new ArrayList(); 
   public GenericCls(int size){      
       for(int i = 0;i<size;i++){
           myList.add("item"+i);
       }
   }
   public  List<String> getList(){ 
      return this.myList;
   }
   public String getList(int idx){ 
      return this.myList.get(idx);
   }
}
package com.hwdev.demo;
import java.lang.reflect.*;
/**
 * 反射獲取方法引數
 * @author wangming
 */
public class ReflectDemo05 {
 
     public static void Test() {
        try
        {
 
            Class helloC = Class.forName("com.hwdev.demo.GenericCls"); 
            //建構函式呼叫
            Object obj = helloC.getConstructor(int.class).newInstance(3);
            Method method = helloC.getMethod("getList", int.class);
            Class<?> returnType = method.getReturnType();
            System.out.println("ReturnType = " + returnType.getName());
            Parameter[] params = method.getParameters();
            for(Parameter p : params){    
                System.out.println("ParameterizedType = " + p.getParameterizedType());
                System.out.println("getModifiers = " + p.getModifiers());
                System.out.println("getName = " + p.getName());
                System.out.println("getType = " + p.getType());
            }
           //呼叫方法
           Object ret =  method.invoke(obj, new Object[]{2});
           System.out.println("ret = " + ret.toString());
            Method method2 = helloC.getMethod("getList", null);
            Type greturnType = method2.getGenericReturnType();
            System.out.println("getGenericReturnType = " + returnType.getName());
            if(greturnType instanceof ParameterizedType){
                ParameterizedType type = (ParameterizedType) greturnType;
                System.out.println("type = " + type.getTypeName());
                Type[] typeArguments = type.getActualTypeArguments();
                for(Type typeArgument : typeArguments){
                    Class typeArgClass = (Class) typeArgument;
                    System.out.println("typeArgClass = " + typeArgClass);
                }
            }
        }catch(ClassNotFoundException | SecurityException ex){
            ex.printStackTrace();
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }    
}

執行上述示例,輸出如下所示。

ReturnType = java.lang.String
ParameterizedType = int
getModifiers = 0
getName = arg0
getType = int
ret = item2
getGenericReturnType = java.lang.String
type = java.util.List<java.lang.String>
typeArgClass = class java.lang.String

關於反射還有非常多的知識點可以講解,比如利用反射技術實現外掛的動態載入等。反射的效率問題,可以通過使用高效的第三方反射庫,或者加入緩衝機制來解決,這裡不再贅述。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章