注解
- 注解是用來干什么的
- 它有什么作用
- 注解的常見分類
- 內置注解
- @Override
- 注解定義
- @Deprecated
- 注解定義
- @SuppressWarnings
- 注解定義
- 元注解
- @Target
- 注解定義
- ElementType
- @Retention&&@RetentionTarget
- 注解定義
- RetentionPolicy
- @Documented
- 注解定義
- @Inherited
- 注解定義
- 用法
- Repeatable(重復注解)
- 注解定義
- JDK8之前
- JDK8之后
- Native
- 注解與反射接口
- 相關接口
- 自定義注解
- 注解的本質(未完結)
注解是用來干什么的
注解的漢語意思: 用淺近的文字解釋艱深的詞句
注解是JDK1.5版本開始引入的一個特性,用于對代碼進行說明,可以對包,類,接口,字段,方法參數,局部變量進行注解.
它有什么作用
主要作用是下面四個方面
- 生成文檔
通過代碼里標識的元數據生成javaDoc文檔 - 編譯檢查
通過代碼里標識的元數據讓編譯器在編譯期間進行檢查驗證 - 編譯時動態處理
編譯時通過代碼里標識的元數據動態處理,例如動態生成代碼 - 運行時動態處理
運行時通過底阿媽里標識的元數據動態處理,例如使用反射注入實例
注解的常見分類
- Java自帶的標準注解
用于標明重寫某個方法,某個類或方法過時,表明要忽略的警告,用這些注解表明后編譯器就會 進行檢查 - 元注解
用于定義注解的注解 - 自定義注解
根據自己的需求定義注解,并可用元注解對自定義注解進行注解
內置注解
@Override
表示當前的方法定義將覆蓋父類中的方法
注解定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這個注解可以用來修飾方法,并只在編譯期有效,編譯期后class文件就不存在了
@Deprecated
表示代碼被棄用,如果使用了被@Deprecated注解的代碼編譯器會發出警告
注解定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- 它會被文檔化
- 可以保留到運行時
- 可以修飾構造方法,屬性,局部變量,方法,包,參數,類型
@SuppressWarnings
關閉編譯器警告信息
注解定義
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {String[] value();
}
- 可以修飾類型,屬性,方法,參數,構造器,局部變量
- 只能存活在源碼
- 取值為String[]
它可以取的值如下所示:
參數 | 作用 |
---|---|
all | 抑制所有警告 |
deprecation | 抑制啟用注釋的警告 |
finally | 抑制finally模塊沒有返回的警告 |
null | 忽略對null的操作 |
unused | 抑制沒被使用過的代碼的警告 |
等等 |
元注解
@Target
描述注解的使用范圍(即被修飾的注解可以在什么地方使用)
注解定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {/*** Returns an array of the kinds of elements an annotation interface* can be applied to.* @return an array of the kinds of elements an annotation interface* can be applied to*/ElementType[] value();
}
- 可被文檔化
- 可以保留到運行時
- 只能在注解類上使用
- value值在ElementType中
ElementType
public enum ElementType {TYPE, // 類、接口、枚舉類FIELD, // 成員變量(包括:枚舉常量)METHOD, // 成員方法PARAMETER, // 方法參數CONSTRUCTOR, // 構造方法LOCAL_VARIABLE, // 局部變量ANNOTATION_TYPE, // 注解類PACKAGE, // 可用于修飾:包TYPE_PARAMETER, // 類型參數,JDK 1.8 新增TYPE_USE // 使用類型的任何地方,JDK 1.8 新增}
@Retention&&@RetentionTarget
描述注解保留的時間范圍(即被描述的注解在它所修飾的類中可以被保留到何時)
注解定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {RetentionPolicy value();
}
- 可被文檔化
- 可以保留到運行時
- 只能在注解類上使用
- value值在RetentionPolicy 中
RetentionPolicy
public enum RetentionPolicy {SOURCE, // 源文件保留CLASS, // 編譯期保留,默認值RUNTIME // 運行期保留,可通過反射去獲取注解信息
}
我們可以通過執行javap -verbose RetentionTest
獲取到RetentionTest
的class字節碼內容如下.
{public retention.RetentionTest();flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0public void sourcePolicy();flags: ACC_PUBLICCode:stack=0, locals=1, args_size=10: returnLineNumberTable:line 7: 0public void classPolicy();flags: ACC_PUBLICCode:stack=0, locals=1, args_size=10: returnLineNumberTable:line 11: 0RuntimeInvisibleAnnotations:0: #11()public void runtimePolicy();flags: ACC_PUBLICCode:stack=0, locals=1, args_size=10: returnLineNumberTable:line 15: 0RuntimeVisibleAnnotations:0: #14()
}
我們可以得到下面的兩個結論:
- 編譯期沒有記錄下sourcePolicy()方法的注解信息
- 編譯期分別使用了
RuntimeInvisibleAnnotations
,RuntimeVisibleAnnotations
屬性去記錄了classPolicy()
方法和runtimePolicy()
方法的注解信息
@Documented
描述使用javaDoc工具為類生成幫助文檔時是否要保留其注解信息
注解定義
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation {public String value() default "default";
}
- 可被生成文檔
- 使用范圍是類,接口,枚舉類,成員方法
@Inherited
被他修飾的注解具有繼承性.
如果某個類使用了被@Inherited修飾的注解,則其子類將自動具有該注釋
注解定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
- 可被生成文檔
- 可被保留到運行時
- 只能用于注解類
用法
定義@Inherited
注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestInheritedAnnotation {String [] values();int number();
}
使用這個注解
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
}class Student extends Person{@Testpublic void test(){Class clazz = Student.class;Annotation[] annotations = clazz.getAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation.toString());}}
}
輸出內容
xxxxxxx.TestInheritedAnnotation(values=[value], number=10)
即使Student類沒有顯示地被注解@TestInheritedAnnotation
,但是它的父類Person被注解,而且@TestInheritedAnnotation
被@Inherited
注解,因此Student類自動有了該注解.
Repeatable(重復注解)
允許在同一申明類型(類,屬性,方法)多次使用同一注解
注解定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {Class<? extends Annotation> value();
}
- 可被生成文檔
- 可以保留至運行時
- 只能作用域注解類
JDK8之前
JDK8之前有重復使用注解的解決方案,但是可讀性不好
方案: 由另一個注解來存儲重復注解,在使用的時候,用存儲注解Authorities
來擴展重復注解
public @interface Authority {String role();
}public @interface Authorities {Authority[] value();
}public class RepeatAnnotationUseOldVersion {@Authorities({@Authority(role="Admin"),@Authority(role="Manager")})public void doSomeThing(){}
}
JDK8之后
方案: 創建重復注解時,加上@Repeatable
,指向存儲注解Authorities
,在使用的時候,可以直接重復使用Authority注解.
@Repeatable(Authorities.class)
public @interface Authority {String role();
}public @interface Authorities {Authority[] value();
}public class RepeatAnnotationUseNewVersion {@Authority(role="Admin")@Authority(role="Manager")public void doSomeThing(){ }
}
Native
使用@Native注解修飾成員變量,表示這個變量可以被本地代碼引用,常常被代碼生成工具使用,了解即可.
注解與反射接口
我們如果想自定義注解,那么就必須先了解注解與反射接口的這部分內容.
反射包java.lang.reflect
下的AnnotatedElement
接口提供這些方法.
注意:只有注解被定義為RUNTIME
后,該注解才能是運行時可見,當class文件被裝載時,被保存在class文件中的Annotation
才會被虛擬機讀取.
AnnotatedElement
接口是所有程序元素(Class,Method,Constructor)的父接口.
Class類
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement, TypeDescriptor.OfField<Class<?>>, Constable {
xxxx
}
Method類
public final class Method extends Executable {
xxx
}
public abstract sealed class Executable extends AccessibleObject{
xxx
}
public class AccessibleObject implements AnnotatedElement {
xxx
}Constructor類
public final class Constructor<T> extends Executable {
xxx
}
和上面Method的繼承路徑是是一樣的,不贅述了.
相關接口
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
判斷該元素上是否包含指定類型的注解,存在則返回true,否則返回false.
(此方法會忽略注解對應的注解容器)<T extends Annotation> T getAnnotation(Class<T> annotationClass)
返回該程序元素上存在的,制定類型的注解,如果該類型的注解不存在,則返回null<Annotation[] getAnnotations()
返回該程序元素上存在的所有注解,若沒有注解,返回長度為0的數組<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
返回該程序元素上存在的,指定類型的注解數據.若沒有,返回長度為0的數組
該方法調用者可以隨意修改返回數據,不會對其他調用者返回數組產生影響.
(會檢測注解對應的重復注解容器)<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass
返回直接存在此元素上的所有注解.
該方法忽略繼承的注解,如果沒有注解直接存在此元素上,返回null.<T extends Annotation> T[] getDeclaredAnotationsByType(Class<T> annotationClass)
返回直接存在此元素上所有指定類型注解,
該方法忽略繼承的注解Annotion[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注解機器注解對應的重復注解容器.
該方法忽略繼承注解,如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。該方法的調用者可以隨意修改返回的數組,而不會對其他調用者返回的數組產生任何影響。
自定義注解
理解注解與反射接口后,我們通過自定義注解來把知識點融合應用一下
- 定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {public String title() default "";public String description() default "";}
- 使用注解
public class TestMethodAnnotation {@Override@MyMethodAnnotation(title = "toStringMethod", description = "override toString method")public String toString() {return "Override toString method";}@Deprecated@MyMethodAnnotation(title = "old static method", description = "deprecated old static method")public static void oldMethod() {System.out.println("old method, don't use it.");}@SuppressWarnings({"unchecked", "deprecation"})@MyMethodAnnotation(title = "test method", description = "suppress warning static method")public static void genericsTest() throws FileNotFoundException {List l = new ArrayList();l.add("abc");oldMethod();}
}
- 用反射接口獲取注解信息
public static void main(String[] args) {try {// 獲取所有methodsMethod[] methods = TestMethodAnnotation.class.getClassLoader().loadClass(("com.pdai.java.annotation.TestMethodAnnotation")).getMethods();// 遍歷for (Method method : methods) {// 方法上是否有MyMethodAnnotation注解if (method.isAnnotationPresent(MyMethodAnnotation.class)) {try {// 獲取并遍歷方法上的所有注解for (Annotation anno : method.getDeclaredAnnotations()) {System.out.println("Annotation in Method '"+ method + "' : " + anno);}// 獲取MyMethodAnnotation對象信息MyMethodAnnotation methodAnno = method.getAnnotation(MyMethodAnnotation.class);System.out.println(methodAnno.title());} catch (Throwable ex) {ex.printStackTrace();}}}} catch (SecurityException | ClassNotFoundException e) {e.printStackTrace();}
}
- 測試的輸出
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @com.pdai.java.annotation.MyMethodAnnotation(title=old static method, description=deprecated old static method)
old static method
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.genericsTest() throws java.io.FileNotFoundException' : @com.pdai.java.annotation.MyMethodAnnotation(title=test method, description=suppress warning static method)
test method
Annotation in Method 'public java.lang.String com.pdai.java.annotation.TestMethodAnnotation.toString()' : @com.pdai.java.annotation.MyMethodAnnotation(title=toStringMethod, description=override toString method)
toStringMethod
注解的本質(未完結)
這部分要扯到動態代理和注解的處理器,emmm比較麻煩.
后面發完動態處理后返回來填坑