程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 在Eclipse 3.1中體驗J2SE 5.0的新特性 第二部分 :注釋類型

在Eclipse 3.1中體驗J2SE 5.0的新特性 第二部分 :注釋類型

編輯:關於JAVA

J2SE 5.0 (Tiger)的發布是Java語言發展史上的一個重要的裡程碑, 是迄今為止在 Java 編程方面所取得的最大進步。

J2SE 5.0提供了很多令人激動的特性。這些特性包括范型(generics)的支持, 枚舉類 型(enumeration)的支持, 元數據(metadata)的支持, 自動拆箱(unboxing)/裝箱 (autoboxing), 可變個數參數(varargs), 靜態導入(static imports), 以及新的線程架 構(Thread framework)。

隨著J2SE 5.0的推出, 越來越多的集成開發環境(IDE)支持J2SE 5.0的開發。著名的開 源Java IDE Eclipse從3.1M4開始支持J2SE 5.0的開發, 目前最新的版本是3.1RC4。

本系列將介紹J2SE 5.0中三個比較重要的特性: 枚舉類型, 注釋類型, 范型, 並在此 基礎上介紹在如何在Eclipse 3.1開發環境中開發枚舉類型, 注釋類型和范型應用。本文 將介紹注釋類型。

2. 注釋類型

2.1注釋類型簡介

J2SE 5.0提供了很多新的特性。其中的一個很重要的特性,就是對元數據(Metadata) 的支持。在J2SE5.0中,這種元數據叫作注釋(Annotation)。通過使用注釋, 程序開發人 員可以在不改變原有邏輯的情況下,在源文件嵌入一些補充的信息。代碼分析工具,開發 工具和部署工具可以通過這些補充信息進行驗證或者進行部署。舉個例子,比如說你希望 某個方法的參數或者返回值不為空,雖然我們可以在Java doc中說明,但是表達同樣意思 的說法有很多,比如"The return value should not be null"或者"null is not allowed here"。測試工具很難根據這些語言來分析出程序員所期望的前提條件(Pre- condition)和執行後的條件(Post-condition)。而使用注釋(Annotation),這個問題就可 以輕而易舉的解決了。

2.2定義注釋

J2SE5.0支持用戶自己定義注釋。定義注釋很簡單,注釋是由@Interface關鍵字來聲明 的。比如下面是一個最簡單的注釋(Annotation)。

清單1一個最簡單的注釋

public @interface TODO{}

除了定義清單1中的注釋以外,我們還可以在注釋(Annotation)中加入域定義。方法 很簡單,不需定義Getter和Setter方法,而只需一個簡單的方法,比如:

清單2 為注釋加入域

public @interface TODO{
   String priority();
}

定義了這個注釋之後,我們在程序中引用就可以使用這個注釋了。

清單3 使用自定義的注釋

@TODO(
   priority="high"
)
public void calculate(){
   //body omission
}

由於TODO中只定義了一個域,使用TODO的時候,可以簡寫為

清單4 單域注釋的簡寫

@TODO("high")

類似的,你可以在你的注釋(Annotation)類型中定義多個域,也可以為每個域定義 缺省值。比如:

清單5定義缺省值

public @interface TODO{
   String priority();
   String owner();
   boolean testable() default true;
}

如果定義了缺省值,在使用的時候可以不用再賦值。比如:

清單6使用定義了缺省值的注釋

@TODO(
   priority="high",
   owner="Catherine"
)
public void calculate(){
   //body omission
}

在這個例子中,testable用缺省值true。

和上文一樣,我們使用Eclipse 3.1作為集成的編譯運行環境。Eclipse 3.1提供了向 導幫助用戶來定義注釋。1.首先我們創建一個Plug-in 項目, com.catherine.lab.annotation.demo。在Package Explorer中選中包package com.catherine.lab.annotation.demo, 2.點擊New->Other->Java->Annotation ,彈出了下面的對話框。4.輸入注釋的名稱,在這裡例子中輸入TODO, 點擊Finish, 圖2 中的注釋就生成了。

圖1 創建注釋向導

圖2 注釋向導生成的代碼

2.2.1注釋的類型

從上面的例子中,我們可以看出,按照使用者所需要傳入的參數數目, 注釋 (Annotation)的類型可以分為三種。

第一種是標記注釋類型:

標記注釋(Marker)是最簡單的注釋, 不需要定義任何域。下面要介紹的Override和 Deprecated都是標記類型的。當然,如果一個注釋類型提供了所有域的缺省值,那麼這個 注釋類型也可以認為是一個注釋類型。使用標記類型的語法很簡單。

清單7 標記注釋的用法

@MarkerAnnotation

第二種是單值注釋類型:單值注釋類型只有一個域。語法也很簡單:

清單8 單值注釋的用法

@SingleValueAnnotation("some value")

第三種是全值注釋類型。全注釋類型其實並不算是一個真正的類型,只是使用注釋類 型完整的語法:

清單9 全值注釋的用法

@MultipleValueAnnotation(
   key1=value1,
   key2=value2,
    key3=value3,
)

2.2.2 J2SE的內建注釋(build-in annotation)

在程序中不僅可以使用自己定義的注釋,還可以使用J2SE5.0中內建的注釋類型。下面 我們就詳細來介紹J2SE5.0提供的注釋類型。J2SE 5.0中預定義了三種注釋注釋類型:

Override :java.lang.Override 表示當前的方法重寫了父類的某個方法,如果父類 的對應的方法並不存在,將會發生編譯錯誤。

Deprecated:java.lang.Deprecated 表示 並不鼓勵使用當前的方法或者域變量。

SuppressWarnings: java.lang.SuppressWarnings關閉編譯器告警,這樣,在編譯1.5 之前的代碼的時候,不會出現大量不關心的無關的告警。

下面舉一個使用Override的例子。Override這個注釋類型在使用模板方法(Template Method,圖2)非常有用。熟悉設計模式的讀者們一定知道,模板方法中通常定義了抽象 類,並且這個抽象類中定義了主要的控制流。子類就是通過重寫父類中控制流中所調用的 方法來實現自己的邏輯。有的時候,父類會將這些方法定義為抽象方法,但是有的時候也 會提供缺省實現。在後者的情況下,子類可以不實現這個方法。

這樣就帶來一個問題,如果你希望在子類中重寫這個方法,但是無意中寫錯了方法的 名字,這個錯誤是很難被發現的。因為你希望重寫的這個方法,會被編譯器當作一個新的 方法而不是重寫父類的方法。而現在使用@Override,這個擔心就是不必要的。如果你拼 錯了你希望重寫的方法,編譯器會報錯,告訴你父類沒有相應的方法。

圖2 模板方法的類圖

清單10給出了模板方法的一個例子。這個例子中有定義了兩個類,SubClass和 BaseClass。其中SubClass繼承了BaseClass,並且希望重寫BaseClass的方法doPartII( )。然而SubClass中錯誤的拼寫了這個方法的名稱。圖3顯示了SubClass中的編譯錯誤。 熟悉eclipse的讀者會看到在編輯器裡出現了Error Marker,說明這一行有編譯錯誤。將 鼠標指向這行,顯示了錯誤信息。

清單10 模板方法

public abstract class BaseClass{ //模板方法的基類
public void doWork(){
doPartI(); //先調用doPartI()方法
   doPartII();//之後調用doPartII()方法
}
   public abstract void doPartI();
   public void doPartII(){
}
}
public class SubClass extend BaseClass{
   public void doPartI(){
};
   @Override
   public void doPortII(){//拼寫錯誤,產生編譯錯誤
   System.out.println("override the method of superclass");
}
}

圖3 Override應用的例子

2.2.3 注釋的注釋

值得注意的是,J2SE5.0還提供了四種用於注釋的注釋類型。有以下的四種:

1. Target:用來指定這個注釋(Annotation)是為哪種類型而定義的。比如,這個類 型可能只是為method定義的。比如override,不能用@override來修飾class或者field。

比如清單11中定義了一個注釋:TODO,而這個注釋定義了Target為 ElementType.method。因此,TODO只能用來修飾方法,不能用來修飾類或者類變量。圖5 中給出了一個非法使用TODO的例子。在MyCalculator中,定義了一個布爾型的變量 isReady,如果用TODO來修飾這個類變量的話,會出現編譯錯誤。而用TODO來修飾方法 calculateRate(),則不會出現編譯錯誤。這是因為TODO的定義已經規定了,只能用來修 飾方法。

清單11 Target的用法

@Target({ElementType.METHOD})
public @interface TODO {
   int priority() default 0;
}

圖5 TODO注釋的非法使用

2.Retention:Retention的策略可以從以下三種中選取:

RetentionPolicy.SOURCE:編譯器編譯之後會會從class file中除去注釋 (Annotation)。

Retention.CLASS:注釋(Annotation)保留在class file中,但是VM不會處理。

RetentionPolicy.RUNTIME,:注釋(Annotation)保留在class file,VM會進行處理。

請注意,如果你希望在運行時查找到這些注釋在什麼地方被用到,一定要在定義注釋 的時候,選擇RetentionPolicy.RUNTIME,否則即使你用注釋修飾了類變量或者方法,在運 行時也沒有辦法獲得這個信息的。

3.Documented:這個注釋(Annotation)將作為public API的一部分。

4.Inherited : 假設注釋(Annotation)定義的時候使用了Inherited,那麼如果這個 注釋(Annotation)修飾某個class,這個類的子類也被這個注釋(Annotation)所修飾 。

2.3注釋的應用

下面各小節顯示了在哪些情況下可以使用注釋以及如何使用注釋。

2.3.1動態查找注釋

當我們定義好了注釋以後,我們可以開發一些分析工具來解釋這些注釋。這裡通常要 用到Java的反射特性。比如說我們希望找到某個對象/方法/域使用了哪些注釋,或者獲得 某個特定的注釋,或者判斷是否使用某個特定的注釋, 我們可以參考下面這個例子。這個 例子中定義了兩個注釋:TODO和TOFORMATE。在MyCalculator類中,TODO用來修飾方法 calculateRate,而TOFORMATE用來修飾類變量concurrency和debitDate。而在類 TestCalculator的main函數中,通過Java反射特性,我們查找到使用這些注釋的類變量和 方法。清單12-清單15分別顯示這些類的定義。

清單12 TODO注釋的定義

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TODO {
   int priority() default 0;
}

清單13 TOFORMATE的定義

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TOFORMATE {
}

清單14 使用注釋的類MyCalculator

public class MyCalculator {
   boolean isReady;
     @TOFORMATE double concurrency;
   @TOFORMATE Date debitDate;
   public MyCalculator() {
   super();
   }

   @TODO
   public void calculateRate(){
     System.out.println("Calculating...");
   }
}

清單15動態查找注釋

public class TestCalculator {
   public static void main(String[] args) {
     MyCalculator cal = new MyCalculator();
     cal.calculateRate();
   try {
     Class c = cal.getClass();
     Method[] methods = c.getDeclaredMethods();

       for (Method m: methods) {
     // 判斷這個方法有沒有使用TODO
     if (m.isAnnotationPresent(TODO.class))
     System.out.println("Method "+m.getName()+": the TODO is present");
     }

     Field[] fields = c.getDeclaredFields();
     for (Field f : fields) {
     // 判斷這個域有沒有使用TOFORMATE
       if (f.isAnnotationPresent(TOFORMATE.class))
       System.out.println("Field "+f.getName()+": the TOFORMATE is present");
       }
     } catch (Exception exc) {
       exc.printStackTrace();
     }
   }
}

下面我們來運行這個例子,這個例子的運行結果如圖10所示。

運行結果和我們先前的定義是一致的。在運行時,我們可以獲得注釋使用的相關信息 。

圖6 運行結果

在我們介紹了什麼是注釋以後,你可能會想知道注釋可以應用到什麼地方呢?使用注 釋有什麼好處呢?在下面的小節中我們將介紹一個稍復雜的例子。從這個例子中,你將體 會到注釋所以提供的強大的描述機制(declarative programming)。

2.3.2 使用注釋替代Visitor模式

在J2SE 5.0以前,我們在設計應用的時候,我們經常會使用Visitor這個設計模式。 Visitor這個模式一般是用於為我們已經設計好了一組類添加方法,而不需要擔心改變定 義好的類。比如說我們已經定義了好了一組類結構,但是我們希望將這些類的對象部分數 據輸出到某種格式的文件中。

Vistor模式的實現

使用Vistor模式,首先我們在Employee這個類中加入export方法,export方法如圖11 所示。Export方法接受Exporter對象作為參數,並在方法體中調用exporter對象的visit ()方法。

圖11 使用Vistor模式實現格式輸出

在這裡我們定義了一個Exporter抽象類,我們可以通過繼承Exporter類,重寫其visit 方法來實現不同格式的文件輸出。圖11種給出visit方法的實現是一個簡單的例子。如果 要實現輸出成XML格式的,可以定義Exporter子類:XMLExporter。如果希望輸出成文本的 可以定義TXTExporter。但是這樣做不夠靈活的地方在於,如果Employee加入其他的域變 量,那麼相應的visitor類也需要進行修改。這就違反了面向對象Open for Extension, close for Modification的原則。

使用注釋替代Vistor模式

使用注釋(Annotation),也可以完成數據輸出的功能。首先定義一個新的注釋類型 :@Exportable。然後定義一個抽象的解釋器ExportableGenerator,將Employee 對象傳 入解釋器。在解釋器中,查找哪些域使用了Exportable這個注釋(Annotation),將這些 域(Field)按照一定格式輸出。圖12給出了Exportable注釋的定義。

清單16注釋Exportable的定義

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Exportable {
}

清單17-清單20中給出了包含數據的這些類的定義以及這些類是如何使用注釋 Exportable的。圖18定義了Main函數,使用ExporterGenerator來產生輸出文件。清單21 給出了使用注釋來實現這一功能的兩個類:ExporterGenerator和TXTExporterGenerator 。其中ExporterGenerator定義了一個基本的框架。而TXTExporterGenerator繼承了 ExporterGenerator,並且重寫了outputField方法,在這個方法中實現了特定格式的輸出 。用戶可以繼承這個ExporterGenerator,並且實現其中的抽象方法來定義自己期望的格 式。

清單17 Employee的類定義

public abstract class Employee {
   public abstract String getName();
   public abstract String getEmpNo();
   public Employee() {
     super();
   }
}

清單18 Regular的類定義

public class Regular extends Employee{
   @Exportable String name;
   @Exportable String address;
   @Exportable String title;
   @Exportable String phone;
   @Exportable String location;
   @Exportable Date onboardDate;
   @Exportable ArrayList<Employee> team;
   String empNo;

   public Regular(String name, String address, String title, String phone,
   String location, Date date) {
     super();
     this.name = name;
     this.address = address;
     this.title = title;
     this.phone = phone;
     this.location = location;
     onboardDate = date;
     team = new ArrayList<Employee>();
   }

   public void addMemeber(Employee e){
     team.add(e);
   }
   @Override
   public String getName() {
     // TODO Auto-generated method stub
     return name;
   }
}

清單19 Vendor的類定義

public class Vendor extends Employee {
   @Exportable String name;
   @Exportable String company;
   @Exportable String team;
   @Exportable String workingHours;
   String empNo;

   public Vendor(String name, String company, String team, String hours) {
     super();
     this.name = name;
     this.company = company;
     this.team = team;
     workingHours = hours;
   }
}

清單20 Contractor的類定義

public class Contractor extends Employee{
   @Exportable String name;
   @Exportable String company;
   @Exportable String contractDuration;
   String empNo;
   public Contractor(String name, String company) {
     super();
     // TODO Auto-generated constructor stub
     this.name = name;
     this.company = company;
     contractDuration ="1";
   }
}

清單21 Supplemental的類定義

public class Contractor extends Employee{
   @Exportable String name;
   @Exportable String company;
   @Exportable String contractDuration;
   String empNo;
   public Contractor(String name, String company) {
     super();
     this.name = name;
     this.company = company;
     contractDuration ="1";
   }
}

清單22使用ExportableGenerator的程序

public class TestExportable {
   public TestExportable() {
     super();
   }
   public static void main(String[] args) {
     Regular em=new Regular("Catherine","IBM","Software Engineer","82888288","BJ", new Date());
       Employee vn1=new Vendor("Steve","IBM","PVC","8");
     Employee vn2=new Vendor("Steve","IBM","PVC","8");
     Employee ct=new Contractor("Joe","IBM");
     Employee sup=new Supplemental("Linda","IBM","8");
     em.addMemeber(vn1);
     em.addMemeber(vn2);
     em.addMemeber(ct);
     em.addMemeber(sup);

     PrintWriter ps;
     try {
       ps = new PrintWriter(new FileOutputStream(new File("C:\\test.output"),true));
       ExportableGenerator eg=new TXTExportableGenerator(ps);
       eg.genDoc(em,0);
       eg.flush();
     } catch (FileNotFoundException e) {
       e.printStackTrace();
     }

   }
}

清單23 ExportableGenerator

public abstract class ExportableGenerator {
   PrintWriter out = null;
   public ExportableGenerator(PrintWriter out) {
   super();
   this.out = out;
   }
   public void genDoc(Employee e, int tagNum) {
   Class employee = e.getClass();
     Field[] fields = employee.getDeclaredFields();
     outputFieldHeader(out,e);
     for (Field f : fields) {
       if (f.isAnnotationPresent(Exportable.class)) {
         if (f.getType() != ArrayList.class) {
           for(int i=0; i<tagNum;i++){
           out.print("***");
           }
           outputSimpleField(out, f, e);
         }else{
         try {
         ArrayList team=(ArrayList)f.get(e);
   out.println("-----------------------------");
       for(int i=0;i <team.size();i++){
       Employee member=(Employee)team.get(i);
       genDoc(member,tagNum+1);
       out.println("-----------------------------");
       }
     } catch (IllegalArgumentException e1) {
           e1.printStackTrace();
       } catch (IllegalAccessException e1) {
         e1.printStackTrace();
       }
   }
   }
     }
   outputFieldFooter(out,e);
   }
   public void flush(){
     out.flush();
     out.close();
   }
   protected String value(Field f, Object obj) {
   Class type = f.getType();
try {
   if (type == String.class)
     return (String) f.get(obj);
   if (type == Date.class) {
     return DateFormat.getDateInstance().format((Date)f.get(obj));
}
   } catch (IllegalArgumentException e) {
   e.printStackTrace();
       return f.getName();
     } catch (IllegalAccessException e) {
       e.printStackTrace();
       return f.getName();
     }
     return f.getName();
   }
   protected abstract void outputSimpleField(PrintWriter out, Field f,
       Object obj);
   protected abstract void outputFieldHeader(PrintWriter out,Object e);
   protected abstract void outputFieldFooter(PrintWriter out,Object e);

清單24 TXTExportableGenerator

public class TXTExportableGenerator extends ExportableGenerator {
   public TXTExportableGenerator(PrintWriter out) {
     super(out);
   }
   @Override
   protected void outputSimpleField(PrintWriter out, Field f,Object obj) {
     out.print(f.getName());
     out.print("=");
     out.print(value(f,obj));
     out.print(";");
     out.println();
   }
@Override
   protected void outputFieldHeader(PrintWriter out,Object e) {
   }
   @Override
   protected void outputFieldFooter(PrintWriter out,Object e) {
     //out.println(e.getClass().getName()+":");
}
}

在這個例子中,我們將一個Employee對象的部分內容輸出到文件C:\test.output中。圖 19顯示了這個例子的輸出結果。

圖12 輸出結果

通過這種方法,我們可以動態生成Employee對象的域輸出,而不需要在程序中寫明要 輸出哪些確定的域。如果需要更為豐富的格式,我們可以定義多個注釋類型。通過對不同 注釋以及屬性的解析,實現格式化的文件輸出。

2.4注釋類型的小結

所謂元數據,指的是關於信息的信息。一般而言,代碼分析工具,測試工具或者部署 工具會使用元數據來產生配置信息以及使用配置信息產生控制邏輯。這些工具通常使用 Java的反射特性,重構元數據的信息,並對這些信息進行解釋。

新的技術會不斷改變程序設計和開發人員的設計思想。那麼注釋(Annotation)給我 們帶來了什麼呢? 僅僅在代碼分析,或者是開發測試框架和部署框架的時候才有用麼? 我認為並不是這樣。從上面的例子可以看出,注釋(Annotation)的應用范圍其實是很廣 泛的。在我們的應用中充分的利用元數據,可以提高的軟件的質量和可維護性。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved