在咱們編寫代碼時,必定看到過這樣的代碼:java
class Student { private String name; @Override public String toString(String str) {//編譯錯誤! return "Student name = " + name; } }
其中的@Override,就是一個「註解」,@Override通常出如今重寫equals()或者toString()方法的上邊,意思是告訴編譯器:下邊的代碼是重寫父類方法的。這時編譯器會按照「重寫」的語法嚴格檢查下面的方法,若是不符合重寫語法,將會編譯錯誤。數組
"註解"做爲一種「標記」,被寫在源碼中,不會改變程序的執行流程。它一般由「註解解析工具」來解析,而「註解解析器」能夠隨Java編譯器啓動,也能夠獨立啓動,來解析註解,並以此能夠作一些事情。ide
註解只在源碼中,編譯成class文件後就不存在了。工具
註解在源碼和.class文件中都存在(如:JDK內置系統註解)測試
在運行階段還起做用,甚至會影響運行邏輯的註解(如:JUnit的@Test)this
註解的做用很是普遍,註解能夠被用在類、屬性、構造方法、成員方法、局部變量等位置,用於對這些元素進行說明。由「註解解析工具」解析後,能夠生成文檔、進行代碼分析、編譯檢查等。
本例將會實現一個用做"編譯檢查「的註解,以及一個"註解解析器"。"註解解析器"將會隨着javac編譯器一同啓動來對使用了註解的類進行編譯,並檢查類名、字段名、方法名是否以大寫、小寫字符開頭,若是違反了規則,編譯時將會報錯。編碼
「註解」本質上是一個「類」,咱們能夠根據本身的須要定義本身的註解。
定義註解的語法很簡單:命令行
public @interface CheckWord{ ... }
"註解」編譯後會生成.class文件。但這是一個很是簡單的註解,它能夠被用在任何位置,並且編譯器遇到這種註解也不作任何事情。例如:code
@CheckWord public class Student { @CheckWord public Student() { } @CheckWord private String name; @CheckWord public void study() { } }
下面咱們先使用「元註解」來規定這個註解能夠被用在哪裏。對象
「元註解」也是一種「註解」,它是已經實現好的。必須用在「註解」的定義上,它能夠規定註解能夠用在哪裏,以及能夠存在於源碼中,或者class中,或者運行時。
經常使用的「元註解」有兩個:
1).@Target : 規定註解能夠用在哪裏。經常使用的取值被定義在枚舉java.lang.annotation.ElementType中:
ElementType.TYPE:類和接口上
ElementType.FIELD: 用在成員變量上
ElementType.METHOD: 用在方法上
ElementType.PARAMETER: 用在參數上
ElementType.CONSTRUCTOR: 用在構造方法上
ElementType.LOCAL_VARIABLE: 用在局部變量上
2).@Retention : 規定註解能夠存在於哪裏。經常使用的取值被定義在枚舉java.lang.annotation.RetentionPolicy中:
RetentionPolicy.SOURCE: 規定註解只存在於Java源代碼中, 編譯生成的字節碼文件中就不存在了。
RetentionPolicy.CLASS: 規定註解存在於Java源代碼、 編譯之後的字節碼文件中, 但JVM運行時,不會被加載到內存。
RetentionPolicy.RUNTIME: 規定註解存在於Java源代碼中、 編譯之後的字節碼文件中、 運行時內存中, 程序能夠經過反射獲取該註解。
例如:修改咱們的註解,規定它只能用在"類","字段",「方法」上,而且能夠存在於「源碼中」:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface CheckWord { }
若是再編譯以前的Student類,會發現用在"構造方法"上的@CheckWord會編譯錯誤,由於咱們規定了它只能用在"類","字段","方法"上。
1.「註解」中能夠定義一些屬性,「註解解析器」能夠根據「屬性」的不一樣,分別作不一樣的事情。
例如@Target註解中的ElementType.TYPE就是此註解的一個屬性,它是一個"枚舉"類型。
下面讓咱們來看看怎樣定義屬性,而後再解析這些屬性。
註解中定義屬性的語法:數據類型 屬性名() [deafult 值];
1.其中「數據類型」能夠是:
1).全部基本類型;
2).String;
3).Class;
4).枚舉;
5).註解;
6).以上任一類型的數組
2.屬性名():屬性名能夠自由設定,要遵循Java標識符的命名規則;其中的一對()是必須的。
3.[default 值]:爲此屬性設置的默認值。
2.本例中因爲只檢查大小寫,爲了規範取值,因此定義一個"枚舉"類型的屬性。
1).先定義枚舉:
public enum StartsWith { UPPERCASE, LOWERCASE }
此枚舉定義了兩個值:UPPERCASE表示:大寫;LOWERCASE表示:小寫。
2).修改"CheckWord"註解的代碼:
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface CheckWord { StartsWith value(); }
說明:
a.StartsWith表示"數據類型",是一個"枚舉"類型。
b.value表示"屬性名",在使用此註解時,此屬性的可取值只有StartsWith.UPPERCASE和StartsWith.LOWERCASE兩個。
c.此屬性沒有設置"默認值",在使用此註解時必需要設置此屬性的值。以下面的代碼:
3).修改"Student"類的代碼:
@CheckWord(StartsWith.UPPERCASE) public class Student { @CheckWord(StartsWith.LOWERCASE) private String stuName; @CheckWord(StartsWith.LOWERCASE) public void show() { } }
1."註解解析器"一般是隨着註解一塊兒定義的,用於解析"註解",並作一些事情。本例的"註解解析器"用於與javac編譯器一塊兒啓動,編譯Student類時,檢查各元素的名稱是否按要求以指定的大寫、小寫字母開頭。
2.自定義"註解解析器"須要繼承AbstractProcessor類,並重寫process()方法,完整的"註解解析器"代碼以下:
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; @SupportedAnnotationTypes("CheckWord") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //獲取全部使用了@CheckWord註解的元素 Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class); // 遍歷這些元素 for (Element e : annoEle) { //獲取元素名稱,多是:類名、屬性名、方法名 String name = e.getSimpleName().toString(); //獲取這個名字的第一個字母 char c = name.charAt(0); //獲取這個元素上的@CheckWord註解對象 CheckWord anno = e.getAnnotation(CheckWord.class); //獲取這個註解的value屬性的值,它是一個StartsWith枚舉類型 StartsWith sw = anno.value(); //判斷屬性值是否設置爲:StartsWith.UPPERCASE,但名字的首字母是小寫 if (sw == StartsWith.UPPERCASE && Character.isLowerCase(c)) { //向控制檯打印異常信息 this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該大寫!"); return false; } //判斷屬性值是否設置爲:StartsWith.LOWERCASE,但名字的首字母是大寫 if (sw == StartsWith.LOWERCASE && Character.isUpperCase(c)) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該小寫!"); return false; } } return true; } }
此代碼的細節你們能夠根據註釋一點一點研究。一些類:TypeElement,RoundEnvironment,Element等的一些方法你們能夠在API手冊中查找。
其它說明:
@SupportedAnnotationTypes("CheckWord") : 表示只處理CheckWord註解。
@SupportedSourceVersion(SourceVersion.RELEASE_8) : 表示支持JDK1.8。
1.在編譯前,咱們看一下完整的代碼清單:請確保如下的四個類在同一個目錄下
1).枚舉類:
public enum StartsWith { UPPERCASE, LOWERCASE }
2).自定義註解類:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface CheckWord { StartsWith value(); }
3).使用了CheckWord註解的Student類:
@CheckWord(StartsWith.UPPERCASE) public class Student { @CheckWord(StartsWith.LOWERCASE) private String StuName; @CheckWord(StartsWith.LOWERCASE) public void show() { } }
4).註解解析器類:
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.util.Set; @SupportedAnnotationTypes("CheckWord") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class); for (Element e : annoEle) { String name = e.getSimpleName().toString(); char c = name.charAt(0); CheckWord anno = e.getAnnotation(CheckWord.class); StartsWith sw = anno.value(); if (sw == StartsWith.UPPERCASE) { if (Character.isLowerCase(c)) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該大寫!"); return false; } } if (sw == StartsWith.LOWERCASE) { if (Character.isUpperCase(c)) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該小寫!"); return false; } } } return true; } }
2.啓動命令行,使用javac依次進行編譯:
javac StartsWith.java
javac CheckWord.java
javac MyProcessor.java(若是報錯: 編碼GBK的不可映射字符,是由於代碼中的中文,可使用javac -encoding UTF-8 MyProcessor.java進行編譯)
接下來使用MyProcessor解析器編譯Student:
javac -processor MyProcessor Student.java
執行命令後,會有錯誤提示:
錯誤: 名稱:StuName 首字母應該小寫!
1 個錯誤
源碼級註解的應用很是普遍,例如:進行代碼檢查、生成新類、生成文件。本文實現了基本的代碼檢查,用於檢查類中的元素是否按照要求進行首字母大寫或者小寫。也能夠根據須要,驗證是否所有大寫,或者所有小寫。但願你們經過本案例可以瞭解源碼級註解的編寫及使用。