Java反射

 反射是Java的一個特點,也是使原本爲靜態語言的Java,多了那麼一些靈活性,在理解各個框架源碼以及組件內容的時候是一個不錯的知識點,比如註解,這是一個非常常見,又很好使的玩意,之前也有簡單的學習---Java 註解 基礎、Java 註解 實踐
從主要以下幾點開始學習
  • Class類的使用
  • 方法的反射
  • 成員變量的反射
  • 構造器的反射
  • Java類加載機制
  •  
Class類 和 面向對象
在面向對象的環境中,萬事萬物皆對象,但也總有例外,Java中有兩個不屬於對象,一個是普通數據類型,一個是靜態的成員
普通數據類型有封裝類的彌補,靜態的屬於類,那麼類是不是對象呢,類是對象,是java.lang.Class類的實例對象,看文字還比較容易理解,中文說出來就比較繞口, 英文: there is a class named Class
一個普通的類的實例對象表示

 
[AppleScript] 純文本查看 複製代碼
1
2
3
4
5
6
public class Coo {
    //Doo的實例對象 以doo表示
    Doo doo=new Doo();
}
 
class Doo{}
那麼一個Class類的實例對象,有三種表示方式,但不能是new Class,因爲下面源碼中也解釋爲什麼
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
/*
 * 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的實例對象,下面三種方式:
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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的實例對象中,第三種具有很好的動態加載類③
  • 可以表示類的類類型,還可以動態加載類
  • 區分編譯、運行
  • 編譯時加載類屬於靜態加載類
  • 運行時加載類屬於動態加載類
很多時候,大家都是通過工具(IDEA、eclipse等)進行辦公或者學習,編譯和運行都是由工具來輔助完成的,那麼我們需要知道編譯、運行的區別
1.2..3...好,我們得到了編譯、運行知識的技能
只要是在類裏面用到的,都隱含class,對應的類的類類型,如下:
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
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類中,寫個方法
[Java] 純文本查看 複製代碼
1
2
3
4
5
6
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獲取類的信息:
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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類
[Java] 純文本查看 複製代碼
1
2
3
4
5
6
public class Coo {
    public static void main(String[] args) {
        String hello=new String();
        Doo.staticVoidMethod(hello);
    }
}
控制檯打印 , 所有String內的方法信息:
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
類的全名稱:java.lang.String[/font][/align]類的名字:String
返回類型:boolean
方法名稱:equals
參數類型:java.lang.Object
====================================
返回類型:java.lang.String
方法名稱:toString
====================================
返回類型:int
方法名稱:hashCode
====================================
返回類型:int
方法名稱:compareTo
參數類型:java.lang.Object
====================================
//......
 
可以總結出來,getDeclaredXXX()方法都是獲取自己聲明的內容,包括成員變量,構造器,方法等等,直接的getXXX()方法部分會獲取所有內容包括父類的內容,另外數組是一個特殊的存在,打印的是“0]”差不多的樣子,在JVM對數組的存儲方式也比較VIP,有興趣的可以理解擴展
方法的反射
上面有獲取所有的方法的示例,下面來學習如何獲取某一個方法以及方法的反射操作
①方法的名稱和方法的參數列表可以唯一定位某一個方法
②method.invoke(對象,參數列表)
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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>是否相等
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
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的泛型是一種規範,只在編譯時有效,跳過編譯編譯就無效了,爲了驗證這一點,剛好可以使用反射來做一個驗證
[Java] 純文本查看 複製代碼
1
2
3
4
5
//獲取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...等等很多很多
 
 



  •  

1.png (50.37 KB, 下載次數: 0)

 

1.png
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章