JAVA反射與註解

前言

現在在我們構建自己或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,比如:Butter KnifeRetrofit 2Dagger 2GreenDao等,如果你沒用過,那你需要找時間補一下啦;有時在使用後我們會好奇他們到底是怎麼做到這種簡潔、高效、松耦合等諸多優點的,當然這裏我不探討它們具體怎麼實現的 (可以看看我之前寫的幾篇文章) ,而關心的是它們都用到同樣的技術那就是本篇所講的反射註解,並實現的依賴注入。

閱讀本篇文章有助於你更好的理解這些大形框架的原理和複習Java的知識點。爲什麼要把反射放在前面講呢,實際上是因爲我們學習註解的時候需要用到反射機制,所以,先學習反射有助於理解後面的知識。

JAVA反射

主要是指程序可以訪問,檢測和修改它本身狀態或行爲的一種能力,並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。

反射機制是什麼

面試有可能會問到,這句話不管你能不能理解,但是你只要記住就可以了

反射機制就是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。

用一句話總結就是反射可以實現在運行時可以知道任意一個類屬性和方法

反射機制能做什麼

反射機制主要提供了以下功能:

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具有的成員變量和方法;
  • 在運行時調用任意一個對象的方法;
  • 生成動態代理(ps:這個知識點也很重要,後續會爲大家講到)

Java 反射機制的應用場景

  • 逆向代碼 ,例如反編譯
  • 與註解相結合的框架 例如Retrofit
  • 單純的反射機制應用框架 例如EventBus
  • 動態生成類框架 例如Gson

反射機制的優點與缺點

爲什麼要用反射機制?直接創建對象不就可以了嗎,這就涉及到了動態與靜態的概念

  • 靜態編譯:在編譯時確定類型,綁定對象,即通過。

  • 動態編譯:運行時確定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以降低類之間的藕合性。

優點

  • 可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯後,發佈了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個軟件肯定是沒有多少人用的。採用靜態的話,需要把整個程序重新編譯一次纔可以實現功能的更新,而採用反射機制的話,它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。

缺點

  • 對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於只直接執行相同的操作。

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)
對於普通的對象,我們一般都會這樣創建和表示:

1
Code code1 = new Code();

上面說了,所有的類都是Class的對象,那麼如何表示呢,可不可以通過如下方式呢:

1
Class c = new Class();

但是我們查看Class的源碼時,是這樣寫的:

1
2
3
private  Class(ClassLoader loader) { 
    classLoader = loader; 
}

可以看到構造器是私有的,只有JVM可以創建Class的對象,因此不可以像普通類一樣new一個Class對象,雖然我們不能new一個Class對象,但是卻可以通過已有的類得到一個Class對象,共有三種方式,如下:

1
2
3
Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是通過獲取類的靜態成員變量class得到的
Class c2 = code1.getClass(); code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的 
Class c3 = Class.forName("com.trigl.reflect.Code"); 這種方法是Class類調用forName方法,通過一個類的全量限定名獲得

這裏,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這裏就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那麼Code和c1、c2、c3不就一樣了嗎?爲什麼還叫Code什麼類類型?這裏不要糾結於它們是否相同,只要理解類類型是幹什麼的就好了,顧名思義,類類型就是類的類型,也就是描述一個類是什麼,都有哪些東西,所以我們可以通過類類型知道一個類的屬性和方法,並且可以調用一個類的屬性和方法,這就是反射的基礎。

舉個簡單例子代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一種:Class c1 = Code.class;
        Class class1=ReflectDemo.class;
        System.out.println(class1.getName());

        //第二種:Class c2 = code1.getClass();
        ReflectDemo demo2= new ReflectDemo();
        Class c2 = demo2.getClass();
        System.out.println(c2.getName());

        //第三種:Class c3 = Class.forName("com.trigl.reflect.Code");
        Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
        System.out.println(class3.getName());
    }
}

執行結果:

1
2
3
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

Java反射相關操作

在這裏先看一下sun爲我們提供了那些反射機制中的類:
java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;

前面我們知道了怎麼獲取Class,那麼我們可以通過這個Class幹什麼呢?
總結如下:

  • 獲取成員方法Method
  • 獲取成員變量Field
  • 獲取構造函數Constructor

下面來具體介紹

  1. 獲取成員方法信息

    兩個參數分別是方法名和方法參數類的類類型列表。

1
2
3
4
5
6
7
8
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法,不包括父類的 
public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的

//具體使用
Method[] methods = class1.getDeclaredMethods();//獲取class對象的所有聲明方法 
Method[] allMethods = class1.getMethods();//獲取class對象的所有public方法 包括父類的方法 
Method method = class1.getMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的public方法 
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的方法

舉個例子:

例如類A有如下一個方法:

1
2
3
public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"歲");
    }

現在知道A有一個對象a,那麼就可以通過:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person");  //先生成class
Object o = c.newInstance();                           //newInstance可以初始化一個實例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10);                         //通過invoke調用該方法,參數第一個爲實例對象,後面爲具體參數值

完整代碼如下:

1
2
3
4
5
6
7
8
9
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
47
48
49
public class Person {
    private String name;
    private int age;
    private String msg="hello wrold";
 public String getName() {
        return name;
  }

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

    public int getAge() {
        return age;
  }

    public void setAge(int age) {
        this.age = age;
  }

    public Person() {
    }

    private Person(String name) {
        this.name = name;
  System.out.println(name);
  }

    public void fun() {
        System.out.println("fun");
  }

    public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"歲");
  }
}

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Object o = c.newInstance();
            Method method = c.getMethod("fun", String.class, int.class);
            method.invoke(o, "tengj", 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

我叫tengj,今年10歲

怎樣,是不是感覺很厲害,我們只要知道這個類的路徑全稱就能玩弄它於鼓掌之間。

有時候我們想獲取類中所有成員方法的信息,要怎麼辦。可以通過以下幾步來實現:

1.獲取所有方法的數組:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到該類所有的方法,不包括父類的
或者:
Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的

2.然後循環這個數組就得到每個方法了:

1
for (Method method : methods)

完整代碼如下:
person類跟上面一樣,這裏以及後面就不貼出來了,只貼關鍵代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods){
                String  methodName= m.getName();
                System.out.println(methodName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

getName
setName
setAge
fun
fun
getAge

這裏如果把c.getDeclaredMethods();改成c.getMethods();執行結果如下,多了很多方法,以爲把Object裏面的方法也打印出來了,因爲Object是所有類的父類:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
  1. 獲取成員變量信息

想一想成員變量中都包括什麼:成員變量類型+成員變量名

類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field裏面封裝的方法來獲取這些信息。

單獨獲取某個成員變量,通過Class類的以下方法實現:

參數是成員變量的名字

1
2
3
4
5
6
7
8
public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量
public Field getField(String name) // 獲得該類自所有的public成員變量,包括其父類變量

//具體實現
Field[] allFields = class1.getDeclaredFields();//獲取class對象的所有屬性 
Field[] publicFields = class1.getFields();//獲取class對象的public屬性 
Field ageField = class1.getDeclaredField("age");//獲取class指定屬性 
Field desField = class1.getField("des");//獲取class指定的public屬性

舉個例子:

例如一個類A有如下成員變量:

1
private int n;

如果A有一個對象a,那麼就可以這樣得到其成員變量:

1
2
Class c = a.getClass();
Field field = c.getDeclaredField("n");

完整代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取成員變量
            Field field = c.getDeclaredField("msg"); //因爲msg變量是private的,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//設置是否允許訪問,因爲該變量是private的,所以要手動設置允許訪問,如果msg是public的就不需要這行了。
            Object msg = field.get(o);
            System.out.println(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

hello wrold

同樣,如果想要獲取所有成員變量的信息,可以通過以下幾步

1.獲取所有成員變量的數組:

1
Field[] fields = c.getDeclaredFields();

2.遍歷變量數組,獲得某個成員變量field

1
for (Field field : fields)

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Field[] fields = c.getDeclaredFields();
            for(Field field :fields){
                System.out.println(field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

name
age
msg
  1. 獲取構造函數

最後再想一想構造函數中都包括什麼:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor裏面封裝的方法來獲取這些信息。

單獨獲取某個構造函數,通過Class類的以下方法實現:

這個參數爲構造函數參數類的類類型列表

1
2
3
4
5
6
7
8
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  獲得該類所有的構造器,不包括其父類的構造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構造器,包括父類

//具體
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//獲取class對象的所有聲明構造函數 
Constructor<?>[] publicConstructors = class1.getConstructors();//獲取class對象public構造函數 
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//獲取指定聲明構造函數 
Constructor publicConstructor = class1.getConstructor(String.class);//獲取指定聲明的public構造函數

舉個例子:

例如類A有如下一個構造函數:

1
2
3
public A(String a, int b) {
    // code body
}

那麼就可以通過:

1
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

來獲取這個構造函數。

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取構造函數
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//設置是否允許訪問,因爲該構造器是private的,所以要手動設置允許訪問,如果構造器是public的就不需要這行了。
            constructor.newInstance("tengj");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

tengj

注意:Class的newInstance方法,只能創建只包含無參數的構造函數的類,如果某類只有帶參數的構造函數,那麼就要使用另外一種方式:

1
fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

獲取所有的構造函數,可以通過以下步驟實現:

1.獲取該類的所有構造函數,放在一個數組中:

1
Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構造函數數組,獲得某個構造函數constructor:

1
for (Constructor constructor : constructors)

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
public class ReflectDemo {
    public static void main(String[] args){
            Constructor[] constructors = c.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                System.out.println(constructor);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
  1. 其他方法

註解需要用到的

1
2
3
4
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//獲取class對象的所有註解 
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//獲取class對象指定註解 
Type genericSuperclass = class1.getGenericSuperclass();//獲取class對象的直接超類的 
Type Type[] interfaceTypes = class1.getGenericInterfaces();//獲取class對象的所有接口的type集合

獲取class對象的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean isPrimitive = class1.isPrimitive();//判斷是否是基礎類型 
boolean isArray = class1.isArray();//判斷是否是集合類
 boolean isAnnotation = class1.isAnnotation();//判斷是否是註解類 
boolean isInterface = class1.isInterface();//判斷是否是接口類 
boolean isEnum = class1.isEnum();//判斷是否是枚舉類 
boolean isAnonymousClass = class1.isAnonymousClass();//判斷是否是匿名內部類 
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判斷是否被某個註解類修飾 
String className = class1.getName();//獲取class名字 包含包名路徑 
Package aPackage = class1.getPackage();//獲取class的包信息 
String simpleName = class1.getSimpleName();//獲取class類名 
int modifiers = class1.getModifiers();//獲取class訪問權限 
Class<?>[] declaredClasses = class1.getDeclaredClasses();//內部類 
Class<?> declaringClass = class1.getDeclaringClass();//外部類

getSuperclass():獲取某類的父類  
getInterfaces():獲取某類實現的接口

通過反射了解集合泛型的本質

擴展的知識點,瞭解就可以了。後續會爲大家寫一篇關於泛型的文章。

首先下結論:

Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。

下面通過一個實例來驗證:

1
2
3
4
5
6
7
8
9
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
/**
 * 集合泛型的本質
 */
public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 沒有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型


        /*
         * 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
         * 這個時候如果list2添加int類型會報錯
         */
        list2.add("hello");
//      list2.add(20); // 報錯!list2有泛型限制,只能添加String,添加int報錯
        System.out.println("list2的長度是:" + list2.size()); // 此時list2長度爲1


        /*
         * 2.然後通過反射添加元素方式,在運行期動態加載類,首先得到list1和list2
         * 的類類型相同,然後再通過方法反射繞過編譯器來調用add方法,看能否插入int
         * 型的元素
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 結果:true,說明類類型完全相同

        // 驗證:我們可以通過方法的反射來給list2添加元素,這樣可以繞過編譯檢查
        try {
            Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
            m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
            System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增加了,並沒有泛型檢查
        } catch (Exception e) {
            e.printStackTrace();
        }

        /*
         * 綜上可以看出,在編譯器的時候,泛型會限制集合內元素類型保持一致,但是編譯器結束進入
         * 運行期以後,泛型就不再起作用了,即使是不同類型的元素也可以插入集合。
         */
    }
}

執行結果:

list2的長度是:1
true
list2的長度是:2

思維導圖

有助於理解上述所講的知識點

原文出處:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/

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