1. 程式人生 > >【Java入門提高篇】Day13 Java中的反射機制

【Java入門提高篇】Day13 Java中的反射機制

== getchar 復制對象 enc 類型判斷 amt sim 博客 contains

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

  好了,言歸正傳。

  反射機制是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[], int, int, Charset);
  public java.lang.String(byte[], int, int, 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, int);
  public java.lang.String();
  public java.lang.String(char[]);
  public java.lang.String(String);
  public java.lang.String(char[], int, int);
  public java.lang.String(byte[], int);
  public java.lang.String(byte[], int, int, 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, int, int, [C, int, int, int);
  static int indexOf([C, int, int, 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, 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, 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, int);
  public boolean endsWith(java.lang.String);
  public boolean equalsIgnoreCase(java.lang.String);
  public static transient String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;);
  public static transient String format(java.lang.String, [Ljava.lang.Object;);
  public void getBytes(int, int, [B, int);
  public byte[] getBytes(java.nio.charset.Charset);
  public byte[] getBytes(java.lang.String);
  public byte[] getBytes();
  public void getChars(int, int, [C, 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, int, java.lang.String, int);
  public int lastIndexOf(java.lang.String, int);
  public int lastIndexOf(int, int);
  static int lastIndexOf([C, int, int, [C, int, int, 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, java.lang.String, int, int);
  public boolean regionMatches(boolean, int, java.lang.String, int, 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入門提高篇】Day13 Java中的反射機制