1. 程式人生 > 程式設計 >一文搞懂Java中的反射機制

一文搞懂Java中的反射機制

  前一段時間一直忙,所以沒什麼時間寫部落格,拖了這麼久,也該更新更新了。最近看到各種知識付費的推出,感覺是好事,也是壞事,好事是對知識沉澱的認可與推動,壞事是感覺很多人忙於把自己的知識變現,相對的在沉澱上做的實際還不夠,我對此暫時還沒有什麼想法,總覺得,慢慢來,會更快一點,自己掌握好節奏就好。

  好了,言歸正傳。

  反射機制是Java中的一個很強大的特性,可以在執行時獲取類的資訊,比如說類的父類,介面,全部方法名及引數,全部常量和變數,可以說類在反射面前已經衣不遮體了(咳咳,這是正規車)。先舉一個小栗子,大家隨意感受一下:

public void testA(){
    String name = "java.lang.String";
    try{
      Class cl = Class.forName(name);
      Class supercl = cl.getSuperclass();
      String modifiers = Modifier.toString(cl.getModifiers());
      if (modifiers.length() > 0){
        System.out.print(modifiers + " ");
      }
      System.out.print(name);
      if (supercl != null && supercl != Object.class){
        System.out.print(" extents " + supercl.getName());
      }
      System.out.print("{\n");
      printFields(cl);
      System.out.println();
      printConstructors(cl);
      System.out.println();
      printMethods(cl);
      System.out.println("}");
    }catch (ClassNotFoundException e){
      e.printStackTrace();
    }
    System.exit(0);
  }
  private static void printConstructors(Class cl){
    Constructor[] constructors = cl.getDeclaredConstructors();
    
    for (Constructor c : constructors){
      String name = c.getName();
      System.out.print(" ");
      String modifiers = Modifier.toString(c.getModifiers());
      if (modifiers.length() > 0){
        System.out.print(modifiers + " ");
      }
      System.out.print(name + "(");
      
      Class[] paraTypes = c.getParameterTypes();
      for (int j = 0; j < paraTypes.length; j++){
        if (j > 0){
          System.out.print(",");
        }
        System.out.print(paraTypes[j].getSimpleName());
      }
      System.out.println(");");
    }
  }
  
  private static void printMethods(Class cl){
    Method[] methods = cl.getDeclaredMethods();
    for (Method m : methods){
      Class retType = m.getReturnType();
      String name = m.getName();
      
      System.out.print(" ");
      String modifiers = Modifier.toString(m.getModifiers());
      if (modifiers.length() > 0){
        System.out.print(modifiers + " ");
      }
      System.out.print(retType.getSimpleName() + " " + name +"(");
      Class[] paramTypes = m.getParameterTypes();
      for(int j = 0; j < paramTypes.length; j++){
        if (j > 0){
          System.out.print(",");
        }
        System.out.print(paramTypes[j].getName());
      }
      System.out.println(");");
    }
  }
  
  private static void printFields(Class cl){
    Field[] fields = cl.getFields();
    for (Field f : fields){
      Class type = f.getType();
      String name = f.getName();
      System.out.print(" ");
      String modifiers = Modifier.toString(f.getModifiers());
      if (modifiers.length() > 0){
        System.out.print(modifiers + " ");
      }
      System.out.println(type.getSimpleName() + " " + name +";");
    }
  }

  呼叫testA方法輸出如下:

public final java.lang.String{
 public static final Comparator CASE_INSENSITIVE_ORDER;

 public java.lang.String(byte[],int,int);
 public java.lang.String(byte[],Charset);
 public java.lang.String(byte[],String);
 public java.lang.String(byte[],String);
 java.lang.String(char[],boolean);
 public java.lang.String(StringBuilder);
 public java.lang.String(StringBuffer);
 public java.lang.String(byte[]);
 public java.lang.String(int[],int);
 public java.lang.String();
 public java.lang.String(char[]);
 public java.lang.String(String);
 public java.lang.String(char[],int);

 public boolean equals(java.lang.Object);
 public String toString();
 public int hashCode();
 public int compareTo(java.lang.String);
 public volatile int compareTo(java.lang.Object);
 public int indexOf(java.lang.String,int);
 public int indexOf(java.lang.String);
 public int indexOf(int,int);
 public int indexOf(int);
 static int indexOf([C,[C,int);
 static int indexOf([C,java.lang.String,int);
 public static String valueOf(int);
 public static String valueOf(long);
 public static String valueOf(float);
 public static String valueOf(boolean);
 public static String valueOf([C);
 public static String valueOf([C,int);
 public static String valueOf(java.lang.Object);
 public static String valueOf(char);
 public static String valueOf(double);
 public char charAt(int);
 private static void checkBounds([B,int);
 public int codePointAt(int);
 public int codePointBefore(int);
 public int codePointCount(int,int);
 public int compareToIgnoreCase(java.lang.String);
 public String concat(java.lang.String);
 public boolean contains(java.lang.CharSequence);
 public boolean contentEquals(java.lang.CharSequence);
 public boolean contentEquals(java.lang.StringBuffer);
 public static String copyValueOf([C);
 public static String copyValueOf([C,int);
 public boolean endsWith(java.lang.String);
 public boolean equalsIgnoreCase(java.lang.String);
 public static transient String format(java.util.Locale,[Ljava.lang.Object;);
 public static transient String format(java.lang.String,[Ljava.lang.Object;);
 public void getBytes(int,[B,int);
 public byte[] getBytes(java.nio.charset.Charset);
 public byte[] getBytes(java.lang.String);
 public byte[] getBytes();
 public void getChars(int,int);
 void getChars([C,int);
 private int indexOfSupplementary(int,int);
 public native String intern();
 public boolean isEmpty();
 public static transient String join(java.lang.CharSequence,[Ljava.lang.CharSequence;);
 public static String join(java.lang.CharSequence,java.lang.Iterable);
 public int lastIndexOf(int);
 public int lastIndexOf(java.lang.String);
 static int lastIndexOf([C,int);
 public int lastIndexOf(java.lang.String,int);
 public int lastIndexOf(int,int);
 static int lastIndexOf([C,int);
 private int lastIndexOfSupplementary(int,int);
 public int length();
 public boolean matches(java.lang.String);
 private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder);
 public int offsetByCodePoints(int,int);
 public boolean regionMatches(int,int);
 public boolean regionMatches(boolean,int);
 public String replace(char,char);
 public String replace(java.lang.CharSequence,java.lang.CharSequence);
 public String replaceAll(java.lang.String,java.lang.String);
 public String replaceFirst(java.lang.String,java.lang.String);
 public String[] split(java.lang.String);
 public String[] split(java.lang.String,int);
 public boolean startsWith(java.lang.String,int);
 public boolean startsWith(java.lang.String);
 public CharSequence subSequence(int,int);
 public String substring(int);
 public String substring(int,int);
 public char[] toCharArray();
 public String toLowerCase(java.util.Locale);
 public String toLowerCase();
 public String toUpperCase();
 public String toUpperCase(java.util.Locale);
 public String trim();
}

  這裡把String型別的所有方法和變數都獲取到了,使用的僅僅是String型別的全名。當然,反射的功能不僅僅是獲取類的資訊,還可以在執行時動態建立物件,回想一下,我們正常的物件使用,都是需要在程式碼中先宣告,然後才能使用它,但是使用反射後,就能在執行期間動態建立物件並呼叫其中的方法,甚至還能直接檢視類的私有成員變數,還能獲取類的註解資訊,在泛型中型別判斷時也經常會用到。反射可以說完全打破了類的封裝性,把類的資訊全部暴露了出來。

  上面的程式碼看不太明白也沒關係,只要稍微感受一下反射的能力就好了。介紹完了反射能做的事情,本篇教程就不再寫一些玩具程式碼了,這次以一個實用型的程式碼為媒介來介紹反射。

  在開發中,經常會遇到兩個不同類物件之間的複製,把一個類中的欄位資訊get取出來,然後set到另一個類中,大部分情況下,兩個類對應的欄位是一樣,每次這樣使用是很麻煩的,那麼利用反射就可以實現一個封裝,只需要呼叫一個方法即可實現簡單的類欄位複製。

  那麼,先來想想,要複製一個類物件的所有欄位資訊到另一個類物件中,首先,怎麼獲取一個類的某個欄位的值呢?我們先來編寫一個方法:

  /**
   * 獲取物件的指定欄位的值
   * @param obj 目標物件
   * @param fieldName 目標欄位
   * @return 返回欄位值
   * @throws Exception 可能丟擲異常
   */
  private static Object getFieldValue(Object obj,String fieldName) throws Exception{
    //獲取型別資訊
    Class clazz = obj.getClass();
    //取對應的欄位資訊
    Field field = clazz.getDeclaredField(fieldName);
    //設定可訪問許可權
    field.setAccessible(true);
    //取欄位值
    Object value = field.get(obj);
    return value;
  }

  這裡使用了兩個之前沒有說過的類,一個是Class,是不是很眼熟,想一想,我們每次定義一個類的時候是不是都要用到它,哈哈,那你就想錯了,那是class關鍵詞,java是大小寫的敏感的,這裡的Class是一個類名,那這個類是幹嘛用的呢?

  虛擬機器在載入每一個類的時候,會自動生成一個對應的Class類來儲存該類的資訊,可以理解為Class類是那個類的代理類,是連線實際類與類載入器的橋樑,可以通過它來獲取虛擬機器的類載入器引用,從而實現更多的騷操作。Class類是一個泛型類,每個類都有對應的一個Class類,比如String對應的Class類就是Class<String>。

  Class有很多方法來獲取更多關於類的資訊,這裡使用getDeclaredField方法來獲取指定欄位資訊,返回的是Field型別物件,這個物件裡儲存著關於欄位的一些資訊,如欄位名稱,欄位型別,欄位修飾符,欄位可訪問性等,setAccessible方法可以設定欄位的可訪問性質,這樣就能直接訪問private修飾的欄位了,然後使用get方法來獲取指定物件的對應欄位的值。

  我們來測試一下:

  public void testB(){
    try{
      Employee employee = new Employee();
      employee.setName("Frank");
      employee.setSalary(6666.66);
      System.out.println((String)getFieldValue(employee,"name"));
      System.out.println((double)getFieldValue(employee,"salary"));
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  輸出如下:

Frank
6666.66

  接下來,我們需要獲取類中所有欄位,然後在另一個類中查詢是否有對應欄位,如果有的話就設定欄位的值到相應的欄位中。

  /**
   * 複製一個類物件屬性到另一個類物件中
   * @param objA 需要複製的物件
   * @param objB 複製到的目標物件型別
   * @return 返回複製後的目標物件
   */
  private static void parseObj(Object objA,Object objB) throws Exception{
    if (objA == null){
      return;
    }
    //獲取objA的類資訊
    Class classA = objA.getClass();
    Class classB = objB.getClass();
    try {
      //獲取objA的所有欄位
      Field[] fieldsA = classA.getDeclaredFields();
      //獲取objB的所有欄位
      Field[] fieldsB = classB.getDeclaredFields();
      if (fieldsA == null || fieldsA.length <= 0 || fieldsB == null || fieldsB.length <= 0){
        return;
      }
      
      //生成查詢map
      Map<String,Field> fieldMap = new HashMap<>();
      for (Field field:fieldsA){
        fieldMap.put(field.getName(),field);
      }
      
      //開始複製欄位資訊
      for (Field fieldB : fieldsB){
        //查詢是否在objB的欄位中存在該欄位
        Field fielaA = fieldMap.get(fieldB.getName());
        if (fielaA != null){
          fieldB.setAccessible(true);
          fieldB.set(objB,getFieldValue(objA,fielaA.getName()));
        }
      }
    } catch (IllegalStateException e) {
      throw new IllegalStateException("instace fail: ",e);
    }
  }

  這裡獲取到classA和classB的所有欄位之後,先生成了一個map用於查詢,可以減少遍歷次數,然後之後只需要遍歷一次就可以判斷相應欄位是否存在,如果存在則取出對應值設定到相應的欄位裡去。

  接下來測試一下:

  public void testB(){
    try{
      //生成Employee物件
      Employee employee = new Employee("Frank",6666.66);
      //生成一個Manager物件
      Manager manager = new Manager();
      //複製物件
      parseObj(employee,manager);
      System.out.println(manager.getName());
      System.out.println(manager.getSalary());
    }catch (Exception e){
      e.printStackTrace();
    }
  }
public class Employee {
  private String name;
  private Double salary;
  
  public Employee(String name,Double salary) {
    this.name = name;
    this.salary = salary;
  }
  
  public String getName() {
    return name;
  }
  
  public void setName(String name) {
    this.name = name;
  }
  
  public Double getSalary() {
    return salary;
  }
  
  public void setSalary(Double salary) {
    this.salary = salary;
  }
}
public class Manager {
  private String name;
  private Double salary;
  private Double bonus;
  
  public String getName() {
    return name;
  }
  
  public void setName(String name) {
    this.name = name;
  }
  
  public Double getSalary() {
    return salary;
  }
  
  public void setSalary(Double salary) {
    this.salary = salary;
  }
  
  public Double getBonus() {
    return bonus;
  }
  
  public void setBonus(Double bonus) {
    this.bonus = bonus;
  }
}

  輸出如下:

Frank
6666.66

  完美,這樣我們就利用了反射機制完美的把相同的欄位在不同類的物件之間進行了複製,這裡僅僅是兩個欄位,所以可能好處不明顯,但事實上,實際開發中,經常會有將BO轉換為VO的操作,這時候,這個操作就很有必要了,簡單的一行命令就可以代替一大堆的get和set操作。

  當然,使用反射機制固然高階大氣上檔次,但是也是一把雙刃劍,使用不當很可能會帶來嚴重後果,而且使用反射的話,會佔用更多資源,執行效率也會降低,上述工具類是用執行效率換開發效率。開發中不建議大量使用,還是那句話,技術只是手段,需要使用的時候再使用,不要為了使用而使用。

  至於反射中的其他方法和姿勢,大家儘可以慢慢去摸索,這裡僅僅是拋磚引玉。

  至此,本篇講解完畢,歡迎大家繼續關注。

以上就是一文搞懂Java中的反射機制的詳細內容,更多關於Java反射機制的資料請關注我們其它相關文章!