JAVA註解Annotation

JAVA註解Annotation

什麼是註解?

  用一個詞就可以描寫敘述註解。那就是元數據,即一種描寫敘述數據的數據。因此,可以說註解就是源碼的元數據。php

比方。如下這段代碼:java

@Override
publicString toString() {
    return"This is String Representation of current object.";
}

  上面的代碼中。我重寫了toString()方法並使用了@Override註解。但是,即便我不使用@Override註解標記代碼。程序也可以正常執行。那麼。該註解表示什麼?這麼寫有什麼優勢嗎?其實。@Override告訴編譯器這種方法是一個重寫方法(描寫敘述方法的元數據),假設父類中不存在該方法,編譯器便會報錯,提示該方法沒有重寫父類中的方法。數組

假設我不當心拼寫錯誤。好比將toString()寫成了toStrring(){double r},並且我也沒有使用@Override註解。那程序依舊能編譯執行。ruby

但執行結果會和我指望的大不一樣樣。現在咱們瞭解了什麼是註解,並且使用註解有助於閱讀程序。markdown


  Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描寫敘述元數據的一種工具。架構

爲何要引入註解?

  使用Annotation以前(甚至在使用以後),XML被普遍的應用於描寫敘述元數據。不知什麼時候開始一些應用開發者和架構師發現XML的維護愈來愈糟糕了。他們但願使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是鬆耦合的(在某些狀況下甚至是全然分離的)代碼描寫敘述。app

假設你在Google中搜索「XML vs. annotations」,會看到不少關於這個問題的辯論。最有趣的是XML配置其實就是爲了分離代碼和配置而引入的。上述兩種觀點可能會讓你很是疑惑,二者觀點彷佛構成了一種循環。但各有利弊。如下咱們經過一個樣例來理解這二者的差異。
  假如你想爲應用設置很是多的常量或參數,這樣的狀況下。XML是一個很是好的選擇,因爲它不會同特定的代碼相連。假設你想把某個方法聲明爲服務,那麼使用Annotation會更好一些,因爲這樣的狀況下需要註解和方法緊密耦合起來,開發者也必須認識到這點。
  還有一個很是重要的因素是Annotation定義了一種標準的描寫敘述元數據的方式。框架

在這以前,開發者一般使用他們本身的方式定義元數據。ide

好比。使用標記interfaces,凝視。transient關鍵字等等。每個程序猿依照本身的方式定義元數據。而不像Annotation這樣的標準的方式。
  眼下。不少框架將XML和Annotation兩種方式結合使用,平衡二者之間的利弊。函數

JDK內建Annotation

註解 說明
@Override 當咱們想要複寫父類中的方法時,咱們需要使用該註解去告知編譯器咱們想要複寫這種方法。這樣一來當父類中的方法移除或者發生更改時編譯器將提示錯誤信息。

@Deprecated 當咱們但願編譯器知道某一方法不建議使用時。咱們應該使用這個註解。

Java在javadoc 中推薦使用該註解,咱們應該提供爲何該方法不推薦使用以及替代的方法。

@SuppressWarnings 這個僅僅是告訴編譯器忽略特定的警告信息,好比在泛型中使用原生數據類型。

它的保留策略是SOURCE(譯者注:在源文件裏有效)並且被編譯器丟棄。

@SafeVarargs 修飾」堆污染」警告
@FunctionalInterface Java8特有的函數式接口

注意:
1. value特權:假設使用註解時僅僅需要爲value成員變量指定值, 則使用註解時可以直接在該註解的括號裏指定value值, 而無需使用name=value的形式. 如@SuppressWarnings(「unchecked」)
2. 請堅持使用@Override註解: 假設在每個方法中使用Override註解來聲明要覆蓋父類聲明, 編譯器就可以替你防止大量的錯誤.

JDK元Annotation

  元Annotation用於修飾其它的Annotation定義.

元註解 釋義
@Retention 指明瞭該Annotation被保留的時間長短。取值(RetentionPoicy)有:1. SOURCE:在源文件裏有效(即源文件保留);2. CLASS:在class文件裏有效(即class保留);3. RUNTIME:在執行時有效(即執行時保留)
@Target 指明該類型的註解可以註解的程序元素的範圍。

假設Target元註解沒有出現,那麼定義的註解可以應用於程序的不論什麼元素。取值(ElementType)有:1. CONSTRUCTOR:用於描寫敘述構造器;2. FIELD:用於描寫敘述域; 3. LOCAL_VARIABLE:用於描寫敘述局部變量; 4. METHOD:用於描寫敘述方法; 5. PACKAGE:用於描寫敘述包; 6. PARAMETER:用於描寫敘述參數; 7. TYPE:用於描寫敘述類、接口(包括註解類型) 或enum聲明

@Documented 指明擁有這個註解的元素可以被javadoc此類的工具文檔化。

這樣的類型應該用於註解那些影響客戶使用帶凝視的元素聲明的類型。

假設一種聲明使用Documented進行註解,這樣的類型的註解被做爲被標註的程序成員的公共API。

@Inherited 指明該註解類型被本身主動繼承。假設用戶在當前類中查詢這個元註解類型並且當前類的聲明中不包括這個元註解類型。那麼也將本身主動查詢當前類的父類是否存在Inherited元註解,這個動做將被反覆執行知道這個標註類型被找到,或者是查詢到頂層的父類。

Annotation演示樣例

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{

}
@Testable
class SupperClass
{

}

class SubClass extends SupperClass
{
    public SubClass()
    {
        for(Annotation annotation:SubClass.class.getAnnotations())
        {
            System.out.println(annotation);
        }

        for(Annotation annotation:SubClass.class.getDeclaredAnnotations())
        {
            System.out.println("getDeclaredAnnotations:"+annotation);
        }
    }
}
package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

public class Client {
    @Test
    public void Client()
    {
        new SubClass();
    }
}

執行結果:@annotation.Testable()

本身定義註解

  使用@interface本身定義註解時,本身主動繼承了java.lang.annotation.Annotation接口,由編譯程序本身主動完畢其它細節。在定義註解時,不能繼承其它的註解或接口。@interface用來聲明一個註解,當中的每個方法其實是聲明瞭一個配置參數。方法的名稱就是參數的名稱。返回值類型就是參數的類型(返回值類型僅僅能是基本類型、Class、String、enum)。可以經過default來聲明參數的默認值。
  定義註解格式:
    public @interface 註解名 {定義體}
  註解參數的可支持數據類型:
1. 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2. String類型
3. Class類型
4. enum類型
5. Annotation類型
6. 以上所有類型的數組

  Annotation類型裏面的參數該怎麼設定:
1. 僅僅能用public或默認(default)這兩個訪問權修飾.好比,String value();這裏把方法設爲defaul默認類型; 
2. 參數成員僅僅能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.好比,String value();這裏的參數成員就爲String;  
3. 假設僅僅有一個參數成員,最好把參數名稱設爲」value」,後加小括號。

  依據Annotation是否包括成員變量,可以把Annotation分爲兩類:
1. 標記Annotation:沒有成員變量的Annotation;這樣的Annotation僅利用自身的存在與否來提供信息;
2. 元數據Annotation:包括成員變量的Annotation;他們可以接受(和提供)不少其它的元數據。

  定義新註解使用@interface關鍵字。其定義過程與定義接口很是類似(見上面的@Testable), 需要注意的是:Annotation的成員變量在Annotation定義中是以無參的方法形式來聲明的, 其方法名和返回值類型定義了該成員變量的名字和類型,並且咱們還可以使用default關鍵字爲這個成員變量設定默認值。

例如如下所看到的。

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Tag
{
    String name() default "zzh";
    String description() default "excellent!";
}

  註解元素必須有肯定的值,要麼在定義註解的默認值中指定。要麼在使用註解時指定。非基本類型的註解元素的值不可爲null。

所以,使用空字符串或0做爲默認值是一種常用的作法。

這個約束使得處理器很是難表現一個元素的存在或缺失的狀態,因爲每個註解的聲明中。所有元素都存在,並且都具備對應的值。爲了繞開這個約束。咱們僅僅能定義一些特殊的值,好比空字符串或者負數,一次表示某個元素不存在,在定義註解時。這已經成爲一個習慣使用方法。


  本身定義的Annotation繼承了Annotation這個接口,所以本身定義註解中包括了Annotation接口中因此的方法;

package java.lang.annotation;

public interface Annotation { 
    boolean equals(Object obj); 
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

提取Annotation信息

  使用Annotation修飾了類/方法/成員變量等以後,這些Annotation不會本身生效,必須由這些註解的開發者提供對應的工具來提取並處理Annotation信息(固然,僅僅有當定義Annotation時使用了@Retention(RetentionPolicy.RUNTIME)修飾,JVM纔會在裝載class文件時提取保存在class文件裏的Annotation,該Annotation纔會在執行時可見,這樣咱們才幹夠解析).
  Java使用Annotation接口來表明程序元素前面的註解, 用AnnotatedElement接口表明程序中可以接受註解的程序元素.像Class Constructor FieldMethod Package這些類都實現了AnnotatedElement接口.
  比方Class的定義:public final class Class extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement。
  AnnotatedElement接口的API例如如下:

修飾符與類型 方法與描寫敘述
T ==getAnnotation==(類 annotationClass) Returns this element’s annotation for the specified type if such an annotation is present, else null.
Annotation[] ==getAnnotations()== Returns all annotations present on this element.
Annotation[] ==getDeclaredAnnotations()== Returns all annotations that are directly present on this element.
boolean ==isAnnotationPresent==(類 annotationClass) Returns true if an annotation for the specified type is present on this element, else false.
package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

public class Client {
    @Test
    public void client() throws NoSuchMethodException, SecurityException
    {
        Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
        for(Annotation annotation : annotations)
        {
            System.out.println(annotation.annotationType().getName());
        }
    }
}

執行結果:org.junit.Test
  假設需要獲取某個註解中的元數據。則需要強轉成所需要的註解類型,而後經過註解對象的抽象方法來訪問這些數據。

package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

@Tag(name="hiddenzzh")
public class Client {
    @Test
    public void client() throws NoSuchMethodException, SecurityException
    {
        Annotation[] annotations = this.getClass().getAnnotations();
        for(Annotation annotation : annotations)
        {
            if(annotation instanceof Tag)
            {
                Tag tag = (Tag)annotation;
                System.out.println("name:"+tag.name());
                System.out.println("description:"+tag.description());
            }
        }
    }
}

執行結果:
name:hiddenzzh
description:excellent!

Annotation處理器編寫

  註解對代碼的語意沒有直接影響, 他們僅僅負責提供信息給相關的程序使用. 註解永遠不會改變被註解代碼的含義, 但可以經過工具對被註解的代碼進行特殊處理.

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{
}
package annotation;

import java.io.IOException;

public class TestCase
{
    @Testable
    public void test1()
    {
        System.out.println("test1");
    }

    public void test2() throws IOException {
        System.out.println("test2");
        throw new IOException("我test2出錯啦...");
    }

    @Testable
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我test3出錯啦...");
    }

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

    @Testable
    public void test5() {
        System.out.println("test5");
    }

    @Testable
    public void test6() {
        System.out.println("test6");
    }
}
package annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestableProcessor {
    public static void process(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException
    {
        int passed = 0;
        int failed = 0;
        Object obj = Class.forName(className).newInstance();
        for(Method method:Class.forName(className).getMethods())
        {
            if(method.isAnnotationPresent(Testable.class))
            {
                try
                {
                    method.invoke(obj, null);
                    ++passed;
                }
                catch(IllegalAccessException | InvocationTargetException e)
                {
                    System.out.println("method "+method.getName()+" execute error:< "+e.getCause()+" >");
                    e.printStackTrace(System.out);
                    ++failed;
                }
            }

        }
        System.out.println("共執行 "+(failed+passed)+"個方法。成功:"+passed+"個");
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException
    {
        process("annotation.TestCase");
    }
}

執行結果:

test1
test3
method test3 execute error:< java.lang.RuntimeException: 我test3出錯啦... >
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at annotation.TestableProcessor.process(TestableProcessor.java:19)
    at annotation.TestableProcessor.main(TestableProcessor.java:36)
Caused by: java.lang.RuntimeException: 我test3出錯啦...
    at annotation.TestCase.test3(TestCase.java:21)
    ... 6 more
test5
test6
共執行 4個方法。成功:3

  注意到在TestCase中僅僅有test1,test3,test5以及test6標註了@Testable的註解,經過註解處理器TestableProcessor進行處理,僅僅執行了這個四個標註註解的方法,這個就是經過註解來實現junit功能的一個雛形。

Annotation處理器處理異常

package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest { Class<? extends Exception> value(); }
package annotation;

public class Sample {
    @ExceptionTest(ArithmeticException.class)
    public static void m1()
    {
        int i=0;
        i=i/i;
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2()
    {
        int [] a = new int[0];
        int i=a[1];
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3(){}
}
package annotation; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ExceptionProcess { public static void main(String[] args) throws ClassNotFoundException { int tests = 0; int passed = 0; Class testClass = Class.forName("annotation.Sample"); for(Method m:testClass.getDeclaredMethods()) { if(m.isAnnotationPresent(ExceptionTest.class)) { tests++; try{ m.invoke(null); System.out.printf("Test %s failed: no exception%n",m); } catch(InvocationTargetException wrappedEx) { Throwable exc = wrappedEx.getCause(); Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value(); if(excType.isInstance(exc)) { passed++; } else { System.out.printf("Test %s failed: expected %s, got %s%n",m,excType.getName(),exc); } } catch(Exception exc) { System.out.println("INVALID @Test: "+m); } } } } }

執行結果:

Test public static void annotation.Sample.m2() failed: expected java.lang.ArithmeticException, got java.lang.ArrayIndexOutOfBoundsException: 1
Test public static void annotation.Sample.m3() failed: no exception

  這三段程序類似於上面用來處理Testable註解的代碼,但有一處不一樣:這段代碼提取了註解參數的值,並用它檢驗改測試拋出的異常是否爲正確的類型。沒有顯示的轉換,所以沒有出現ClassCastException的危急。編譯過的測試程序確保它的註解參數表示的是有效的異常類型,需要提醒一點:有可能註解參數在編譯時是有效的,但是表示特定異常類型的類文件在執行時卻再也不存在。

在這樣的但願很是少出現的狀況下。測試執行類拋出TypeNotPresentException異常。

相關文章
相關標籤/搜索