本文將介紹J2SE 5.0中三個(gè)比較重要的特性: 枚舉類(lèi)型, 注釋類(lèi)型, 范型, 并在此基礎上介紹在如何在Eclipse 3.1開(kāi)發(fā)環(huán)境中開(kāi)發(fā)枚舉類(lèi)型, 注釋類(lèi)型和范型應用。
J2SE 5.0 (Tiger)的發(fā)布是Java語(yǔ)言發(fā)展史上的一個(gè)重要的里程碑, 是迄今為止在 Java 編程方面所取得的最大進(jìn)步。
J2SE 5.0提供了很多令人激動(dòng)的特性。這些特性包括范型(generics)的支持, 枚舉類(lèi)型(enumeration)的支持, 元數據(metadata)的支持, 自動(dòng)拆箱(unboxing)/裝箱(autoboxing), 可變個(gè)數參數(varargs), 靜態(tài)導入(static imports), 以及新的線(xiàn)程架構(Thread framework)。
隨著(zhù)J2SE 5.0的推出, 越來(lái)越多的集成開(kāi)發(fā)環(huán)境(IDE)支持J2SE 5.0的開(kāi)發(fā)。 著(zhù)名的開(kāi)源Java IDE Eclipse從3.1M4開(kāi)始支持J2SE 5.0的開(kāi)發(fā), 目前最新的版本是3.1RC4。
本系列將介紹J2SE 5.0中三個(gè)比較重要的特性: 枚舉類(lèi)型, 注釋類(lèi)型, 范型, 并在此基礎上介紹在如何在Eclipse 3.1開(kāi)發(fā)環(huán)境中開(kāi)發(fā)枚舉類(lèi)型, 注釋類(lèi)型和范型應用。本文將介紹注釋類(lèi)型。
2.1注釋類(lèi)型簡(jiǎn)介
J2SE 5.0提供了很多新的特性。其中的一個(gè)很重要的特性,就是對元數據(Metadata)的支持。在J2SE5.0中,這種元數據叫作注釋(Annotation)。通過(guò)使用注釋, 程序開(kāi)發(fā)人員可以在不改變原有邏輯的情況下,在源文件嵌入一些補充的信息。代碼分析工具,開(kāi)發(fā)工具和部署工具可以通過(guò)這些補充信息進(jìn)行驗證或者進(jìn)行部署。舉個(gè)例子,比如說(shuō)你希望某個(gè)方法的參數或者返回值不為空,雖然我們可以在Java doc中說(shuō)明,但是表達同樣意思的說(shuō)法有很多,比如"The return value should not be null"或者"null is not allowed here"。測試工具很難根據這些語(yǔ)言來(lái)分析出程序員所期望的前提條件(Pre-condition)和執行后的條件(Post-condition)。 而使用注釋(Annotation),這個(gè)問(wèn)題就可以輕而易舉的解決了。
2.2定義注釋
J2SE5.0支持用戶(hù)自己定義注釋。定義注釋很簡(jiǎn)單,注釋是由@Interface關(guān)鍵字來(lái)聲明的。比如下面是一個(gè)最簡(jiǎn)單的注釋?zhuān)ˋnnotation)。
|
除了定義清單1中的注釋以外,我們還可以在注釋?zhuān)ˋnnotation)中加入域定義。方法很簡(jiǎn)單,不需定義Getter和Setter方法,而只需一個(gè)簡(jiǎn)單的方法,比如:
清單2 為注釋加入域 |
定義了這個(gè)注釋之后,我們在程序中引用就可以使用這個(gè)注釋了。
清單3 使用自定義的注釋 |
由于TODO中只定義了一個(gè)域,使用TODO的時(shí)候,可以簡(jiǎn)寫(xiě)為
清單4 單域注釋的簡(jiǎn)寫(xiě) |
類(lèi)似的,你可以在你的注釋?zhuān)ˋnnotation)類(lèi)型中定義多個(gè)域,也可以為每個(gè)域定義缺省值。比如:
清單5定義缺省值 |
如果定義了缺省值,在使用的時(shí)候可以不用再賦值。比如:
清單6使用定義了缺省值的注釋 |
在這個(gè)例子中,testable用缺省值true。
和上文一樣,我們使用Eclipse 3.1作為集成的編譯運行環(huán)境。Eclipse 3.1提供了向導幫助用戶(hù)來(lái)定義注釋。 1.首先我們創(chuàng )建一個(gè)Plug-in 項目,com.catherine.lab.annotation.demo。在Package Explorer中選中包package com.catherine.lab.annotation.demo, 2.點(diǎn)擊New->Other->Java->Annotation,彈出了下面的對話(huà)框。4.輸入注釋的名稱(chēng),在這里例子中輸入TODO, 點(diǎn)擊Finish, 圖2中的注釋就生成了。


2.2.1注釋的類(lèi)型
從上面的例子中,我們可以看出,按照使用者所需要傳入的參數數目, 注釋?zhuān)ˋnnotation)的類(lèi)型可以分為三種。
第一種是標記注釋類(lèi)型:
標記注釋(Marker)是最簡(jiǎn)單的注釋, 不需要定義任何域。下面要介紹的Override和Deprecated都是標記類(lèi)型的。當然,如果一個(gè)注釋類(lèi)型提供了所有域的缺省值,那么這個(gè)注釋類(lèi)型也可以認為是一個(gè)注釋類(lèi)型。使用標記類(lèi)型的語(yǔ)法很簡(jiǎn)單。
清單7 標記注釋的用法 |
第二種是單值注釋類(lèi)型:單值注釋類(lèi)型只有一個(gè)域。語(yǔ)法也很簡(jiǎn)單:
清單8 單值注釋的用法 |
第三種是全值注釋類(lèi)型。 全注釋類(lèi)型其實(shí)并不算是一個(gè)真正的類(lèi)型,只是使用注釋類(lèi)型完整的語(yǔ)法:
清單9 全值注釋的用法 |
2.2.2 J2SE的內建注釋(build-in annotation)
在程序中不僅可以使用自己定義的注釋?zhuān)€可以使用J2SE5.0中內建的注釋類(lèi)型。下面我們就詳細來(lái)介紹J2SE5.0提供的注釋類(lèi)型。J2SE 5.0中預定義了三種注釋注釋類(lèi)型:
Override :java.lang.Override 表示當前的方法重寫(xiě)了父類(lèi)的某個(gè)方法,如果父類(lèi)的對應的方法并不存在,將會(huì )發(fā)生編譯錯誤。
Deprecated:java.lang.Deprecated 表示 并不鼓勵使用當前的方法或者域變量。
SuppressWarnings: java.lang.SuppressWarnings關(guān)閉編譯器告警,這樣,在編譯1.5之前的代碼的時(shí)候,不會(huì )出現大量不關(guān)心的無(wú)關(guān)的告警。
下面舉一個(gè)使用Override的例子。Override這個(gè)注釋類(lèi)型在使用模板方法(Template Method,圖2)非常有用。熟悉設計模式的讀者們一定知道,模板方法中通常定義了抽象類(lèi),并且這個(gè)抽象類(lèi)中定義了主要的控制流。子類(lèi)就是通過(guò)重寫(xiě)父類(lèi)中控制流中所調用的方法來(lái)實(shí)現自己的邏輯。有的時(shí)候,父類(lèi)會(huì )將這些方法定義為抽象方法,但是有的時(shí)候也會(huì )提供缺省實(shí)現。在后者的情況下,子類(lèi)可以不實(shí)現這個(gè)方法。
這樣就帶來(lái)一個(gè)問(wèn)題,如果你希望在子類(lèi)中重寫(xiě)這個(gè)方法,但是無(wú)意中寫(xiě)錯了方法的名字,這個(gè)錯誤是很難被發(fā)現的。因為你希望重寫(xiě)的這個(gè)方法,會(huì )被編譯器當作一個(gè)新的方法而不是重寫(xiě)父類(lèi)的方法。而現在使用@Override,這個(gè)擔心就是不必要的。如果你拼錯了你希望重寫(xiě)的方法,編譯器會(huì )報錯,告訴你父類(lèi)沒(méi)有相應的方法。

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

2.2.3 注釋的注釋
值得注意的是,J2SE5.0還提供了四種用于注釋的注釋類(lèi)型。有以下的四種:
1. Target:用來(lái)指定這個(gè)注釋?zhuān)ˋnnotation)是為哪種類(lèi)型而定義的。比如,這個(gè)類(lèi)型可能只是為method定義的。比如override,不能用@override來(lái)修飾class或者field。
比如清單11中定義了一個(gè)注釋?zhuān)篢ODO,而這個(gè)注釋定義了Target為ElementType.method。因此,TODO只能用來(lái)修飾方法,不能用來(lái)修飾類(lèi)或者類(lèi)變量。圖5中給出了一個(gè)非法使用TODO的例子。在MyCalculator中,定義了一個(gè)布爾型的變量 isReady,如果用TODO來(lái)修飾這個(gè)類(lèi)變量的話(huà),會(huì )出現編譯錯誤。而用TODO來(lái)修飾方法calculateRate(),則不會(huì )出現編譯錯誤。這是因為T(mén)ODO的定義已經(jīng)規定了,只能用來(lái)修飾方法。
清單11 Target的用法 |

2.Retention:Retention的策略可以從以下三種中選?。?/p>
請注意,如果你希望在運行時(shí)查找到這些注釋在什么地方被用到,一定要在定義注釋的時(shí)候,選擇RetentionPolicy.RUNTIME,否則即使你用注釋修飾了類(lèi)變量或者方法,在運行時(shí)也沒(méi)有辦法獲得這個(gè)信息的。
3.Documented:這個(gè)注釋?zhuān)ˋnnotation)將作為public API的一部分。
4.Inherited : 假設注釋?zhuān)ˋnnotation)定義的時(shí)候使用了Inherited,那么如果這個(gè)注釋?zhuān)ˋnnotation)修飾某個(gè)class,這個(gè)類(lèi)的子類(lèi)也被這個(gè)注釋?zhuān)ˋnnotation)所修飾。
2.3注釋的應用
下面各小節顯示了在哪些情況下可以使用注釋以及如何使用注釋?!?/p>
2.3.1動(dòng)態(tài)查找注釋
當我們定義好了注釋以后,我們可以開(kāi)發(fā)一些分析工具來(lái)解釋這些注釋。這里通常要用到Java的反射特性。比如說(shuō)我們希望找到某個(gè)對象/方法/域使用了哪些注釋?zhuān)蛘攉@得某個(gè)特定的注釋?zhuān)蛘吲袛嗍欠袷褂媚硞€(gè)特定的注釋, 我們可以參考下面這個(gè)例子。這個(gè)例子中定義了兩個(gè)注釋?zhuān)篢ODO和TOFORMATE。在MyCalculator類(lèi)中,TODO用來(lái)修飾方法calculateRate,而TOFORMATE用來(lái)修飾類(lèi)變量concurrency和debitDate。而在類(lèi)TestCalculator的main函數中,通過(guò)Java反射特性,我們查找到使用這些注釋的類(lèi)變量和方法。清單12-清單15分別顯示這些類(lèi)的定義。
清單12 TODO注釋的定義 |
|
|
|
下面我們來(lái)運行這個(gè)例子,這個(gè)例子的運行結果如圖10所示。
運行結果和我們先前的定義是一致的。在運行時(shí),我們可以獲得注釋使用的相關(guān)信息。

在我們介紹了什么是注釋以后,你可能會(huì )想知道注釋可以應用到什么地方呢?使用注釋有什么好處呢?在下面的小節中我們將介紹一個(gè)稍復雜的例子。從這個(gè)例子中,你將體會(huì )到注釋所以提供的強大的描述機制(declarative programming)。
2.3.2 使用注釋替代Visitor模式
在J2SE 5.0以前,我們在設計應用的時(shí)候,我們經(jīng)常會(huì )使用Visitor這個(gè)設計模式。Visitor這個(gè)模式一般是用于為我們已經(jīng)設計好了一組類(lèi)添加方法,而不需要擔心改變定義好的類(lèi)。比如說(shuō)我們已經(jīng)定義了好了一組類(lèi)結構,但是我們希望將這些類(lèi)的對象部分數據輸出到某種格式的文件中。
Vistor模式的實(shí)現
使用Vistor模式,首先我們在Employee這個(gè)類(lèi)中加入export方法,export方法如圖11所示。Export方法接受Exporter對象作為參數,并在方法體中調用exporter對象的visit()方法。

在這里我們定義了一個(gè)Exporter抽象類(lèi),我們可以通過(guò)繼承Exporter類(lèi),重寫(xiě)其visit方法來(lái)實(shí)現不同格式的文件輸出。圖11種給出visit方法的實(shí)現是一個(gè)簡(jiǎn)單的例子。如果要實(shí)現輸出成XML格式的,可以定義Exporter子類(lèi):XMLExporter。如果希望輸出成文本的可以定義TXTExporter。但是這樣做不夠靈活的地方在于,如果Employee加入其他的域變量,那么相應的visitor類(lèi)也需要進(jìn)行修改。這就違反了面向對象Open for Extension, close for Modification的原則。
使用注釋替代Vistor模式
使用注釋?zhuān)ˋnnotation),也可以完成數據輸出的功能。首先定義一個(gè)新的注釋類(lèi)型:@Exportable。然后定義一個(gè)抽象的解釋器ExportableGenerator,將Employee 對象傳入解釋器。在解釋器中,查找哪些域使用了Exportable這個(gè)注釋?zhuān)ˋnnotation),將這些域(Field)按照一定格式輸出。圖12給出了Exportable注釋的定義。
清單16注釋Exportable的定義 |
清單17-清單20中給出了包含數據的這些類(lèi)的定義以及這些類(lèi)是如何使用注釋Exportable的。 圖18定義了Main函數,使用ExporterGenerator來(lái)產(chǎn)生輸出文件。清單21給出了使用注釋來(lái)實(shí)現這一功能的兩個(gè)類(lèi):ExporterGenerator和TXTExporterGenerator。其中ExporterGenerator定義了一個(gè)基本的框架。而TXTExporterGenerator繼承了ExporterGenerator,并且重寫(xiě)了outputField方法,在這個(gè)方法中實(shí)現了特定格式的輸出。用戶(hù)可以繼承這個(gè)ExporterGenerator,并且實(shí)現其中的抽象方法來(lái)定義自己期望的格式。
清單17 Employee的類(lèi)定義 |
|
|
|
|
|
|
|
在這個(gè)例子中,我們將一個(gè)Employee對象的部分內容輸出到文件C:\test.output中。圖19顯示了這個(gè)例子的輸出結果。

通過(guò)這種方法,我們可以動(dòng)態(tài)生成Employee對象的域輸出,而不需要在程序中寫(xiě)明要輸出哪些確定的域。如果需要更為豐富的格式,我們可以定義多個(gè)注釋類(lèi)型。通過(guò)對不同注釋以及屬性的解析,實(shí)現格式化的文件輸出。
2.4注釋類(lèi)型的小結
所謂元數據,指的是關(guān)于信息的信息。一般而言,代碼分析工具,測試工具或者部署工具會(huì )使用元數據來(lái)產(chǎn)生配置信息以及使用配置信息產(chǎn)生控制邏輯。這些工具通常使用Java的反射特性,重構元數據的信息,并對這些信息進(jìn)行解釋。
新的技術(shù)會(huì )不斷改變程序設計和開(kāi)發(fā)人員的設計思想。那么注釋?zhuān)ˋnnotation)給我們帶來(lái)了什么呢? 僅僅在代碼分析,或者是開(kāi)發(fā)測試框架和部署框架的時(shí)候才有用么? 我認為并不是這樣。從上面的例子可以看出,注釋?zhuān)ˋnnotation)的應用范圍其實(shí)是很廣泛的。在我們的應用中充分的利用元數據,可以提高的軟件的質(zhì)量和可維護性。
聯(lián)系客服