淺談java註解<最通俗易懂的講解>
Java註解用於為Java程式碼提供元資料。
元資料是指用來描述資料的資料,通俗一點,就是描述程式碼間關係,或者程式碼與其它資源(例如資料庫表)之間內在聯絡的資料。在一些技術框架中,如Struts、hibernate就不知不覺用到了元資料。對於Struts來說,元資料指的是struts-config.xml;對hibernate來說就是hbm檔案。以上闡述的幾種元資料都是基於xml檔案的或者其他形式的單獨配置檔案。這樣表示有些不便之處。1、與被描述的檔案分離,不利於一致性的維護;2、所有這樣的檔案都是ASCII檔案,沒有顯式的型別支援。基於元資料的廣泛使用,JDK5.0引入了Annotation的概念來描述元資料。在Java中,元資料以標籤的形式存在於Java程式碼中,元資料標籤的存在並不影響程式程式碼的編譯和執行。簡而言之,言而總之,註解就是標籤的意思。
一、如何建立註解?
JDK5.0出來後,Java語言中就有了四種類型,即類class、列舉enum、介面interface、註解@interface,它們處於同一級別,Java就是通過註解來表示元資料的。
package OSChina.ClientNew;
public @interface MyAnnotation {
// 定義公共的final靜態屬性
int age = 25;
// 定義公共的抽象方法
String name();
}
Java註解本質上就是介面,是繼承了Annotation介面的介面。
二、元註解
元註解是可以註解到註解上的註解,或者說元註解是一種基本註解,它能夠應用到其它的註解上面。
元標籤有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。
1、@Retention
Retention,中文釋義保留期的意思
當@Retention應用到註解上的時候,它解釋說明了這個註解的生命週期。
- RetentionPolicy.SOURCE 註解只在原始碼階段保留,在編譯器進行編譯時它將被丟棄忽視。
- RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,它並不會被載入到JVM中。
- RetentionPolicy.RUNTIME 註解可以保留到程式執行的時候,它會被載入到JVM中。
2、@Documented
顧名思義,這個元註解肯定和文件有關。它的作用是能夠將註解中的元素包含到Javadoc中去。
3、@Target
標明註解運用的地方。
- ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
- ElementType.CONSTRUCTOR 可以給構造方法進行註解
- ElementType.FIELD 可以給屬性進行註解
- ElementType.LOCAL_VARIABLE 可以給區域性變數進行註解
- ElementType.METHOD 可以給方法進行註解
- ElementType.PACKAGE 可以給一個包進行註解
- ElementType.PARAMETER 可以給一個方法內的引數進行註解
- ElementType.TYPE 可以給一個型別進行註解,比如類、介面、列舉
4、@Inherited
lnherited是繼承的意思。
如果一個超類被@Inherited註解過的註解進行註解的話,那麼如果它的子類沒有被任何註解應用的話,那麼這個子類就繼承了超類的註解。
程式碼例項
package OSChina.ClientNew;
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;
@Retention(RetentionPolicy.RUNTIME)//註解可以保留到程式執行時,載入到JVM中
@Target(ElementType.TYPE)//給一個型別進行註解,比如類、介面、列舉
@Inherited //子類繼承父類時,註解會起作用
public @interface Desc {
enum Color {
White, Grayish, Yellow
}
// 預設顏色是白色的
Color c() default Color.White;
}
5、@Repeatable
Repeatable 自然是可重複的意思。@Repeatable 是 Java 1.8 才加進來的,所以算是一個新的特性。
什麼樣的註解會多次應用呢?通常是註解的值可以同時取多個。
在生活中一個人往往是具有多種身份,如果我把每種身份當成一種註解該如何使用???
先宣告一個Persons類用來包含所有的身份
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
Person[] value();
}
這裡@Target是宣告Persons註解的作用範圍,引數ElementType.Type代表可以給一個型別進行註解,比如類,介面,列舉。
@Retention是註解的有效時間,RetentionPolicy.RUNTIME是指程式執行的時候。
Person註解:
@Repeatable(Persons.class)
public @interface Person{
String role() default "";
}
@Repeatable括號內的就相當於用來儲存該註解內容的容器。
宣告一個Man類,給該類加上一些身份。
@Person(role="CEO")
@Person(role="husband")
@Person(role="father")
@Person(role="son")
public class Man {
String name="";
}
在主方法中訪問該註解:
public static void main(String[] args) {
Annotation[] annotations = Man.class.getAnnotations();
System.out.println(annotations.length);
Persons p1=(Persons) annotations[0];
for(Person t:p1.value()){
System.out.println(t.role());
}
}
下面的程式碼結果輸出相同,但是可以先判斷是否是相應的註解,比較嚴謹。
if(Man.class.isAnnotationPresent(Persons.class)) {
Persons p2=Man.class.getAnnotation(Persons.class);
for(Person t:p2.value()){
System.out.println(t.role());
}
}
執行結果:
三、註解的屬性
註解的屬性也叫做成員變數,註解只有成員變數,沒有方法。註解的成員變數在註解的定義中以“無參的方法”形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面程式碼中定義了TestAnnotation這個註解中擁有id和msg兩個屬性。在使用的時候,我們應該給他們進行賦值。
賦值的方式是在註解的括號內以value=“”形式,多個屬性之前用,隔開。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
需要注意的是,在註解中定義屬性時它的型別必須是 8 種基本資料型別外加 類、介面、註解及它們的陣列。
註解中屬性可以有預設值,預設值需要用 default 關鍵值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "江疏影";
}
TestAnnotation 中 id 屬性預設值為 -1,msg 屬性預設值為 江疏影。
它可以這樣應用。
@TestAnnotation()
public class Test {}
因為有預設值,所以無需要再在 @TestAnnotation 後面的括號裡面進行賦值了,這一步可以省略。
另外,還有一種情況。如果一個註解內僅僅只有一個名字為 value 的屬性時,應用這個註解時可以直接接屬性值填寫到括號內。
public @interface Check {
String value();
}
上面程式碼中,Check 這個註解只有 value 這個屬性。所以可以這樣應用。
@Check("hi")
int a;
這和下面的效果是一樣的
@Check(value="hi")
int a;
最後,還需要注意的一種情況是一個註解沒有任何屬性。比如
public @interface Perform {}
那麼在應用這個註解的時候,括號都可以省略。
@Perform
public void testMethod(){}
四、Java預置的註解
學習了上面相關的知識,我們已經可以自己定義一個註解了。其實 Java 語言本身已經提供了幾個現成的註解。
1、@Override
這個大家應該很熟悉了,提示子類要複寫父類中被 @Override 修飾的方法
2、@Deprecated
加上這個註解之後,表示此方法或類不再建議使用,呼叫時會出現刪除線,但不代表不能用,只是說,不推薦使用,因為有更好的方法可以呼叫。
那麼直接刪掉不就完了?
因為在一個專案中,工程比較大,程式碼比較多,而在後續的開發過程中,可能之前的某個方法實現的並不是很合理,這個時候要重新寫一個方法,而之前的方法還不能隨便刪,因為別的地方可能在呼叫它,所以加上這個註解,就OK啦!
package OSChina.ClientNew;
import java.util.ArrayList;
import java.util.List;
public class Hero {
@Deprecated
public void say(){
System.out.println("nothing has to say!");
}
public void speak(){
System.out.println("i have a dream!");
}
public void addItems(String item){
List items = new ArrayList();
items.add(item);
System.out.println("i am "+items);
}
}
3、@SuppressWarnings
阻止警告的意思。
該批註的作用是給編譯器一條指令,告訴它對被批註的程式碼元素內部的某些警告保持靜默。
注:這個註解有很多引數,這裡就不多做贅述了,如有需要,請自行百度!
4、@SafeVarargs
引數安全型別註解。
它的目的是提醒開發者不要用引數做一些不安全的操作,它的存在會阻止編譯器產生unchecked這樣的警告。
在宣告具有模糊型別(比如:泛型)的可變引數的建構函式或方法時,Java編譯器會報unchecked警告。鑑於這種情況,如果程式猿斷定宣告的建構函式和方法的主體no problem,可使用@SafeVarargs進行標記,這樣Java編譯器就不會報unchecked警告了!
先看看@SafeVarargs在Java SE中的宣告:
package java.lang;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
由Java原始碼宣告我們瞭解到:@SafeVarargs註解,只能用於標記建構函式和方法,由於保留策略宣告為RUNTIME,所以此註解可以在執行時生效。
@SafeVarargs註解,只能用於static或final的方法。
程式碼例項:
泛型引數的方法,不加註解的情況:
package OSChina.ClientNew; public class SafeVarargsAnnotation<S> { private S[] args; public SafeVarargsAnnotation(S... args){ this.args = args; } public void loopPrintArgs(S... args){ for (S arg : args){ System.out.println(arg); } } public final void printSelfArgs(S... args){ for (S arg : this.args) { System.out.println(arg); } } public static <T> void loopPrintInfo(T... infos){ for(T info:infos){ System.out.println(info); } } public static void main(String[] args) { SafeVarargsAnnotation.loopPrintInfo("A","B","C"); } }
註解的正確使用方式:
package OSChina.ClientNew;
public class SafeVarargsAnnotation<S> {
private S[] args;
//建構函式可以使用@SafeVarargs標記
@SafeVarargs
public SafeVarargsAnnotation(S... args){
this.args = args;
}
//此處不能使用@SafeVarargs,因為此方法未宣告為static或final方法,
// 如果要抑制unchecked警告,可以使用@SuppressWarnings註解
@SuppressWarnings("unchecked")
public void loopPrintArgs(S... args){
for (S arg : args){
System.out.println(arg);
}
}
//final方法可以使用@SafeVarargs標記
@SafeVarargs
public final void printSelfArgs(S... args){
for (S arg : this.args) {
System.out.println(arg);
}
}
//static方法可以使用@SafeVarargs標記
@SafeVarargs
public static <T> void loopPrintInfo(T... infos){
for(T info:infos){
System.out.println(info);
}
}
public static void main(String[] args) {
SafeVarargsAnnotation.loopPrintInfo("A","B","C");
}
}
5、@FunctionalInterface
Java 8為函式式介面引入了一個新註解@FunctionalInterface,主要用於編譯級錯誤檢查,加上該註解,當你寫的介面不符合函式式介面定義的時候,編譯器會報錯。
它們主要用在Lambda表示式和方法引用(實際上也可認為是Lambda表示式)上。
如定義了一個函式式介面如下:
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
那麼就可以使用Lambda表示式來表示該介面的一個實現(注:JAVA 8 之前一般是用匿名類實現的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
五、註解與反射
1、註解通過反射獲取。首先可以通過 Class 物件的 isAnnotationPresent() 方法判斷它是否應用了某個註解。
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
2、或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一種方法返回指定型別的註解,後一種方法返回註解到這個元素上的所有註解。
3、程式碼例項:
① 沒加註解的時候:
package OSChina.ClinetNew1.Annotation;
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if(hasAnnotation){
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
}
}
屁都沒有!
② 加上註解
package OSChina.ClinetNew1.Annotation;
@TestAnnotation
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if(hasAnnotation){
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
}
}
這個正是 TestAnnotation 中 id 和 msg 的預設值。
上面的例子只是檢閱出了註解在類上的註解,其實屬性、方法上的註解也是一樣的。同樣還是要假手與反射。
③ 屬性和方法上的註解:
package OSChina.ClinetNew1.Annotation;
public @interface Check {
String value();
}
package OSChina.ClinetNew1.Annotation;
public @interface Perform {
}
package OSChina.ClinetNew1.Annotation;
import OSChina.ClientNew.Hero;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@TestAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
//獲取類的註解
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
try {
Field a = Test.class.getDeclaredField("a");
a.setAccessible(true);
//獲取一個成員變數上的註解
Check check = a.getAnnotation(Check.class);
if ( check != null ) {
System.out.println("check value:"+check.value());
}
Method testMethod = Test.class.getDeclaredMethod("testMethod");
if ( testMethod != null ) {
// 獲取方法中的註解
Annotation[] ans = testMethod.getAnnotations();
for( int i = 0;i < ans.length;i++) {
System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
需要注意的是,如果一個註解要在執行時被成功提取,那麼 @Retention(RetentionPolicy.RUNTIME) 是必須的。
六、註解的使用場景
1、註解的官方釋義:
註解是一系列元資料,它提供資料用來解釋程式程式碼,但是註解並非是所解釋的程式碼本身的一部分。註解對於程式碼的執行效果沒有直接影響。
2、註解有許多用處:
① 提供資訊給編譯器:編譯器可以利用註解來探測錯誤或警告資訊
② 編譯階段時的處理:軟體工具可以利用註解資訊來生成程式碼、HTML文件或其它響應處理。
③ 執行時的處理:某些註解可以在程式執行時接受程式碼的提取。
值得注意的是,註解不是程式碼本身的一部分。
3、註解運用的地方太多了,比如JUnit測試框架,典型的使用方法:
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
@Test 標記了要進行測試的方法 addition_isCorrect().
還有例如ssm框架等運用了大量的註解。
七、註解的應用例項
package OSChina.ClientNew;
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;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Desc {
enum Color {
White, Grayish, Yellow
}
// 預設顏色是白色的
Color c() default Color.White;
}
該註解Desc前增加了三個註解:Retention表示的是該註解的保留級別,Target表示的是註解可以標註在什麼地方,@Inherited表示該註解會被自動繼承。
package OSChina.ClinetNew1;
@Desc(c = Desc.Color.White)
abstract class Bird {
public abstract Desc.Color getColor();
}
package OSChina.ClinetNew1;
public enum BirdNest {
Sparrow;
// 鳥類繁殖
public Bird reproduce() {
Desc bd = Sparrow.class.getAnnotation(Desc.class);
return bd == null ? new Sparrow() : new Sparrow(bd.c());
}
}
package OSChina.ClinetNew1;
public class Sparrow extends Bird {
private Desc.Color color;
// 預設是淺灰色
public Sparrow() {
color = Desc.Color.Grayish;
}
// 建構函式定義鳥的顏色
public Sparrow(Desc.Color _color) {
color = _color;
}
@Override
public Desc.Color getColor() {
return color;
}
}
上面程式聲明瞭一個Bird抽象類,並且標註了Desc註解,描述鳥類的顏色是白色,然後編寫一個麻雀Sparrow類,它有兩個建構函式,一個是預設的建構函式,也就是我們經常看到的麻雀是淺灰色的,另外一個建構函式是自定義麻雀的顏色,之後又定義了一個鳥巢(工廠方法模式),它是專門負責鳥類繁殖的,它的生產方法reproduce會根據實現類註解資訊生成不同顏色的麻雀。我們編寫一個客戶端呼叫,程式碼如下:
public static void main(String[] args) {
Bird bird = BirdNest.Sparrow.reproduce();
Desc.Color color = bird.getColor();
System.out.println("Bird's color is :" + color);
}
會打印出什麼呢?因為採用了工廠方法模式,它主要的問題是bird比那裡到底採用了哪個建構函式來生成,如果單獨看子類Sparrow,它沒有任何註釋,那工廠方法中bd變數應該就是null了,應該呼叫無參構造!
輸出為什麼會是白色呢?這是我們新增到父類的顏色,why?這是因為我們在註解上加了@Inherited註解,它表示的意思是我們只要把註解@Desc加到父類Bird上,它的所有子類都會從父類繼承@Desc註解。
八、總結
1、註解就是標籤,註解為了解釋程式碼
2、註解的基本語法@interface
3、註解的元註解
4、註解的屬性
5、註解主要給編譯器及工具型別的軟體用的
6、註解的提取要藉助於Java的反射技術,反射比較慢,所以註解使用時也需要謹慎計較時間成本