Java系列筆記(2) - Java RTTI和反射機制 Java系列筆記(1) - Java 類加載與初始化 Java反射詳解

目錄html

  1. 前言
  2. 傳統的RTTI
  3. 反射
  4. 反射的實現方式
  5. 反射的性能
  6. 反射與設計模式

前言java

並非全部的Class都能在編譯時明確,所以在某些狀況下須要在運行時再發現和肯定類型信息(好比:基於構建編程,),這就是RTTI(Runtime Type Information,運行時類型信息)。編程

在java中,有兩種RTTI的方式,一種是傳統的,即假設在編譯時已經知道了全部的類型;還有一種,是利用反射機制,在運行時再嘗試肯定類型信息。設計模式

本文主要講反射方式實現的RTTI,建議在閱讀本文以前,先了解類的加載機制(參考個人博客:Java系列筆記(1) - Java 類加載與初始化)。api

在本文中,將共同使用下面的玩具類Toy,該類中定義了公有、私有方法,變量,構造方法,父類、父接口等:數組

package myblog.rtti;

/**
 * @project MyBlog
 * @create 2013年6月28日 下午4:42:46
 * @version 1.0.0
 * @author 張廣
 */
public interface IToy {
        public String playToy(String player) throws Exception;
}
package myblog.rtti;

public class AbstractToy implements IToy {
        @Override
        public String playToy(String player) throws Exception {
                System.out.println(player + " plays abstract toy");
                return "";
        }
}
package myblog.rtti;

public class Toy extends AbstractToy {

        private String name;

        public String color;

        protected int size;

        public static final int price = 10;

        static {
                System.out.println("Loading");
        }

        public Toy() {// 構造方法必定要聲明爲public類型,否則用getConstructors沒法獲得
                System.out.println("Initialing");
                setName("myToy");
                color = "red";
                size = 5;
        }

        public Toy(String name, String color, int size) {
                this.setName(name);
                this.color = color;
                this.size = size;
        }

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

        @Override
        public String playToy(String player) throws Exception {
                String msg = buildMsg(player);
                System.out.println(msg);
                return msg;
        }

        private String buildMsg(String player) {
                String msg = player + " plays " + name;
                return msg;
        }
}

傳統的RTTI安全

嚴格的說,反射也是一種形式的RTTI,不過,通常的文檔資料中把RTTI和反射分開,由於通常的,你們認爲RTTI指的是傳統的RTTI,經過繼承和多態來實現,在運行時經過調用超類的方法來實現具體的功能(超類會自動實例化爲子類,或使用instance of)。ide

傳統的RTTI有3種實現方式:post

  • 向上轉型或向下轉型(upcasting and downcasting),在java中,向下轉型(父類轉成子類)須要強制類型轉換
  • Class對象(用了Class對象,不表明就是反射,若是隻是用Class對象cast成指定的類,那就仍是傳統的RTTI)
  • instanceof或isInstance()

傳統的RTTI與反射最主要的區別,在於RTTI在編譯期須要.class文件,而反射不須要。傳統的RTTI使用轉型或Instance形式實現,但都須要指定要轉型的類型,好比:性能

public void rtti(Object obj){
    Toy toy = Toy(obj);
    // Toy toy = Class.forName("myblog.rtti.Toy")
    // obj instanceof Toy
}

注意其中的obj雖然是被轉型了,但在編譯期,就須要知道要轉成的類型Toy,也就是須要Toy的.class文件。

相對的,反射徹底在運行時在經過Class類來肯定類型,不須要提早加載Toy的.class文件。

反射

那到底什麼是反射(Reflection)呢?反射有時候也被稱爲內省(Introspection),事實上,反射,就是一種內省的方式,Java不容許在運行時改變程序結構或類型變量的結構,但它容許在運行時去探知、加載、調用在編譯期徹底未知的class,能夠在運行時加載該class,生成實例對象(instance object),調用method,或對field賦值。這種相似於「看透」了class的特性被稱爲反射(Reflection),咱們能夠將反射直接理解爲:能夠看到本身在水中的倒影,這種操做與直接操做源代碼效果相同,但靈活性高得多。

關於Java的反射API,不必去記憶,能夠在任何JDK API中查詢便可:

Class類:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/Class.html

reflect包:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/reflect/package-summary.html

反射的實現方式

package myblog.rtti;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @project MyBlog
 * @create 2013年6月28日 下午3:00:33
 * @version 1.0.0
 * @author 張廣
 */
public class ToyReflection {
        public static void printInfo(String info, Object obj) {
                if (obj.getClass().isArray()) {
                        System.out.println(info + ": ");
                        int length = Array.getLength(obj);
                        System.out.println("Array Size: " + length);
                        for (int i = 0; i < length; i++) {
                                System.out.print("Array[" + i + "]: " + Array.get(obj, i) + ", ");
                        }
                        if (length != 0)
                                System.out.println();
                }
                System.out.println(info + ": " + obj.toString());
        }

        public static void main(String[] args) {

                try {
                        // 得到類對象
                        Class<?> c = Class.forName("myblog.rtti.Toy");
                        printInfo("得到類對象", c);

                        // 得到超類
                        Class<?> superClass = c.getSuperclass();
                        printInfo("得到超類", superClass);

                        // 得到全部父接口
                        Class<?>[] interfaces = c.getInterfaces();
                        printInfo("得到全部父接口", interfaces);

                        // 實例化
                        Toy toy = (Toy) c.newInstance();
                        printInfo("實例化", toy);

                        // 得到訪問屬性爲public的構造方法
                        Constructor<?>[] constructors = c.getConstructors();
                        printInfo("得到構造方法", constructors);

                        // 得到指定參數的構造方法
                        Constructor<?> constructor = c.getDeclaredConstructor(String.class, String.class, int.class);
                        printInfo("得到指定構造方法", constructor);

                        // 得到方法,getMethod只能得到public方法,包括父類和接口繼承的方法
                        Method method = c.getMethod("playToy", String.class);
                        printInfo("得到公有方法", method);

                        // 調用方法
                        method.invoke(toy, "張三");

                        // 得到修飾符,包括private/public/protect,static
                        String modifier = Modifier.toString(method.getModifiers());
                        printInfo("得到修飾符", modifier);

                        // 得到參數類型
                        Class<?>[] paramTypes = method.getParameterTypes();
                        printInfo("得到參數類型", paramTypes);

                        // 得到返回值類型
                        Class<?> returnType = method.getReturnType();
                        printInfo("得到返回值類型", returnType);

                        // 得到異常類型
                        Class<?>[] excepTypes = method.getExceptionTypes();
                        printInfo("得到異常類型", excepTypes);

                        // 調用私有方法,getDeclaredMethod得到類自身的方法,包括public,protect,private方法
                        Method method2 = c.getDeclaredMethod("buildMsg", String.class);
                        method2.setAccessible(true);
                        String result = (String) method2.invoke(toy, "李四");
                        printInfo("得到私有方法", result);

                        // 得到所有屬性
                        Field[] fields = c.getFields();
                        printInfo("得到所有屬性", fields);

                        // 得到類自身定義的指定屬性
                        Field field = c.getDeclaredField("name");
                        printInfo("得到自身屬性", field);

                        // 得到類及其父類,父接口定義的public屬性
                        Field field2 = c.getField("color");
                        printInfo("得到公有屬性", field2);

                        // 得到權限修飾符,包括private/public/protect,static,final
                        String fieldModifier = Modifier.toString(field.getModifiers());
                        printInfo("得到權限修飾符", fieldModifier);

                        // 操做數組
                        int[] exampleArray = { 1, 2, 3, 4, 5 };
                        // 得到數組類型
                        Class<?> componentType = exampleArray.getClass().getComponentType();
                        printInfo("數組類型", componentType.getName());
                        // 得到長度
                        printInfo("數組長度", Array.getLength(exampleArray));
                        // 得到指定元素
                        printInfo("得到數組元素", Array.get(exampleArray, 2));
                        // 修改指定元素
                        Array.set(exampleArray, 2, 6);
                        printInfo("修改數組元素", exampleArray);

                        // 得到當前的類加載器
                        printInfo("得到當前類加載器", toy.getClass().getClassLoader().getClass().getName());
                } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                } catch (InstantiationException e) {
                        e.printStackTrace();
                } catch (IllegalAccessException e) {
                        e.printStackTrace();
                } catch (SecurityException e) {
                        e.printStackTrace();
                } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                } catch (InvocationTargetException e) {
                        e.printStackTrace();
                } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                }

        }
}

 經過上面的代碼,能夠清晰的理解如何「在水中看到本身」,不過須要注意的有幾點:

1,在java的反射機制中,getDeclaredMethod獲得的是所有方法,getMethod獲得的是公有方法;

2,反射機制的setAccessible可能會破壞封裝性,能夠任意訪問私有方法和私有變量;

3,setAccessible並非將private改成public,事實上,public方法的accessible屬性也是false的,setAccessible只是取消了安全訪問控制檢查,因此經過設置setAccessible,能夠跳過訪問控制檢查,執行的效率也比較高。參考:http://blog.csdn.net/devilkin64/article/details/7766792

反射的性能

反射機制給予Java開發很大的靈活性,但反射機制自己也有缺點,表明性的缺陷就是反射的性能,通常來講,經過反射調用方法的效率比直接調用的效率要至少慢一倍以上。

關於性能的問題,能夠參考這篇博客http://blog.csdn.net/l_serein/article/details/6219897

反射與設計模式

反射的一個很重要的做用,就是在設計模式中的應用,包括在工廠模式和代理模式中的應用。關於這一方面,我會在後續的文章中介紹,有興趣的朋友也先能夠參考這篇文章http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中關於動態代理模式實現方法的文章。

參考資料

JAVA編程思想,第14章

Java-RTTI與反射機制--詳細 :http://blog.csdn.net/dahaizisheng/article/details/1762327

Java反射詳解:http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

RTTI和反射機制:http://blog.sina.com.cn/s/blog_5ea2d6840100v9bu.html

Java中的RTTI和反射機制:http://blog.csdn.net/a81895898/article/details/8457623

Java反射性能測試:http://blog.csdn.net/l_serein/article/details/6219897

相關文章
相關標籤/搜索