天天看點

注解(一)- 基礎知識與運作時注解

在java和android 中,注解的運用非常廣泛,很多的類庫,第三方架構中都用到了注解。是以我們有必要來熟悉注解的相關知識。

Annotation,注解(也稱為中繼資料),可以為我們在代碼中添加額外的資訊,我們也可以很友善的使用這些資料

當然,在代碼中添加額外資訊我們最經常使用的是注釋(comment),好的注釋對于了解代碼或邏輯是非常重要的,comment通俗易懂,并且使用一些工具,注釋也可以生成專門的文檔,但是注解相比注釋,擁有更加強大的,不可替代的功能。它可以提供編譯期的一些操作,比如類型檢查,生成新的檔案(包括java檔案等)。

annotation是java5才引入的新特性,它通過将資訊和源代碼結合在一起,可以提供一些java語言本身無法的表達的額外資訊。它的相關内容可以由編譯器來測試或者驗證,當然,運作時期也可以使用注解來提供額外資訊。

注解的文法其實比較簡單,除了多個

@

符号,其他和java本身文法一樣。java 5 在java.lang中内置了三種标準注解。(android中内置的注解更多)。

  • @Override: 表示目前方法其實是覆寫父類的方法,如果有拼寫錯誤等,編譯器(和IDE)就可以發出錯誤提示。
  • @Deprecated:表示該類或方法不建議使用了,未來有可能被廢棄或者被移除,如果程式員使用了該類或方法,那麼編譯器(和IDE)将會發出警告資訊。 這裡也就就是warning而已,你也可以繼續使用,不過建議還是不要使用被Deprecated的API,說不定未來哪個版本就被移除了。
  • @SuppressWarnings:給編譯器一條指令,告訴它對範圍内的某些類型的警告保持靜默。這樣編譯時就不再輸出該警告了。

看一個簡單的demo:

注解(一)- 基礎知識與運作時注解

從截圖看到看到,eclipse中對不同類型的annotation,都做了相應的提示。

并且也可以看到同一個元素上也可以使用多個不同的注解。而SuppressWarnings注解接收的其實是一個數組,例如demo當中的 unchecked,表示是未檢查的轉換時的警告,而fallthrough則表示在switch塊中,某個case沒有使用break,而直接流向了下一條case時的警告。從編碼習慣上來講,你屏蔽了fallthrough的警告,也可以告别其他人,case沒有使用break,這是因為代碼的邏輯,而不是你忘記寫了。

自定義注解

從以上demo就可以看出,注解很有用,也友善,但是内置的注解不可能滿足我們五花八門的需求,是以此時就需要我們來自定義注解了。

java中内置了四種元注解(meta-annotation),元注解就是負責注解其他注解(這句話好繞啊),來幫助我們自定義注解的。

  • @Target : 表示該注解可以用在什麼地方(使用範圍),可能的ElementType參數包括:
  • CONSTRUCTOR:構造器的聲明。
  • FIELD:域聲明(包含enum的執行個體)。
  • LOCAL_VARIABLE : 局部變量聲明。
  • METHOD : 方法聲明。
  • PACKAGE: 包聲明。
  • PARAMETER: 參數聲明。
  • TYPE: 類,接口(包括注解類型)或 enum類型。
  • @Retention: 表示需要在什麼級别儲存該注解資訊(生命周期),可選的RetentionPolicy參數包括:
  • SOURCE:源碼級别,注解将被編譯器丢棄。
  • CLASS: 在編譯器生成的class檔案中可用,但是會被VM丢棄。(預設的就是該級别)。
  • RUNTIME: 運作期保留該注解,是以此時可以通過反射機制來讀取注解的資訊。
  • @Documented:将注解包含在javadoc中。
  • @Inherited:允許子類繼承父類的注解。

具體的關于如何定義自己的注解,幾行簡單的代碼勝過千言萬語。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public  int id();
	public String description() default "no description";
}
           
public class PasswordUtils {
	@UseCase(id = 47, description = "password must contain one number")
	public boolean validatePassword(String password){
		return true;
	}
	@UseCase(id = 48)
	public String encryptPassword(String password){
		return "encryptPassword()";
	}
	@UseCase(id = 49, description = "new password can't equal previously used ones")
	public boolean checkouForNewPassword(){
		return false;
	}
}
           

通過代碼可以看出,定義一個注解和定義一個interface非常相似,隻不過多了一個

@

符号罷了。在定義注解時,使用了一些元注解,比如

@Target

或者

@Retention

,它們分别用來表明注解的應用範圍,生命周期情況。

而從文法的角度來看,注解的使用方法,也和public,static或void等修飾符一樣,沒什麼大的差别。

而定義注解時,可以看到定義體裡面包含了一些比較特殊的方法。我們也稱之為配置參數,這些方法隻能是public或者default通路權限,方法的傳回值就是配置參數的類型,并且我們可以為其指定預設值,我們在分析處理注解時,程式或工具就可以利用這些值。

@UseCase由UseCase.java定義,并且其中包含了兩個配置參數id和description,并且它們都是有類型的,配置參數可以使用的類型如下:

  • 所有的基本類型。(int,float,boolean等)。
  • String
  • Class
  • enum
  • Annotation
  • 以上類型的數組

關于注解元素的問題,我們還需要注意如下幾個方面。

  1. 配置參數本身還可以是一個注解,這就說明注解可以嵌套。
  2. 配置參數不能有不确定的值,也就是說,配置參數要麼(在定義時)有預設值,要麼(在使用時)提供相應的值。并且這個值還不能是null。是以這個是比較尴尬的地方,是以比如String類型的元素,我們不能指派為null,那麼習慣用法就是使用 "";
  3. 我們在使用注解時,采用的是 鍵值對這種文法,(id = 48),有一個快捷方式,就是注解中如果定義了名為value的配置參數,那麼在使用時,如果該參數是唯一需要指派的一個參數,那麼可以不使用鍵值對,直接在括号内給出value的值就可以了。(value可以是任何合法類型的參數)。
  4. 注解可以嵌套,但是不可以繼承。而實際上,當你使用

    @

    來定義注解時,預設繼承的是java.lang.annotation.Annotation。

而對于上面我們那個 UseCase的demo,我們寫一個程式來進行處理。

public class UseCaseTracker {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Integer> useCases = new ArrayList<Integer>();
		Collections.addAll(useCases, 47, 48, 49, 50);
		trackUseCases(useCases, PasswordUtils.class);
	}
	public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
		for (Method m : cl.getDeclaredMethods()){
			// getDeclaredAnnotation  傳回指定類型的 注解對象 
			UseCase uc = m.getDeclaredAnnotation(UseCase.class);
			if (uc != null){
				System.out.println("Found UseCase :" + uc.id() + "\t" + uc.description());
				useCases.remove(new Integer(uc.id()));
			}
		}
		
		System.out.println("--------------------------------------");
		for (int i : useCases){
			System.out.println("Warning : Miss use case --" + i);
		}
	} 

}
           

輸出結果為:

Found UseCase :47	password must contain one number
Found UseCase :48	no description
Found UseCase :49	new password can't equal previously used ones
--------------------------------------
Warning : Miss use case --50
           

通過上面代碼我們可以看出,利用反射,我們可以很好的處理注解,這其實就像處理普通類那樣。

注解的基本知識本身不複雜,那麼下面我們看一個複雜點的例子。在android常用的xutils3架構中,包含了一個資料庫子產品,這個資料庫在使用時,就是通過注解來創造表名或列名的,那麼下面,我們也自定義一個注解,通過注解來生成javaBean對象的建立資料庫表的語句。

//告訴注解處理器,需要生成一個資料庫表
//這個注解隻能用于類,接口,enum
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
	public String name() default "";
}
           
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
	//這些元素都有預設值,這樣我們就不必強迫程式員必須指派了
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}
           
/**
 * @author www.yaoxiaowen.com
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
	//定義了元素名為value,在符合條件時,我們使用時,可以直接在括号内輸入value的值就ok了。
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}
           
/**
 *  SQLInteger 和 SQLString一樣,都是要求在javabean上,根據不同的資料類型使用不同的注解
 *  @author www.yaoxiaowen.com
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
	String name() default "";
	//注解嵌套
	Constraints constraints() default @Constraints;
}
           
/**
 * 我們的目的就是為該javabean生成一個建立表的語句
 * @author www.yaoxiaowem.com
 */
@DBTable(name = "MEMBER")
public class Member {
	@SQLString(30)
	String firstName;
	
	@SQLString(50)
	String lastName;
	
	@SQLInteger
	Integer age;
	
	@SQLString(value = 30,
			constraints = @Constraints(primaryKey = true))
	String handle;
	
	static int memberCount;
	
}
           
public class TableCreator {
	public static void main(String[] args) throws Exception{
		
			StringBuilder createCommand = new StringBuilder();
			
			String className = "test.annotation.database.Member";
			Class<?> cl = Class.forName(className);
			
			DBTable dbTable = cl.getAnnotation(DBTable.class);
			
			String tableName = dbTable.name();
			//如果名字為空,就使用類名
			if (tableName.length() < 1){
				tableName = cl.getName().toUpperCase();
			}
			
			createCommand.append(
					"CREATE TABLE " + tableName + "(");
			
			List<String> columnDefs = new ArrayList<String>();
			for (Field field : cl.getDeclaredFields()){
				String columnName = null;
				Annotation[] anns = field.getDeclaredAnnotations();
				if (anns.length < 1){
					continue;
				}
				//這裡的寫法之是以簡單,因為我們每個Field上面最多隻有一個 注解
				if (anns[0] instanceof SQLInteger){
					SQLInteger sInt = (SQLInteger)anns[0];
					//沒有名字的話,我們就使用Field的名字來做為 列名
					if (sInt.name().length() < 1){
						columnName = field.getName();
					}else {
						columnName = sInt.name();
					}
					
					columnDefs.add(columnName + " INT " + getConstraints(sInt.constraints()));
				}
				
				if (anns[0] instanceof SQLString){
					SQLString sString = (SQLString)anns[0];
					if (sString.name().length() < 1){
						columnName = field.getName();
					}else {
						columnName = sString.name();
					}
					
					columnDefs.add(columnName + " VARCHAR(" + sString.value() 
								+ ")" + getConstraints(sString.constraints()));
				}
				
				
				
			}
			for (String columnDef : columnDefs){
				createCommand.append("\n\t" + columnDef + ",");
			}
			
			//移除最後的一個逗号
			String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";
			
			System.out.println("TABLE Creation SQL for " + className + "  is: \n" + tableCreate);
				
	}

	//解析出 Constraints 注解的内容
	private static String getConstraints(Constraints con){
		String constraints = "";
		if (!con.allowNull()){
			constraints += " Not Null";
		}
		if (con.primaryKey()){
			constraints += " PRIMARY KEY ";
		}
		
		if (con.unique()){
			constraints += " NNIQUE ";
		}
		return constraints;
	}
}
           
TABLE Creation SQL for test.annotation.database.Member  is: 
CREATE TABLE MEMBER(
	firstName VARCHAR(30),
	lastName VARCHAR(50),
	age INT ,
	handle VARCHAR(30) PRIMARY KEY );
           

這個demo雖然沒有實際的意義,但是仔細分析該demo對于我們了解注解還是比較有幫助的。

在java 5當中引入 annotation時,java也引入了注解處理工具Annotation Processing Tool (apt),apt是一個可以在編譯時使用的指令行工具,但是它是Oracle提供的私有實作,是以該工具在java 8中被移除了,而在java6中,通過 JSR 269 annotation processing facility來規範了自定義注解處理器的這一功能。也有了新的API(javax.annotation.processing),而關于這些内容,我們下一個篇文章再進行介紹。

作者:

www.yaoxiaowen.com

github:

https://github.com/yaowen369

歡迎對于本人的部落格内容批評指點,如果問題,可評論或郵件([email protected])聯系

<p >
		 歡迎轉載,轉載請注明出處.謝謝
</p>


<script type="text/javascript">
 function    Curgo()   
 {   
     window.open(window.location.href);
 }   
</script>