正在學習的小白。不當之處,請多多指教
目錄
- 前言
- 自定義註解的使用
- 元註解
- 註解的解析反射
- 解析註解的作用(引入IOC概念)
前言
java中有四大型別,其中三個是:列舉,介面,類。
今天簡單認識一下第四個型別:註解。
什麼是註解
Annotation 這裡是"註解"的意思。
除此之外,這個單詞患有一個“註釋”的意思。我們都知道,註釋是給程式設計師看的。那麼註解呢?
註解是給程式看的,所以Annotation既有註解也有註釋的意思
複製程式碼
我們很早就見過一些註解:jdk中的@Override,@FunctionalInterface,Junit框架的@Test等。
註解的基本語法
列舉,介面,類,這個三個型別我們都寫過(他們的原始碼 ),基本語法,想必大家都知道。
那麼註解有原始碼嗎?答案是肯定的,jvm沒有那麼厲害,不可能僅憑一個@+一個單詞就知道程式想表達什麼。
我們以@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
意為目標,也就是說明註解的使用範圍
來看一下@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
意為,保留策略,又稱之為生命週期。
我們繼續進入@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概念)
還是和反射一樣的問題?這樣解析註解有什麼用?
這要結合具體場景,有些專案中可能要自己定義註解使用,而最多使用的地方就是框架。
題外話——引入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方法 = 小紅來了
}
}
}
複製程式碼