JAVA反射與注解

前言

現(xiàn)在在我們構(gòu)建自己或公司的項(xiàng)目中,或多或少都會(huì)依賴幾個(gè)流行比較屌的第三方庫(kù),比如:Butter KnifeRetrofit 2Dagger 2、GreenDao等,如果你沒(méi)用過(guò),那你需要找時(shí)間補(bǔ)一下啦;有時(shí)在使用后我們會(huì)好奇他們到底是怎么做到這種簡(jiǎn)潔、高效、松耦合等諸多優(yōu)點(diǎn)的,當(dāng)然這里我不探討它們具體怎么實(shí)現(xiàn)的 (可以看看我之前寫的幾篇文章) ,而關(guān)心的是它們都用到同樣的技術(shù)那就是本篇所講的反射注解,并實(shí)現(xiàn)的依賴注入。

閱讀本篇文章有助于你更好的理解這些大形框架的原理和復(fù)習(xí)Java的知識(shí)點(diǎn)。為什么要把反射放在前面講呢,實(shí)際上是因?yàn)槲覀儗W(xué)習(xí)注解的時(shí)候需要用到反射機(jī)制,所以,先學(xué)習(xí)反射有助于理解后面的知識(shí)。

JAVA反射

主要是指程序可以訪問(wèn),檢測(cè)和修改它本身狀態(tài)或行為的一種能力,并能根據(jù)自身行為的狀態(tài)和結(jié)果,調(diào)整或修改應(yīng)用所描述行為的狀態(tài)和相關(guān)的語(yǔ)義。

反射機(jī)制是什么

面試有可能會(huì)問(wèn)到,這句話不管你能不能理解,但是你只要記住就可以了

反射機(jī)制就是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制。

用一句話總結(jié)就是反射可以實(shí)現(xiàn)在運(yùn)行時(shí)可以知道任意一個(gè)類屬性和方法。

反射機(jī)制能做什么

反射機(jī)制主要提供了以下功能:

  • 在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類;
    
  • 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象;
    
  • 在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法;
    
  • 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法;
    
  • 生成[**動(dòng)態(tài)代理**](https://www.daidingkang.cc/2017/07/18/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/)(ps:這個(gè)知識(shí)點(diǎn)也很重要,后續(xù)會(huì)為大家講到)
    

Java 反射機(jī)制的應(yīng)用場(chǎng)景

  • 逆向代碼 ,例如反編譯
    
  • 與注解相結(jié)合的框架 例如Retrofit
    
  • 單純的反射機(jī)制應(yīng)用框架 例如EventBus
    
  • 動(dòng)態(tài)生成類框架 例如Gson
    

反射機(jī)制的優(yōu)點(diǎn)與缺點(diǎn)

為什么要用反射機(jī)制?直接創(chuàng)建對(duì)象不就可以了嗎,這就涉及到了動(dòng)態(tài)與靜態(tài)的概念

  • 靜態(tài)編譯:在編譯時(shí)確定類型,綁定對(duì)象,即通過(guò)。
    
  • 動(dòng)態(tài)編譯:運(yùn)行時(shí)確定類型,綁定對(duì)象。動(dòng)態(tài)編譯最大限度發(fā)揮了java的靈活性,體現(xiàn)了多態(tài)的應(yīng)用,有以降低類之間的藕合性。
    

優(yōu)點(diǎn)

  • 可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象和編譯,體現(xiàn)出很大的靈活性,特別是在J2EE的開發(fā)中它的靈活性就表現(xiàn)的十分明顯。比如,一個(gè)大型的軟件,不可能一次就把把它設(shè)計(jì)的很完美,當(dāng)這個(gè)程序編譯后,發(fā)布了,當(dāng)發(fā)現(xiàn)需要更新某些功能時(shí),我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個(gè)軟件肯定是沒(méi)有多少人用的。采用靜態(tài)的話,需要把整個(gè)程序重新編譯一次才可以實(shí)現(xiàn)功能的更新,而采用反射機(jī)制的話,它就可以不用卸載,只需要在運(yùn)行時(shí)才動(dòng)態(tài)的創(chuàng)建和編譯,就可以實(shí)現(xiàn)該功能。
    

缺點(diǎn)

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

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實(shí)現(xiàn)的基礎(chǔ)。
類是java.lang.Class類的實(shí)例對(duì)象,而Class是所有類的類(There is a class named Class)
對(duì)于普通的對(duì)象,我們一般都會(huì)這樣創(chuàng)建和表示:

Code code1 = new Code();

上面說(shuō)了,所有的類都是Class的對(duì)象,那么如何表示呢,可不可以通過(guò)如下方式呢:

Class c = new Class();

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

private  Class(ClassLoader loader) { 
    classLoader = loader; 
}

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

Class c1 = Code.class; 這說(shuō)明任何一個(gè)類都有一個(gè)隱含的靜態(tài)成員變量class,這種方式是通過(guò)獲取類的靜態(tài)成員變量class得到的
Class c2 = code1.getClass(); code1是Code的一個(gè)對(duì)象,這種方式是通過(guò)一個(gè)類的對(duì)象的getClass()方法獲得的 
Class c3 = Class.forName("com.trigl.reflect.Code"); 這種方法是Class類調(diào)用forName方法,通過(guò)一個(gè)類的全量限定名獲得

這里,c1、c2、c3都是Class的對(duì)象,他們是完全一樣的,而且有個(gè)學(xué)名,叫做Code的類類型(class type)。
這里就讓人奇怪了,前面不是說(shuō)Code是Class的對(duì)象嗎,而c1、c2、c3也是Class的對(duì)象,那么Code和c1、c2、c3不就一樣了嗎?為什么還叫Code什么類類型?這里不要糾結(jié)于它們是否相同,只要理解類類型是干什么的就好了,顧名思義,類類型就是類的類型,也就是描述一個(gè)類是什么,都有哪些東西,所以我們可以通過(guò)類類型知道一個(gè)類的屬性和方法,并且可以調(diào)用一個(gè)類的屬性和方法,這就是反射的基礎(chǔ)。

舉個(gè)簡(jiǎn)單例子代碼:

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());
    }
}

執(zhí)行結(jié)果:

com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

Java反射相關(guān)操作

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

前面我們知道了怎么獲取Class,那么我們可以通過(guò)這個(gè)Class干什么呢?
總結(jié)如下:

  • 獲取成員方法Method
    
  • 獲取成員變量Field
    
  • 獲取構(gòu)造函數(shù)Constructor
    

下面來(lái)具體介紹

  1. 獲取成員方法信息

兩個(gè)參數(shù)分別是方法名和方法參數(shù)類的類類型列表。

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法,不包括父類的 
public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的

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

舉個(gè)例子:

例如類A有如下一個(gè)方法:

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

現(xiàn)在知道A有一個(gè)對(duì)象a,那么就可以通過(guò):

Class c = Class.forName("com.tengj.reflect.Person");  //先生成class
Object o = c.newInstance();                           //newInstance可以初始化一個(gè)實(shí)例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10);                         //通過(guò)invoke調(diào)用該方法,參數(shù)第一個(gè)為實(shí)例對(duì)象,后面為具體參數(shù)值

完整代碼如下:

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();
        }
    }
}

執(zhí)行結(jié)果:

我叫tengj,今年10歲

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

有時(shí)候我們想獲取類中所有成員方法的信息,要怎么辦??梢酝ㄟ^(guò)以下幾步來(lái)實(shí)現(xiàn):

1.獲取所有方法的數(shù)組:

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

2.然后循環(huán)這個(gè)數(shù)組就得到每個(gè)方法了:

for (Method method : methods)

完整代碼如下:
person類跟上面一樣,這里以及后面就不貼出來(lái)了,只貼關(guān)鍵代碼

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();
        }
    }
}

執(zhí)行結(jié)果:

getName
setName
setAge
fun
fun
getAge

這里如果把c.getDeclaredMethods();改成c.getMethods();執(zhí)行結(jié)果如下,多了很多方法,以為把Object里面的方法也打印出來(lái)了,因?yàn)镺bject是所有類的父類:

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

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

類的成員變量也是一個(gè)對(duì)象,它是java.lang.reflect.Field的一個(gè)對(duì)象,所以我們通過(guò)java.lang.reflect.Field里面封裝的方法來(lái)獲取這些信息。

單獨(dú)獲取某個(gè)成員變量,通過(guò)Class類的以下方法實(shí)現(xiàn):

參數(shù)是成員變量的名字

public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量
public Field getField(String name) // 獲得該類自所有的public成員變量,包括其父類變量

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

舉個(gè)例子:

例如一個(gè)類A有如下成員變量:

private int n;

如果A有一個(gè)對(duì)象a,那么就可以這樣得到其成員變量:

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

完整代碼如下:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取成員變量
            Field field = c.getDeclaredField("msg"); //因?yàn)閙sg變量是private的,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//設(shè)置是否允許訪問(wèn),因?yàn)樵撟兞渴莗rivate的,所以要手動(dòng)設(shè)置允許訪問(wèn),如果msg是public的就不需要這行了。
            Object msg = field.get(o);
            System.out.println(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

hello wrold

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

1.獲取所有成員變量的數(shù)組:

Field[] fields = c.getDeclaredFields();

2.遍歷變量數(shù)組,獲得某個(gè)成員變量field

for (Field field : fields)

完整代碼:

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();
        }
    }
}

執(zhí)行結(jié)果:

name
age
msg
  1. 獲取構(gòu)造函數(shù)

最后再想一想構(gòu)造函數(shù)中都包括什么:構(gòu)造函數(shù)參數(shù)
同上,類的成構(gòu)造函數(shù)也是一個(gè)對(duì)象,它是java.lang.reflect.Constructor的一個(gè)對(duì)象,所以我們通過(guò)java.lang.reflect.Constructor里面封裝的方法來(lái)獲取這些信息。

單獨(dú)獲取某個(gè)構(gòu)造函數(shù),通過(guò)Class類的以下方法實(shí)現(xiàn):

這個(gè)參數(shù)為構(gòu)造函數(shù)參數(shù)類的類類型列表

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  獲得該類所有的構(gòu)造器,不包括其父類的構(gòu)造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構(gòu)造器,包括父類

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

舉個(gè)例子:

例如類A有如下一個(gè)構(gòu)造函數(shù):

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

那么就可以通過(guò):

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

來(lái)獲取這個(gè)構(gòu)造函數(shù)。

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取構(gòu)造函數(shù)
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//設(shè)置是否允許訪問(wèn),因?yàn)樵摌?gòu)造器是private的,所以要手動(dòng)設(shè)置允許訪問(wèn),如果構(gòu)造器是public的就不需要這行了。
            constructor.newInstance("tengj");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

tengj

注意:Class的newInstance方法,只能創(chuàng)建只包含無(wú)參數(shù)的構(gòu)造函數(shù)的類,如果某類只有帶參數(shù)的構(gòu)造函數(shù),那么就要使用另外一種方式:

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

獲取所有的構(gòu)造函數(shù),可以通過(guò)以下步驟實(shí)現(xiàn):

1.獲取該類的所有構(gòu)造函數(shù),放在一個(gè)數(shù)組中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構(gòu)造函數(shù)數(shù)組,獲得某個(gè)構(gòu)造函數(shù)constructor:

for (Constructor constructor : constructors)

完整代碼:

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();
        }
    }
}

執(zhí)行結(jié)果:

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

注解需要用到的

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

獲取class對(duì)象的信息

boolean isPrimitive = class1.isPrimitive();//判斷是否是基礎(chǔ)類型 
boolean isArray = class1.isArray();//判斷是否是集合類
 boolean isAnnotation = class1.isAnnotation();//判斷是否是注解類 
boolean isInterface = class1.isInterface();//判斷是否是接口類 
boolean isEnum = class1.isEnum();//判斷是否是枚舉類 
boolean isAnonymousClass = class1.isAnonymousClass();//判斷是否是匿名內(nèi)部類 
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判斷是否被某個(gè)注解類修飾 
String className = class1.getName();//獲取class名字 包含包名路徑 
Package aPackage = class1.getPackage();//獲取class的包信息 
String simpleName = class1.getSimpleName();//獲取class類名 
int modifiers = class1.getModifiers();//獲取class訪問(wèn)權(quán)限 
Class<?>[] declaredClasses = class1.getDeclaredClasses();//內(nèi)部類 
Class<?> declaringClass = class1.getDeclaringClass();//外部類

getSuperclass():獲取某類的父類  
getInterfaces():獲取某類實(shí)現(xiàn)的接口

通過(guò)反射了解集合泛型的本質(zhì)

擴(kuò)展的知識(shí)點(diǎn),了解就可以了。后續(xù)會(huì)為大家寫一篇關(guān)于泛型的文章。

首先下結(jié)論:

Java中集合的泛型,是防止錯(cuò)誤輸入的,只在編譯階段有效,繞過(guò)編譯到了運(yùn)行期就無(wú)效了。

下面通過(guò)一個(gè)實(shí)例來(lái)驗(yàn)證:

/**
 * 集合泛型的本質(zhì)
 */
public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 沒(méi)有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型


        /*
         * 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
         * 這個(gè)時(shí)候如果list2添加int類型會(huì)報(bào)錯(cuò)
         */
        list2.add("hello");
//      list2.add(20); // 報(bào)錯(cuò)!list2有泛型限制,只能添加String,添加int報(bào)錯(cuò)
        System.out.println("list2的長(zhǎng)度是:" + list2.size()); // 此時(shí)list2長(zhǎng)度為1


        /*
         * 2.然后通過(guò)反射添加元素方式,在運(yùn)行期動(dòng)態(tài)加載類,首先得到list1和list2
         * 的類類型相同,然后再通過(guò)方法反射繞過(guò)編譯器來(lái)調(diào)用add方法,看能否插入int
         * 型的元素
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 結(jié)果:true,說(shuō)明類類型完全相同

        // 驗(yàn)證:我們可以通過(guò)方法的反射來(lái)給list2添加元素,這樣可以繞過(guò)編譯檢查
        try {
            Method m = c2.getMethod("add", Object.class); // 通過(guò)方法反射得到add方法
            m.invoke(list2, 20); // 給list2添加一個(gè)int型的,上面顯示在編譯器是會(huì)報(bào)錯(cuò)的
            System.out.println("list2的長(zhǎng)度是:" + list2.size()); // 結(jié)果:2,說(shuō)明list2長(zhǎng)度增加了,并沒(méi)有泛型檢查
        } catch (Exception e) {
            e.printStackTrace();
        }

        /*
         * 綜上可以看出,在編譯器的時(shí)候,泛型會(huì)限制集合內(nèi)元素類型保持一致,但是編譯器結(jié)束進(jìn)入
         * 運(yùn)行期以后,泛型就不再起作用了,即使是不同類型的元素也可以插入集合。
         */
    }
}

執(zhí)行結(jié)果:

list2的長(zhǎng)度是:1
true
list2的長(zhǎng)度是:2

思維導(dǎo)圖

有助于理解上述所講的知識(shí)點(diǎn)

拓展閱讀
Java反射機(jī)制深入詳解 - 火星十一郎 - 博客園
Java反射入門 - Trigl的博客 - CSDN博客
Java反射機(jī)制 - ①塊腹肌 - 博客園
Java 反射機(jī)制淺析 - 孤旅者 - 博客園
反射機(jī)制的理解及其用途 - 每天進(jìn)步一點(diǎn)點(diǎn)! - ITeye博客
Java動(dòng)態(tài)代理與反射詳解 - 浩大王 - 博客園

JAVA注解

概念及作用

  1. 概念
  • 注解即元數(shù)據(jù),就是源代碼的元數(shù)據(jù)
  • 注解在代碼中添加信息提供了一種形式化的方法,可以在后續(xù)中更方便的 使用這些數(shù)據(jù)
  • Annotation是一種應(yīng)用于類、方法、參數(shù)、變量、構(gòu)造器及包聲明中的特殊修飾符。它是一種由JSR-175標(biāo)準(zhǔn)選擇用來(lái)描述元數(shù)據(jù)的一種工具。
  1. 作用
  • 生成文檔
  • 跟蹤代碼依賴性,實(shí)現(xiàn)替代配置文件功能,減少配置。如Spring中的一些注解
  • 在編譯時(shí)進(jìn)行格式檢查,如@Override等
  • 每當(dāng)你創(chuàng)建描述符性質(zhì)的類或者接口時(shí),一旦其中包含重復(fù)性的工作,就可以考慮使用注解來(lái)簡(jiǎn)化與自動(dòng)化該過(guò)程。

什么是java注解?

在java語(yǔ)法中,使用@符號(hào)作為開頭,并在@后面緊跟注解名。被運(yùn)用于類,接口,方法和字段之上,例如:

@Override
void myMethod() { 
......
}

這其中@Override就是注解。這個(gè)注解的作用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。

java中內(nèi)置的注解

java中有三個(gè)內(nèi)置的注解:

  • @Override:表示當(dāng)前的方法定義將覆蓋超類中的方法,如果出現(xiàn)錯(cuò)誤,編譯器就會(huì)報(bào)錯(cuò)。
  • @Deprecated:如果使用此注解,編譯器會(huì)出現(xiàn)警告信息。
  • @SuppressWarnings:忽略編譯器的警告信息。

本文不在闡述三種內(nèi)置注解的使用情節(jié)和方法,感興趣的請(qǐng)看這里

元注解

自定義注解的時(shí)候用到的,也就是自定義注解的注解;(這句話我自己說(shuō)的,不知道對(duì)不對(duì))

元注解的作用就是負(fù)責(zé)注解其他注解。Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類型,它們被用來(lái)提供對(duì)其它 annotation類型作說(shuō)明。

Java5.0定義的4個(gè)元注解:

  1. @Target

  2. @Retention

  3. @Documented

  4. @Inherited

java8加了兩個(gè)新注解,后續(xù)我會(huì)講到。

這些類型和它們所支持的類在java.lang.annotation包中可以找到。

@Target

@Target說(shuō)明了Annotation所修飾的對(duì)象范圍:Annotation可被用于 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標(biāo)。

作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

類型 | 用途
----|------|----
CONSTRUCTOR | 用于描述構(gòu)造器
FIELD | 用于描述域
LOCAL_VARIABLE | 用于描述局部變量
METHOD | 用于描述方法
PACKAGE | 用于描述包
PARAMETER | 用于描述參數(shù)
TYPE | 用于描述類、接口(包括注解類型) 或enum聲明

比如說(shuō)這個(gè)注解表示只能在方法中使用:

@Target({ElementType.METHOD})
public @interface MyCustomAnnotation {

}

//使用
public class MyClass {
   @MyCustomAnnotation
   public void myMethod()
   {
    ......
   }
}

@Retention

@Retention定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀?。ㄕ?qǐng)注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對(duì) Annotation的“生命周期”限制。

作用:表示需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)

取值(RetentionPoicy)有:

類型 用途 說(shuō)明
SOURCE 在源文件中有效(即源文件保留) 僅出現(xiàn)在源代碼中,而被編譯器丟棄
CLASS 在class文件中有效(即class保留) 被編譯在class文件中
RUNTIME 在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留) 編譯在class文件中

使用示例:

/***
 * 字段注解接口
 */
@Target(value = {ElementType.FIELD})//注解可以被添加在屬性上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM運(yùn)行時(shí)刻,能夠在運(yùn)行時(shí)刻通過(guò)反射API來(lái)獲取到注解的信息
public @interface Column {
    String name();//注解的name屬性
}

@Documented

@Documented用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個(gè)標(biāo)記注解,沒(méi)有成員。

作用:將注解包含在javadoc中

示例:

java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}

@Inherited

  • 是一個(gè)標(biāo)記注解
  • 闡述了某個(gè)被標(biāo)注的類型是被繼承的
  • 使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類
    @Inherited annotation類型是被標(biāo)注過(guò)的class的子類所繼承。類并不從實(shí)現(xiàn)的接口繼承annotation,方法不從它所重載的方法繼承annotation
  • 當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。

作用:允許子類繼承父類中的注解

示例,這里的MyParentClass 使用的注解標(biāo)注了@Inherited,所以子類可以繼承這個(gè)注解信息:

java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
@MyCustomAnnotation
public class MyParentClass { 
  ... 
}
public class MyChildClass extends MyParentClass { 
   ... 
}

自定義注解

格式

public @interface 注解名{
  定義體
}

注解參數(shù)的可支持?jǐn)?shù)據(jù)類型:

  • 所有基本數(shù)據(jù)類型(int,float,double,boolean,byte,char,long,short)
  • String 類型
  • Class類型
  • enum類型
  • Annotation類型
  • 以上所有類型的數(shù)組

規(guī)則

  • 修飾符只能是public 或默認(rèn)(default)
  • 參數(shù)成員只能用基本類型byte,short,int,long,float,double,boolean八種基本類型和String,Enum,Class,annotations及這些類型的數(shù)組
  • 如果只有一個(gè)參數(shù)成員,最好將名稱設(shè)為"value"
  • 注解元素必須有確定的值,可以在注解中定義默認(rèn)值,也可以使用注解時(shí)指定,非基本類型的值不可為null,常使用空字符串或0作默認(rèn)值
  • 在表現(xiàn)一個(gè)元素存在或缺失的狀態(tài)時(shí),定義一下特殊值來(lái)表示,如空字符串或負(fù)值

示例:

/**
 * test注解
 * @author ddk
 *
 */ 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    /**
     * id
     * @return
     */
    public int id() default -1;
    /**
     * name
     * @return
     */
    public String name() default "";
}

注解處理器類庫(kù)

java.lang.reflect.AnnotatedElement

Java使用Annotation接口來(lái)代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個(gè)實(shí)現(xiàn)類:

  • Class:類定義
  • Constructor:構(gòu)造器定義
  • Field:累的成員變量定義
  • Method:類的方法定義
  • Package:類的包定義

java.lang.reflect 包下主要包含一些實(shí)現(xiàn)反射功能的工具類,實(shí)際上,java.lang.reflect 包所有提供的反射API擴(kuò)充了讀取運(yùn)行時(shí)Annotation信息的能力。當(dāng)一個(gè)Annotation類型被定義為運(yùn)行時(shí)的Annotation后,該注解才能是運(yùn)行時(shí)可見(jiàn),當(dāng)class文件被裝載時(shí)被保存在class文件中的Annotation才會(huì)被虛擬機(jī)讀取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過(guò)反射獲取了某個(gè)類的AnnotatedElement對(duì)象之后,程序就可以調(diào)用該對(duì)象的如下四個(gè)個(gè)方法來(lái)訪問(wèn)Annotation信息:

  • 方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
  • 方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒(méi)有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。

注解處理器示例:

/***********注解聲明***************/

/**
 * 水果名稱注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

/**
 * 水果顏色注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     * @author peida
     *
     */
    public enum Color{ BULE,RED,GREEN};
    
    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.GREEN;

}

/**
 * 水果供應(yīng)者注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應(yīng)商編號(hào)
     * @return
     */
    public int id() default -1;
    
    /**
     * 供應(yīng)商名稱
     * @return
     */
    public String name() default "";
    
    /**
     * 供應(yīng)商地址
     * @return
     */
    public String address() default "";
}

/***********注解使用***************/

public class Apple {
    
    @FruitName("Apple")
    private String appleName;
    
    @FruitColor(fruitColor=Color.RED)
    private String appleColor;
    
    @FruitProvider(id=1,name="陜西紅富士集團(tuán)",address="陜西省西安市延安路89號(hào)紅富士大廈")
    private String appleProvider;
    
    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }
    public String getAppleColor() {
        return appleColor;
    }
    
    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }
    public String getAppleName() {
        return appleName;
    }
    
    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }
    public String getAppleProvider() {
        return appleProvider;
    }
    
    public void displayName(){
        System.out.println("水果的名字是:蘋果");
    }
}

/***********注解處理器***************/
//其實(shí)是用的反射


public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz){
        
        String strFruitName=" 水果名稱:";
        String strFruitColor=" 水果顏色:";
        String strFruitProvicer="供應(yīng)商信息:";
        
        Field[] fields = clazz.getDeclaredFields();
        
        for(Field field :fields){
            if(field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                strFruitName=strFruitName+fruitName.value();
                System.out.println(strFruitName);
            }
            else if(field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
                strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            }
            else if(field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
                strFruitProvicer=" 供應(yīng)商編號(hào):"+fruitProvider.id()+" 供應(yīng)商名稱:"+fruitProvider.name()+" 供應(yīng)商地址:"+fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
    }
}

/***********輸出結(jié)果***************/
public class FruitRun {

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        FruitInfoUtil.getFruitInfo(Apple.class);
        
    }

}

====================================
 水果名稱:Apple
 水果顏色:RED
 供應(yīng)商編號(hào):1 供應(yīng)商名稱:陜西紅富士集團(tuán) 供應(yīng)商地址:陜西省西安市延安路89號(hào)紅富士大廈


Java 8 中注解新特性

  • @Repeatable 元注解,表示被修飾的注解可以用在同一個(gè)聲明式或者類型加上多個(gè)相同的注解(包含不同的屬性值)
  • @Native 元注解,本地方法
  • java8 中Annotation 可以被用在任何使用 Type 的地方
  //初始化對(duì)象時(shí)
 String myString = new @NotNull String();
 //對(duì)象類型轉(zhuǎn)化時(shí)
 myString = (@NonNull String) str;
 //使用 implements 表達(dá)式時(shí)
 class MyList<T> implements @ReadOnly List<@ReadOnly T>{
 ...
 }
 //使用 throws 表達(dá)式時(shí)
 public void validateValues() throws @Critical ValidationFailedException{
 ...
 }

思維導(dǎo)圖

拓展閱讀

深入理解Java:注解 - 牛奶、不加糖 - 博客園
Java 注解基礎(chǔ)知識(shí) - 簡(jiǎn)書
【譯】從java注解分析ButterKnife工作流程 - 簡(jiǎn)書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容