Java 反射

從學習Java開始,就入手面向對象、Java反射等等,也懵逼在面向對象的世界中,反問道個人對象是誰,很差意思,可能尚未建立!java

反射是Java的一個特色,也是使本來爲靜態語言的Java,多了那麼一些靈活性,在理解各個框架源碼以及組件內容的時候是一個不錯的知識點,好比註解,這是一個很是常見,又很好使的玩意,以前也有簡單的學習---Java 註解 基礎Java 註解 實踐 (最好先學習Java反射再去玩註解,會頗有幫助)spring

 

從主要如下幾點開始學習數組

  • Class類的使用
  • 方法的反射
  • 成員變量的反射
  • 構造器的反射
  • Java類加載機制

 

Class類 和 面向對象

在面向對象的環境中,萬事萬物皆對象,但也總有例外,Java中有兩個不屬於對象,一個是普通數據類型,一個是靜態的成員框架

普通數據類型有封裝類的彌補,靜態的屬於類,那麼類是否是對象呢,類是對象,是java.lang.Class類的實例對象,看文字還比較容易理解,中文說出來就比較繞口, 英文: there is a class named Classeclipse

 

一個普通的類的實例對象表示函數

public class Coo {
    //Doo的實例對象 以doo表示
    Doo doo=new Doo();
}


class Doo{}

那麼一個Class類的實例對象,有三種表示方式,但不能是new Class,由於下面源碼中也解釋爲何工具

/*
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

上面是Class類中的一個構造器,是私有的,並且註釋說只有JVM建立Class對象,在之前的Java版本中你可能會看到一個無參的構造器,不要緊,那你的構造器也絕對是私有,不能直接建立,在上面中出現了一個JIT編譯的關鍵詞,有興趣的小夥伴能夠研究擴展學習

 

任何類都是Class的實例對象,下面三種方式:this

public class Coo {

    //Doo的實例對象 以doo表示
    Doo doo=new Doo();

    //①能夠看出Doo類有一個隱含的靜態成員變量class
    Class first=Doo.class;

    //②已知類的對象,經過getClass獲取
    Class second=doo.getClass();

    //重理解一次:doo表明Doo類的實例對象,first、second表明的是Class的實例對象
    //這個Class的實例對象又證實說Doo這個類自己是一個實例對象的存在
    //一本正經的胡說八道,那麼給一個官方給出的說法是這樣的 :first、second表示了Doo類的類 類型(class type)
    //全部東西都是對象,類也是對象,是Class的實例對象,這個對象稱爲該類的類類型
    //就能夠分析到,Doo的對象是doo,Doo的類類型是Class的對象first、second
    //無論哪一種表達方式表示Doo的類類型,一個類只多是Class類的一個實例對象,因此first == second

    //③須要異常處理,參數爲類的全稱"com.cloud.eureka.Doo"
    Class third=null;

    {
        try {
            third = Class.forName("com.cloud.eureka.Doo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //依然存在 first == second == third
    //因而可知,咱們能夠經過類的類類型建立該類的對象,經過first、second、third建立
    //須要異常處理,是誰的類的類類型對象,建立的對象就是誰,須要強轉
    //newInstance前提須要無參構造方法
    {
        try {
            Doo dooFirst= (Doo) first.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}


class Doo{}

 

Java 動態加載類信息

三種表示Class的實例對象中,第三種具備很好的動態加載類③spa

  • 能夠表示類的類類型,還能夠動態加載類
  • 區分編譯、運行
  • 編譯時加載類屬於靜態加載類
  • 運行時加載類屬於動態加載類

不少時候,你們都是經過工具(IDEA、eclipse等)進行辦公或者學習,編譯和運行都是由工具來輔助完成的,那麼咱們須要知道編譯、運行的區別

1.2..3...好,咱們獲得了編譯、運行知識的技能

 

只要是在類裏面用到的,都隱含class,對應的類的類類型,以下: 

public class Coo {

    Class c0=int.class;
    Class c1=String.class;
    Class c2=Double.class;
    Class c3=void.class;
    
    // package不是在類裏面的,error
    // Class c4=package.class;
}

 

在Doo類中,寫個方法

class Doo{
    public static void staticVoidMethod(Object o){
        //傳遞的是什麼類型,就是什麼類型
        Class co=o.getClass();
    }
}

o傳遞的是什麼對象,co就是該類的類類型,那麼底層怎麼實現的,可能會比較複雜,貼一份源碼

public final native Class<?> getClass();

這是一個native聲明的一個方法,稱爲本地方法,Java中有一項技術JNR,使用Java聲明,C語言實現,Java 中調用...一堆,有興趣的能夠了解了解,效果就是上面說的,返回類的類類型

 

下面是簡單的經過Class獲取類的信息:

class Doo {
    public static void staticVoidMethod(Object o) {
        //傳遞的是什麼類型,就是什麼類型
        Class co = o.getClass();

        System.out.println("類的全名稱:" + co.getName());
        System.out.println("類的名字:" + co.getSimpleName());

        //Method類,方法對象
        //一個成員方法 就是 一個Method對象
        //getMethods 獲取全部public的方法,其中包括父類繼承的函數
        Method[] allMethods = co.getMethods();

        //getDeclaredMethods獲取該類本身聲明的方法
        Method[] thisMethods = co.getDeclaredMethods();

        for (Method method : allMethods) {
            //method.getReturnType()獲得的是類的類類型
            //好比返回值是String,那麼獲得的是String.class的類類型,經過getName獲取名稱
            System.out.println("返回類型:" + method.getReturnType().getName());

            System.out.println("方法名稱:" + method.getName());

            //獲取參數類型
            Class[] parameterTypes = method.getParameterTypes();
            for (Class c : parameterTypes) {
                System.out.println("參數類型:" + c.getName());
            }
            System.out.println("====================================");
        }

        //成員變量 =》對象
        //屬於java.lang.reflect.Field
        //Field封裝了關於成員變量的操做
        //getFields獲取全部public的成員變量
        Field[] field=co.getFields();
        //獲得本身聲明的成員變量
        Field[] declaredFields=co.getDeclaredFields();
        for (Field fields:field) {
            System.out.println("成員變量類型"+fields.getType());
            System.out.println("成員變量名稱"+fields.getName());
        }
    }
}

簡單來一個main方法,加入一個String類

public class Coo {
    public static void main(String[] args) {
        String hello=new String();
        Doo.staticVoidMethod(hello);
    }
}

控制檯打印 , 全部String內的方法信息: 

類的全名稱:java.lang.String
類的名字:String
返回類型:boolean
方法名稱:equals
參數類型:java.lang.Object
====================================
返回類型:java.lang.String
方法名稱:toString
====================================
返回類型:int
方法名稱:hashCode
====================================
返回類型:int
方法名稱:compareTo
參數類型:java.lang.Object
====================================
//......

能夠總結出來,getDeclaredXXX()方法都是獲取本身聲明的內容,包括成員變量,構造器,方法等等,直接的getXXX()方法部分會獲取全部內容包括父類的內容,另外數組是一個特殊的存在,打印的是「0]」差很少的樣子,在JVM對數組的存儲方式也比較VIP,有興趣的能夠理解擴展

 

方法的反射

上面有獲取全部的方法的示例,下面來學習如何獲取某一個方法以及方法的反射操做

①方法的名稱和方法的參數列表能夠惟必定位某一個方法

②method.invoke(對象,參數列表)

public class MethodReflect {
    //獲取getMethod方法,獲取①號
    public static void main(String[] args) {
        MethodDemo demo = new MethodDemo();
        //1.獲取類信息
        Class c0 = demo.getClass();

        //2.獲取方法
        try {
            //第一種寫法
            Method method1 = c0.getDeclaredMethod("getMethod", new Class[]{String.class, String.class});
            //第二種寫法
            Method method2 = c0.getDeclaredMethod("getMethod", String.class, String.class);

            //平時正常的調用方法: demo.getMethod(str0,str1)
            //如今使用method1來調用--public Object invoke(Object obj, Object... args)
            //第一個參數是調用的類,第二個參數是可用可無,按定義的方法來錄入(str0,str1)
            //invoke的方法若是有返回值,則返回Object的值,void的返回值爲null
            try {
                Object object1 = method1.invoke(demo, new Object[]{"hello", " world"});
                Object object2 = method2.invoke(demo, "hello", " world");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

}

class MethodDemo {
    //①
    public void getMethod(String a, String b) {
        System.out.println("concat: " + a + b);
    }

    //②
    public void getMethod(int a, int b) {
        System.out.println("sum: " + a + b);
    }
}

 

反射和泛型

泛型不說了,很是的經常使用,好比list,map等等,約定類型,很少作解釋,直接先來一個操做,比對List和List<String>是否相等

public class Coo {
    public static void main(String[] args) {
        //無泛型
        List list0=new ArrayList();
        //String泛型
        List<String> list1=new ArrayList<>();
        list1.add("hello");

        Class c0=list0.getClass();
        Class c1=list1.getClass();
        //輸出
        System.out.println(c0==c1);
    }
}

輸出的結果是true,  編譯後的class文件也能夠當成字節碼,說明反射的操做都是編譯以後的操做,並且返回true說明編譯以後list的泛型被抹去了,去泛型化的,獲得Java的泛型是一種規範,只在編譯時有效,跳過編譯編譯就無效了,爲了驗證這一點,恰好可使用反射來作一個驗證

//獲取list1<String> 中的 add方法 ,向裏面加一個int類型的值
Method method=c1.getMethod("add",Object.class);
method.invoke(list1,100);

System.out.println(list1.size());

list1的大小改變了,說明添加成功了,也驗證了Java泛型只在編譯期有效,運行時則去泛型化,若是去遍歷這個list1是會報類型轉化異常的

 

反射的用處有不少,好比工具類,源碼理解,註解解析等等,再例如excel導出導入這樣的操做,網上也有很是多的poi操做案例,也能夠用反射+註解的方式很是簡潔的實現; 例如spring源碼中不少的註解@Autowired、@SpringCloudApplication、@Service...等等不少不少

 

好好學習,每天向上

------------------------------------------------------------

相關文章
相關標籤/搜索