正在學習的小白。不當之處,請多多指教
java
目錄程序員
java中有四大類型,其中三個是:枚舉,接口,類。編程
今天簡單認識一下第四個類型:註解。數組
什麼是註解bash
Annotation 這裏是"註解"的意思。
除此以外,這個單詞患有一個「註釋」的意思。咱們都知道,註釋是給程序員看的。那麼註解呢?
註解是給程序看的,因此Annotation既有註解也有註釋的意思
複製代碼
咱們很早就見過一些註解:jdk中的@Override,@FunctionalInterface,Junit框架的@Test等。框架
枚舉,接口,類,這個三個類型咱們都寫過(他們的源碼 ),基本語法,想必你們都知道。jvm
那麼註解有源碼嗎?答案是確定的,jvm沒有那麼厲害,不可能僅憑一個@+一個單詞就知道程序想表達什麼。ide
咱們以@FunctionalInterface(關於這個註解不清楚的能夠參考函數式編程的內容)爲例:函數式編程
@FunctionalInterface
interface UsB{
void show();
}
複製代碼
按住Ctrl點擊註解進入,就能夠看到@FunctionalInterface的源碼:函數
package java.lang;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製代碼
先不着急認識這些是什麼意思,看懂其構成就能夠,而後咱們照貓畫虎,本身寫一個:
/** * 自定義註解 */
@interface MyAnnotation{ }
複製代碼
@interface 註解名字{ }
那麼,註解有像其餘普通類同樣的屬性嗎?
String name(); 註解中 這是屬性,不是接口中的方法。
註解屬性賦值語法:String name() default "屬性";
(ps:能夠賦值,但通常再也不內部賦值,給外部使用者賦值)
很差意思,註解是沒有方法的!
註解對屬性類型是有要求的:
8個基本數據類型 / 字符串類型 / Class類型 / 註解類型 / 枚舉類型 及其一維數組
註解中只有一個屬性, 那麼請將該屬性定義爲 value 名稱. 好處: 使用該註解時能夠省略 value=
以上6點就是註解的基本語法。
以前咱們使用註解,都是固定的,好比@Override只能在(重寫)方法使用,@FunctionalInterface只能在(有且僅有一個要實現的方法的)接口使用。乳溝隨便使用,就會馬上編譯報錯。 那麼,使用咱們剛剛自定義的註解,使用上有限制嗎?
@MyAnnotation("省略了value")//能夠用在類上
public class AnnotationDemo {
@MyAnnotation("zhangsan") String name;//能夠用在屬性上
@MyAnnotation("show") //能夠用在方法上
public void show(){}
}
//自定義註解
@interface MyAnnotation{
//屬性
String value();
}
複製代碼
這樣看來,咱們目前自定義的註解是沒有任何使用位置的限制的,再回頭看看,前面@FunctionalInterface註解的源碼,或者@Override的源碼,發現咱們自定義的註解少了幾樣東西。沒錯,少了一些註解。準確說,少了一些 「元註解」。
package java.lang;
import java.lang.annotation.*;
//三個元註解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製代碼
什麼是元註解,「元」意爲:最開始,初始的意思,那麼元註解就是其實的註解,或者叫,註解的註解。是用來修飾說明註解的。
元註解都來自於:java.lang.annotation.* 下
先來認識一下元註解:
意爲目標,也就是說明註解的使用範圍
來看一下@Target的源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
複製代碼
能夠看到,他也有本身的元註解,和一個一維數組屬性ElementType[],進入ElementType[],咱們能夠看到,ElementType是一個枚舉類(控制文本長度,去除了全部源碼的註釋):
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
複製代碼
這些枚舉類型很容易看出,Target註解的value值,能夠是這些枚舉元素,例如:FIELD表示使用在屬性上,METHOD可使用在方法上,等等,不在一一說明。
使用: 、
@Target({ElementType.FIELD,ElementType.METHOD}) 注意,多個值用中括號,屬性名爲value,可省略
能夠加在咱們前面寫的自定義註解上看看效果。
意爲,保留策略,又稱之爲生命週期。
咱們繼續進入@Retention的源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
複製代碼
也只有一個屬性,進入RetentionPolicy查看:
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
複製代碼
RetentionPolicy也是一個枚舉類,只有三個值。
這三個值很好理解,他與java程序的聲明週期是一一對應的:
源碼階段(SOURCE),編譯階段(CLASS),運行階段(RUNTIME)。
@Retention的屬性不是數組,因此只能選擇一個值
如:@Retention(RetentionPolicy.CLASS)
ps : RetentionPolicy.RUNTIME 最經常使用,由於一般和反射結合使用,而反射是在運行時操做類。
(不瞭解反射能夠參考個人另外一篇筆記:Java 反射機制那些事)
咱們經過代碼的方式,簡單說明下,如何利用反射解析註解
先來準備一個 Student類:
public class Student {
//一個屬性
public String name;
public Student() { }
//一個構造
public Student(String name) { this.name = name; }
//一個方法
public void show(String msg){
System.out.println("show方法 = " + msg);
}
//重寫toString
@Override
public String toString() {
return "Student{name= "+name+"}';
}
}
複製代碼
再來寫一個自定義註解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface MyAnnotation{
String value();
}
複製代碼
而後,根據自定義註解的做用範圍,在Student類上加上咱們自定義的註解,而後賦上一些值:
@MyAnnotation("小明")
public Student(String name) {
this.name = name;
}
@MyAnnotation("小紅來了")
public void show(String msg){
System.out.println("show方法 = " + msg);
}
複製代碼
在新建一個類,實現咱們的反射部分代碼,這裏個人類就叫作:AnnotationDemo
補充:
註解 某種意義上講能夠做爲 一種配置文件(常見的配置文件 .properties 或者 .xml)
既然寫了一個文件,就要去對他進行一些 讀寫 操做,若是不去讀取並使用他的內容,呢這個(配置)文件存在在程序中有什麼意義??文件是保存信息數據的,因此文件不讀出來,是沒有意義的。
而讀出配置文件的內容 稱之爲 解析!
註解有個解析技術,叫作: 反射!
寫了註解,就至關於,寫了配置文件不讀取!是沒有意義的。
反射是在運行時,操做Class對象,註解寫在Student類中,因此,反射能夠操做Student的Class對象。
咱們就先AnnotationDemo類中在利用反射獲取一個Student裏面的自定義註解:
public class AnnotationDemo {
public static void main(String[] args) throws NoSuchMethodException {
//1.獲取Student的Class對象
Class<?> clazz = Student.class;
//2.先從構造器開刀,找到構造器
Constructor<?> constructor = clazz.getConstructor(String.class);
/** * 3.使用構造起的方法 isAnnotationPresent(), * 方法意爲:有沒有(參數)註解存在?注意:(一個方法。類等能夠有多個註解) * 參數:註解類型的class對象 * 返回值:存在(true), */
boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
//若是存在,來獲取這個註解
if (annotation) {
/* getAnnotation(註解.class)獲取註解 此時獲取到 Student滿參構造上的註解 */
MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
//註解有個屬性叫value
String value = myAnnotation.value();
System.out.println("value = " + value); //value = 小明
}
}
}
複製代碼
以上代碼,就將 Student滿參構造上的註解的屬性值讀取出來了。
仍是和反射同樣的問題?這樣解析註解有什麼用?
這要結合具體場景,有些項目中可能要本身定義註解使用,而最多使用的地方就是框架。
題外話——引入IOC概念
上面的示例代碼咱們能夠看到,AnnotationDemo類中的一系列代碼,就獲取到了Student的滿參構造的註解的value值。那麼獲取到註解的屬性值,咱們就能夠將值反轉到(傳入)這個構造裏面去。
這個就叫作控制反轉(IOC)。
怎麼傳值呢?繼續看代碼!
//部分代碼和上面同樣,註釋省略
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Student.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
if (annotation) {
MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
String value = myAnnotation.value();
System.out.println("value = " + value); //value = 小明
/* 利用 newInstance 方法,就能夠得到Student實例 */
Object obj = constructor.newInstance(value);
System.out.println("obj = " + obj);
}
}
複製代碼
咱們能夠想一下,假如AnnotationDemo類和自定義註解,不是咱們所寫,是一種別人寫的框架,本身歷來不知道這樣一些代碼,而只是用一個註解,傳了個值,就構造出了一個實例對象。這就是框架技術的一部分底層原理。
接下來解析Student的show方法的註解:
public class AnnotationDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//獲取Class對象
Class<?> clazz = Student.class;
//獲取方法
Method show = clazz.getMethod("show", String.class);
boolean b = show.isAnnotationPresent(MyAnnotation.class);
if (b) {
//每一個反射對象都有這樣一個方法,獲取註解
MyAnnotation annotation = show.getAnnotation(MyAnnotation.class);
String value = annotation.value();
show.invoke(clazz.newInstance(), value);
//運行,查看結果
//show方法 = 小紅來了
}
}
}
複製代碼