Java註解入門介紹

前言

註解是java引入的一項非常受歡迎的補充,它提供了一種結構化的,並且具有類型檢查能力的新途徑,從而使得程序員能夠爲代碼加入元數據,而不會導致代碼雜亂且難以閱讀。使用註解能夠幫助我們避免編寫累贅的部署描述文件,以及其他生成的文件。

註解的語法比較簡單,除了@符號的使用之外,它基本與java固有的語法一致。但由於java源碼中提供的內置註解很少,所以大部分同學對註解都不是很瞭解,雖然我們都接觸過,比如java內置的幾種註解:

    @Override,表示當前的方法定義將覆蓋超類中的方法。
    @Deprecated,表示當前方法即將廢棄,不推薦使用。
    @SuppressWarnings,表示忽略編譯器的警告信息。

但這並不能讓我們體會到註解的強大和便利,其實Java還另外提供了四種註解,專門負責新註解的創建。今天我們就來學習下怎麼創建和使用註解。

註解類的定義

首先看看一個註解類的定義:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}

除了@符號以外,註解類的定義很像一個空的接口。定義註解時,會需要一些元註解,如@Target和@Retention,java提供了四種元註解,定義如下:

@Target:表示該註解可以用於什麼地方。
取值(ElementType)包括:
CONSTRUCTOR:用於描述構造器
FIELD:用於描述域
LOCAL_VARIABLE:用於描述局部變量
METHOD:用於描述方法
PACKAGE:用於描述包
PARAMETER:用於描述參數
TYPE:用於描述類、接口(包括註解類型) 或enum聲明

@Retention:表示需要在什麼級別保存該註解信息。
取值(RetentionPolicy)包括:
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在運行時有效(即運行時保留),因此可以通過反射機制讀取註解的信息。

@Documented:表示將此註解包含在javadoc中。

@Inherited:表示允許子類繼承父類中的註解。

可以看出 定義註解格式爲:
  public @interface 註解名 {定義體}

註解類中定義的元素稱爲註解元素,註解元素可用的類型如下:

  所有基本數據類型(int,float,boolean,byte,double,char,long,short)
  String類型
  Class類型
  enum類型
  Annotation類型
  以上所有類型的數組

如果你使用了其它類型,那編譯器就會報錯。注意,也不允許使用任何包裝類型,但由於自動打包的存在,這算不上什麼限制。註解也可以作爲元素的類型,比如我們再定義一個註解類:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;
    //@Constraints後沒有括號表明使用默認值,可以加括號,在裏面定義初始值,比如@Constraints(unique=true)
}

可以看出,所有的註解元素都有一個默認值。編譯器對元素的默認值有些過分挑剔,首先,元素必須具有默認值;其次不能以null作爲默認值。所以我們只能自己定義一些特殊的值,例如空字符串或負數,來表示某個元素不存在。

註解類的使用

註解類定義好了,怎麼使用呢?
爲了體現註解的便利和強大,在這裏我們寫一個demo,使用註解來自動生成一個建數據庫表的SQL命令。
另外我們再提供兩個註解類:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

好,現在我們一共有4個註解類。
@DBTable 代表數據庫表,註解元素name表示表名;
@Constraints 代表對數據表每一列的條件補充,有primaryKey是不是主鍵,默認false,allowNull是否允許爲空,默認true,unique數據是否唯一,默認false;
@SQLString 代表表中的String列,註解元素value表示列長度,name表示列名,constraints表示對列的一些約束條件;
@SQLInteger 代表表中的int列,name表示列名,constraints表示對列的一些約束條件;
下面我們定義一個簡單的Bean類,在其中應用以上4個註解類:

@DBTable(name = "MEMBER")
public class Member {
    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger int age;
    @SQLString(value = 30, constraints = @Constraints(primaryKey = true)) String handle;
    public String getFirstName() {
        return firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public int getAge() {
        return age;
    }
    public String getHandle() {
        return handle;
    }
    public String toString() {
        return handle;
    }
}

註解的元素在使用時表現爲名-值對的形式,並需要置於 @註解類名 聲明之後的括號內。如果沒有使用括號,代表全部使用默認值。

1、類的註解@DBTable給定了值MEMBER,它將會用來作爲表的名字;
2、Bean的屬性firstName和lastName,都被註解爲@SQLString類型,這些註解有兩個有趣的地方:第一,他們都使用了嵌入的@Constraints註解的默認值;第二,它們都使用了快捷方式。何謂快捷方式?如果程序員的註解中定義了名爲value的元素,並且在應用該註解的時候,如果該元素是唯一需要賦值的一個元素,那麼此時無需使用名-值對的這種語法,而只需在括號內給出value元素所需的值即可。這可以應用於任何合法類型的元素。當然了,這也限制了程序員必須將此元素命名爲value。
Bean屬性age全部使用默認值,handle爲主鍵。
3、Bean屬性age全部使用默認值,handle爲主鍵。

實現註解處理器

註解類使用上了,我們還需要一個註解處理器來解析我們定義的Bean,這樣才能將註解轉換成我們需要的操作。
以下定義解析Bean的註解處理器,我們的目的是要輸出一條SQL語句。主要用到了反射技術。

public class TableCreator {
    public static void main(String[] args) throws Exception{
//傳入我們定義好的Bean類名,帶上包名       
Class<?> cl = Class.forName("annotation.Member");
//檢查我們傳入的類是否帶有@DBTable註解
        DBTable dbTable = cl.getAnnotation(DBTable.class);
        List<String> columnDefs = new ArrayList<String>();
//得到類中的所有定義的屬性
        for(Field filed : cl.getDeclaredFields()){
            String columnName = null;
//得到屬性的註解,對一個目標可以使用多個註解
            Annotation[] anns = filed.getAnnotations();
            if(anns.length < 1){
                continue;
            }
//SQLString註解走着
            if(anns[0] instanceof SQLString){
                SQLString sString = (SQLString)anns[0];
//name()使用的是默認值,所以這裏取屬性名
                if(sString.name().length() < 1){
                    columnName = filed.getName().toUpperCase();
                }else{
                    columnName = sString.name();
                }
//構建SQL語句
                columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
            }
//SQLInteger註解走着
            if(anns[0] instanceof SQLInteger){
                SQLInteger sInt = (SQLInteger)anns[0];
                if(sInt.name().length() < 1){
                    columnName = filed.getName().toUpperCase();
                }else{
                    columnName = sInt.name();
                }
                columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
            }
        }
        StringBuilder creator = new StringBuilder("CREATE TABLE " + dbTable.name() + "( ");
        for(String c : columnDefs){
            creator.append("\n" + c + ",");
        }
        creator.deleteCharAt(creator.length()-1);
        creator.append(")");
        System.out.println(creator.toString());
    }

    private static String getConstraints(Constraints con) {
        String constraints = "";
        if(!con.allowNull()){
            constraints += " NOT NULL";
        }
        if(con.primaryKey()){
            constraints += " PRIMARY KEY";
        }
        if(con.unique()){
            constraints += " UNIQUE";
        }
        return constraints;
    }
}

最後的輸出:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30) PRIMARY KEY)

總結

最後,通過這篇文章,我們要學到一下幾點:
1、瞭解java 4種元註解的說明與使用;
2、會自定義註解類;
3、瞭解註解元素的定義和規則;
4、會使用註解類;
5、能寫一個簡單的註解處理器解析註解。

———————————————————-end————————————————————

發佈了32 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章