1.3.4 可變參數
C和C++是支持函數可變長度參數列表的兩種語言。Java決定引入這方面的優勢。只在必要時才使用可變參數列表。如果濫用它們,就很容易創建出製造混亂的源代碼。C語言在函數聲明中使用省略號(…)來代表“任意數量的參數(0個或者多個)”。Java也使用省略號,但是將它同類型和標識符一起使用。這裏的類型可以是任意內容,如任意類、任意基本類型,甚至是數組類型。然而,當在一個數組中使用它時,省略號必須出現在類型描述之前和方括號之後。由於可變參數的自然屬性,每個方法只能有一個類型作爲可變參數,同時它必須出現在參數列表的最後。
下面這個例子中的方法以任意數量的基本整數作爲參數,並返回它們的總和:
public int sum(int... intList)
{
int i, sum;
sum=0;
for(i=0; i<intList.length; i++) {
sum += intList[i];
}
return(sum);
}
從被標記爲可變的參數位置算起,所有傳入的參數都被組合成一個數組。這使得測試傳入了多少參數變得簡單。需要做的事情就是引用數組的length屬性,同時數組還提供對每個參數的便捷訪問。
以下是一個對任意數量元素的數組中的所有值求和的完整示例程序:
public class VarArgsExample {
int sumArrays(int[]... intArrays}
{
int sum, i, j;
sum=0;
for(i=0; i<intArrays.length; i++) {
for(j=0; j<intArrays[i].length; j++) {
sum += intArrays[i] [j];
}
}
return(sum);
}
public static void main(String args[])
{
VarArgsExample va = new VarArgsExample();
int sum=0;
sum = va.sumArrays(new int[]{1,2,3},
new int[]{4,5,6},
new int[]{10,16});
System.out.println("The sum of the numbers is: " + sum);
}
}
這段代碼跟在已建立的方法之後用來定義和使用一個可變參數。省略號出現在方括號之後(也就是說,在可變參數的類型之後)。在該方法中,參數intArrays只是一個數組的數組。
1.3.5 裝箱/拆箱轉換
以前的Java語言存在着一種冗長乏味的處理方式,就是將基本類型(例如int和char)轉換爲它們的對應引用類型(例如,int對應的Integer,以及char對應的Character)時需要進行手工操作。擺脫這種經常性打包/拆包操作的解決方法就是使用裝箱/拆箱轉換。
1.裝箱轉換
裝箱轉換是一個隱式操作,它將基本類型(例如int)自動地放置到它對應的引用類型(在本例中是Integer)的一個實例中。拆箱則是相反的操作,即將一個引用類型(例如Integer)轉換爲它的基本類型(int)。如果沒有裝箱,就需要按如下方式將int基本類型添加到一個集合(它容納Object類型)中:
Integer intObject;
int intPrimitive;
ArrayList arrayList = new ArrayList();
intPrimitive = 11;
intObject = new Integer(intPrimitive);
arrayList.put (intObject); // cannot add intPrimitive directly
儘管此代碼很簡單,但是完全沒必要這麼冗長。隨着裝箱轉換的引入,以上代碼可以重新編寫如下:
int intPrimitive;
ArrayList arrayList = new ArrayList();
intPrimitive = 11;
// here intPrimitive is automatically wrapped in an Integer
arrayList.put(intPrimitive);
這樣,將不再需要創建一個Integer對象來將一個int放置到集合中。在結果引用類型的value()方法(例如Integer的intValue())等於源基本類型的值時,裝箱轉換將會發生。可以參考下面的表1-1來查看所有有效的裝箱轉換。如果存在任意其他的類型,那麼裝箱轉換將是一種恆等轉換(將類型轉換爲其自身類型)。注意,由於引入了裝箱轉換,一些引用基本類型的被禁轉換也不再被禁止,因爲基本類型現在也可以被轉換成某些引用類型。
表1-1
基本類型 |
引用類型 |
基本類型 |
引用類型 |
boolean |
Boolean |
int |
Integer |
byte |
Byte |
long |
Long |
char |
Character |
float |
Float |
short |
Short |
double |
Double |
2.拆箱轉換
Java也引入了拆箱轉換,它將一個引用類型(例如Integer或者Float)轉換成其自身的基本類型(例如int或者float)。可以參考下面的表1-2來查看所有有效的拆箱轉換。在引用類型的value方法等於結果基本類型的值時,拆箱轉換將會發生。
表1-2
引用類型 |
基本類型 |
引用類型 |
基本類型 |
Boolean |
boolean |
Integer |
int |
Byte |
byte |
Long |
long |
Character |
char |
Float |
float |
Short |
short |
Double |
double |
3.裝箱/拆箱轉換的有效上下文
因爲裝箱和拆箱操作是一種轉換操作,所以它們無需程序員的特定指令即可自動發生(與類型轉換(casting)不一樣,類型轉換是一種顯式操作)。存在幾種可以出現裝箱/拆箱轉換的上下文環境。
(1)賦值
當表達式的值被賦予一個變量時,會發生賦值轉換。當表達式的類型與變量的類型不匹配,並且不會存在數據丟失的危險時,轉換將會自動發生。發生轉換的優先級首先是恆等轉換,接着是擴展基本類型轉換,然後是擴展引用類型轉換,最後纔是新的裝箱(或者拆箱)轉換。如果這些轉換都無效,那麼編譯器將會發出一個錯誤。
(2)方法調用
進行方法調用時,如果參數類型沒有精確匹配那些傳入的參數,那麼就可能發生一些轉換。這些轉換被認爲是方法調用轉換。每個在類型上沒有精確匹配方法簽名中對應參數的參數都將會被轉換。可能發生的轉換依次是恆等轉換、擴展基本類型轉換、擴展引用類型轉換,然後是新的裝箱(或者拆箱)轉換。
在多個方法都匹配特定方法調用時,要選擇最精確的方法。匹配最精確方法的規則外加裝箱轉換隻需稍作修改。如果所有用於解決方法歧義的標準檢查都失敗了,那麼裝箱/拆箱轉換將不會用於解決歧義。這樣,在執行裝箱轉換檢查時,方法調用將會被認爲有歧義並且失敗。
將裝箱和泛型聯合使用,可以編寫如下代碼:
import java.util.*;
public class BoxingGenericsExample {
public static void main(String args[])
{
HashMap<String, Integer> hm = new HashMap<String,Integer>();
hm.put("speed", 20);
}
}
基本類型整數20自動轉換成爲一個Integer類型,然後按照指定關鍵字被放入到HashMap中。
1.3.6 靜態導入
Java語言中引入了導入靜態數據,以簡化靜態屬性和方法的使用。在導入靜態信息後,就可以使用方法/屬性,而不需要限制方法/屬性到所屬類名稱。例如,通過導入Math類的靜態成員,就可以編寫abs或者sqrt,而不用寫成Math.abs和Math.sqrt。
這種機制同時還阻止了一種危險的編碼實踐,即將一組靜態屬性放入一個接口中,然後在每個需要使用這些屬性的類中實現該接口。爲了能夠使用不受限制的屬性,不應該實現下面的接口:
interface ShapeNumbers {
public static int CIRCLE = 0;
public static int SQUARE = 1;
public static int TRIANGLE = 2;
}
實現這個接口會對ShapeNumbers接口產生不必要的依賴性。更糟糕的是,隨着類的進化,特別是在其他類也需要訪問這些常量,並且實現這個接口的情況下,對其進行維護會變得很困難。如果包含這些屬性的接口進行了修改並且只有一些類被重新編譯,那麼已編譯類相互之間很容易遇到同步問題。
爲了更清楚地理解這點,將靜態成員放入到一個類(而不是放入一個接口)中,然後通過一個已修改的導入指令語法導入。ShapeNumbers將修訂如下:
package MyConstants;
class ShapeNumbers {
public static int CIRCLE = 0;
public static int SQUARE = 1;
public static int TRIANGLE = 2;
}
然後,一個客戶端類從ShapeNumbers類中導入靜態信息,接着就能夠使用CIRCLE、SQUARE和TRIANGLE屬性,而不需要爲它們加上ShapeNumbers和成員操作符前綴。
爲了導入類中的靜態成員,請在Java源程序文件的導入部分(頂部)指定如下代碼:
import static MyConstants. ShapeNumbers.*; // imports all static data
這行語法只是根據標準的導入語句格式進行了稍許修改。關鍵字static添加在import關鍵字之後,因爲靜態信息正在從一個特定的類中被導入,所以現在不是導入包,而總是添加類名稱。關鍵字static添加到導入語句的主要原因是爲了清晰地向那些讀源代碼的人顯示靜態信息的導入。
也可通過以下語法單獨導入常量:
import static MyConstants.ShapeNumbers.CIRCLE;
import static MyConstants. ShapeNumbers. SQUARE;
這種語法也是所希望的。關鍵字static被包含進來,因爲這是一個靜態導入,並且要導入的靜態信息片段被各自分開顯式指定。
不能從默認包的一個類中靜態地導入數據。類必須位於一個指定的包中。同時,靜態屬性和方法可能會產生衝突。例如,下面是包含靜態常量的兩個類(分別位於Colors.java和Fruits.java中):
package MyConstants;
public class Colors {
public static int white = 0;
public static int black = 1;
public static int red = 2;
public static int blue = 3;
public static int green = 4;
public static int orange = 5;
public static int grey = 6;
}
package MyConstants;
public class Fruits {
public static int apple = 500;
public static int pear = 501;
public static int orange = 502;
public static int banana = 503;
public static int strawberry = 504;
}
如果編寫一個類,試圖同時對這兩個類進行靜態導入,在使用一個同時在上述兩個類中定義的靜態變量之前,一切進展都很正常:
import static MyConstants. Colors.*;
import static MyConstants. Fruits.*;
public class StaticTest {
public static void main(String args[])
{
System.out.println("orange = " + orange);
System.out.println("color orange = " + Colors.orange);
System.out.println("Fruity orange = " + Fruits.orange);
}
}
上述程序的第七行將導致如下編譯器錯誤。由於標識符orange在Colors和Fruits中都定義了,因此編譯器無法解決這種分歧:
StaticTest.java:7: reference to orange is ambiguous, both variable orange in
MyConstants.Colors and variable orange in MyConstants. Fruits match
System.out.println("orange = " + orange);
在本例中,就應該使用定義靜態數據的類來顯式限制這種衝突名稱。不是直接編寫orange,而是編寫Colors.orange或者Fruits.orange。
1.3.7 枚舉
Java在JDK 5版本中從語言級別上引入了枚舉支持。枚舉是指一個經過排序的、被打包成一個單一實體的項列表。一個枚舉的實例可以使用枚舉項列表中任意單一項的值。可能的最簡單的枚舉是下面所顯示的Colors枚舉:
public enum Colors { red, green, blue }
它們給出了將一個任意項同另一個項相比較的能力,並且可以在一個已定義項列表中進行迭代。枚舉(在Jave中簡稱爲enum)是一個特定類型的類。所有枚舉都是Java中的新類java.lang.Enum的隱式子類。此類不能手工進行子類定義。
在Java中內置枚舉支持有許多好處。枚舉是類型安全的,性能可與使用常量相媲美。枚舉中的常量名稱無需用枚舉名稱進行限定。客戶端不需要建立對枚舉中常量的瞭解,因此可以容易地對枚舉進行修改,而無需修改客戶端。如果常量從枚舉中被刪除了,那麼客戶端將會失敗並且將會收到一個錯誤消息。枚舉中的常量名稱可以被打印,因此除了僅僅得到列表中項的序號外還可以獲取更多信息。這也意味着常量可用作集合的名稱,例如HashMap。
因爲在Java中一個枚舉就是一個類,它也可以有域和方法,並且實現接口。枚舉在switch語句中可以一種直接的方式被使用,並且可以便於程序員相對簡單地去理解和使用。
下面是一個基本的枚舉聲明,以及它在switch語句中的使用。如果希望追蹤某個用戶正在使用什麼操作系統,就可以使用一個操作系統的枚舉,即OperatingSystems enum。注意,因爲一個枚舉實際上是一個類,那麼如果在同一個文件中的其他類是公用的,則枚舉就不能是公用的了。同時也要注意,在switch語句中,常量名稱不能用常量所在枚舉的名稱限制。這些細節將由編譯器基於switch子句中使用的enum類型來自動處理:
import java.util.*;
enum OperatingSystems {
windows, unix, linux, macintosh
}
public class EnumExample1 {
public static void main(String args[])
{
OperatingSystems os;
os = OperatingSystems.windows;
switch(os) {
case windows:
System.out.println("You chose Windows!");
break;
case unix:
System.out.println("You chose Unix!");
break;
case linux:
System.out.println("You chose Linux!"};
break;
case macintosh:
System.out.println{"You chose Macintosh!");
break;
default:
System.out.println{"I don't know your OS."};
break;
}
}
}
Java.lang.Enum類實現了Comparable和Serializable接口。在該類中已提供了比較枚舉以及將它們序列化成爲一個數據源的細節。不能將一個enum標記爲abstract,除非每一個常量都有一個類主體,並且這些類主體覆蓋了enum中的抽象方法。同時也要注意,枚舉不能使用new進行實例化。編譯器將會告知“enum types may not be instantiated.”(枚舉類型不可以被實例化)。
Java引入了兩個新集合:EnumSet和EnumMap,它們只是想在使用enum時優化集和映射的性能。枚舉可以與當前已存在的集合類一起使用,或者在需要剪裁成枚舉的優化活動時與新集合一起使用。
方法可聲明在一個enum中。但是,對一個正在定義的構造方法來說會有所限制。構造函數不能鏈接超類構造方法,除非超類是另一個enum。enum中的每個常量都可以有一個類主體,但是因爲這實際上是一個匿名類,因此不能定義一個構造函數。
也可以向枚舉和單個enum常量添加屬性。enum常量後面也可以跟有參數,這些參數被傳遞給在enum中定義的構造函數。
以下是一個具有域和方法的枚舉例子:
enum ProgramFlags {
showErrors(0x01),
includeFileOutput(0x02),
useAlternateProcessor{0x04};
private int bit;
ProgramFlags(int bitNumber)
{
bit = bitNumber;
}
public int getBitNumber()
{
return(bit);
}
}
public class EnumBitmapExample {
public static void main(String args[])
{
ProgramFlags flag = ProgramFlags.showErrors;
System.out.println("Flag selected is: "+
flag.ordinal() +
"which is" +
flag.name());
}
}
ordinal()方法返回列表中常量的位置。因爲它在列表的第一個位置出現,而且序號值是從0開始的,所以showErrors的值是0。Name()方法可以用來獲取常量的名稱,這使得可以獲取有關枚舉的更多信息。
1.3.8 元數據
Sun公司決定在Java的JDK 5版本中包括的另一個特性是元數據功能。這使得人們可以使用工具能夠分析的額外信息來標記類,同時也可以自動向類應用特定代碼塊。元數據功能在java.lang. annotation包中被引入。註釋是指Java中一個tag與構造(例如類,在註釋術語中被稱爲目標)之間的關聯。能夠被註釋的構造的類型在java.lang.annotation.ElementType枚舉中列出,同時在下面的表1-3中列出。甚至註釋也可以被註釋。TYPE覆蓋了類、接口和enum聲明。
表1-3
ElementType常量 |
ElementType常量 |
ANNOTATION_TYPE |
METHOD |
CONSTRUCTOR |
PACKAGE |
FIELD |
PARAMETER |
LOCAL_VARIABLE |
TYPE |
另一個引入的概念是註釋的生命週期,被稱爲保持(retention)。某些註釋也許只是在Java源代碼級別上纔有用,例如javadoc工具的註釋。其他的註釋也許在程序正在執行時才需要。RetentionPolicy枚舉列出了一個註釋的3個type生命週期。SOURCE策略表示註釋應該由編譯器丟棄,也就是說,該註釋只在源代碼級別上有用。CLASS策略表示註釋應該在類文件中出現,但是在運行時可能被丟棄。RUNTIME策略則表示註釋在整個程序執行期間都應該有效,並且這些註釋可以通過反射來查看。
在此包中定義了幾個註釋類型。表1-4中列出了這些類型。這些註釋中的每一個都是從Annotation接口繼承而來,該接口定義了equals方法和toString方法。
表1-4
註釋類名稱 |
描述 |
Target |
指定一個註釋類型可以應用的程序元素。每個程序元素只能出現一次 |
Documented |
指定註釋應該通過javadoc或者其他存檔工具存檔。這隻能應用於註釋 |
Inherited |
從超類繼承註釋,而不是接口。作用於此註釋的策略是RUNTIME,它只能應用於註釋 |
Retention |
表示作用於程序元素上的註釋應該多長時間有效。請參見前面討論的RetentionPolicy。作用於此註釋的策略是RUNTIME,它只能應用於註釋 |
Deprecated |
標記一個不贊成使用的程序元素,告訴開發人員他們不應該再使用該元素。保持策略是SOURCE |
Overrides |
表示一個方法將要覆蓋一個父類中的方法。如果確實不存在覆蓋,那麼編譯器將會生成一個錯誤消息。這隻能應用於方法 |
JDK 5引入了兩個有用的源代碼級別註釋,即@deprecated和@overrides。@deprecated註釋用來標記一個不贊成使用的方法,也就是說,客戶端程序員不應該使用它。當在一個類方法中遇到程序員使用這種註釋時,編譯器將會發出一個警告。另一個註釋@overrides,用來標記一個覆蓋父類中方法的方法。編譯器將會確認標記爲@overrides的方法真正是覆蓋了父類中的一個方法。如果子類中的方法沒有覆蓋父類中的方法,那麼編譯器將會發送一個錯誤,警告程序員方法簽名不匹配父類中的方法。
開發一個自定義註釋並不困難。下面創建一個CodeTag註釋,它可以存儲基本的作者信息和修改日期信息,同時也存儲任意應用到該段代碼的bug修復。註釋將被限制到類和方法上:
import java.lang.annotation.*;
@Retention(RetentionPolicy. SOURCE)
@Target({ElementType. TYPE, ElementType.METHOD})
public @interface CodeTag {
String authorName();
String lastModificationDate();
String bugFixes() default "";
}
Retention被設置爲SOURCE,這意味着此註釋在編譯時和運行時是無效的。doclet API用來訪問源代碼級別上的註釋。Target(對於類/接口/枚舉)被設置爲TYPE,對於方法則被設置爲METHOD。如果CodeTag註釋被應用到任意其他的源代碼元素上,那麼就會生成一個編譯器錯誤。前面的兩個註釋元素是authorName和lastModificationDate,兩者都是必要的。bugFixes元素如果沒有指定,那麼默認是空字符串。下面是一個使用CodeTag註釋的示例類:
import java.lang.annotation.*;
@CodeTag(authorName="Dilbert",
lastModificationDate="May 7, 2006")
public class ServerCommandProcessor {
@CodeTag(authorName="Dilbert",
lastModificationDate="May 10, 2006",
bugFixes="BUG0170")
public void setParams(String serverName)
{
//…
}
public void executeCommand(String command, Object... params)
{
//…
}
}
注意註釋是如何用來標記誰對源代碼進行了修改以及是何時修改的。由於bug的修復從而使得方法在類修改之後一天被修改。自定義的註釋可以用來追蹤關於源代碼修改的信息。爲了查看或者處理這些源代碼註釋,就必須使用doclet API。
doclet API(也叫做Javadoc API)已經擴展到可以支持源代碼中的註釋處理。要使用doclet API,就必須在類路徑中包括tools.jar文件(對於版本5或更高的版本,位於默認JDK安裝的庫文件目錄中)。通過編寫一個擴展了com.sun.javadoc.Doclet的Java類就可以使用doclet API。必須實現start方法,因爲這是Javadoc調用doclet以執行自定義處理的方法。下面這個簡單的doclet打印一個Java源文件中的所有類和方法:
import com.sun.javadoc.*;
public class ListClasses extends Doclet {
public static boolean start(RootDoc root) {
ClassDoc[] classes = root.classes();
for (ClassDoc cd : classes) {
System.out.println("Class [" + cd + "] has the following methods");
for(MemberDoc md : cd.methods()) {
System.out.println(" "+ md);
}
}
return true;
}
}
start方法將RootDoc作爲一個參數,該參數通過javadoc工具被自動傳入。從RootDoc開始可以訪問源代碼中的所有元素,以及關於命令行(例如增加的包和類)的信息。
針對註釋添加到doclet API中的接口是AnnotationDesc、AnnotationDesc.Element ValuePair、AnnotationTypeDoc、AnnotationTypeElementDoc和AnnotationValue。
Java源代碼中可以有註釋的任意元素都具有一個annotations()方法,該方法與doclet API同源代碼元素的對應部分相關聯。這些元素是AnnotationTypeDoc、AnnotationTypeElementDoc、ClassDoc、ConstructorDoc、ExecutableMemberDoc、FieldDoc、MethodDoc以及MemberDoc。Annotations()方法返回一個AnnotationDesc數組。
1.AnnotationDesc
此類代表一個註釋,它包括一個註釋類型(AnnotationTypeDoc)和一個同它們的值相配對的註釋類型元素數組。AnnotationDesc定義了表1-5中的方法。
表1-5
方法 |
描述 |
AnnotationTypeDoc annotationType() |
返回這個註釋的類型 |
AnnotationDesc.ElementValuePair[] elementValues() |
返回一個註釋元素的數組以及它們的值。只返回被顯式列出的元素。沒有被顯式列出的元素(已經假設了它們的默認值),將不會被返回,因爲此方法只處理被列出的元素。如果沒有元素,那麼將返回一個空數組 |
2.AnnotationDesc.ElementValuePair
它代表了一個註釋類型的元素及其值之間的關聯。它定義了表1-6中的方法。
表1-6
方法 |
描述 |
AnnotationTypeElementDoc element() |
返回註釋類型元素 |
AnnotationValue value() |
返回註釋類型元素的值 |
3.AnnotationTypeDoc
此接口代表了源代碼中的一個註釋,就如同ClassDoc代表一個Class一樣。它只定義了一個方法,見表1-7。
表1-7
方法 |
描述 |
AnnotationTypeElementDoc[] elements() |
返回此註釋類型的元素的數組 |
4.AnnotationTypeElementDoc
此接口代表了一個註釋類型的元素,它定了一個方法,見表1-8。
表1-8
方法 |
描述 |
AnnotationValue defaultValue() |
返回與此註釋類型相關聯的默認值;如果沒有默認值,則返回null |
5.AnnotationValue
此接口代表了一個註釋類型元素的值,它定義的方法見表1-9。
表1-9
方法 |
描述 |
String toString() |
返回該值的一個字符串表示形式 |
Object value() |
返回值。此值所表示的對象可以是下面的任意類型: |
下面是一個使用doclet API所提供的註釋支持的示例。此doclet顯示它在一個源文件中發現的所有註釋及其值:
import com.sun.javadoc.*;
import java.lang.annotation.*;
public class AnnotationViewer {
public static boolean start(RootDoc root)
{
ClassDoc[] classes = root.classes();
for (ClassDoc cls : classes) {
showAnnotations(cls);
}
return(true);
}
static void showAnnotations(ClassDoc cls)
{
System.out.println("Annotations for class [" + cls + "]");
process(cls.annotations());
System.out.println();
for(MethodDoc m : cls.methods()) {
System.out.println("Annotations for method [" + m + "]");
process(m.annotations());
System.out.println();
}
}
static void process(AnnotationDesc[] anns)
{
for (AnnotationDesc ad :anns) {
AnnotationDesc.ElementValuePair evp[] = ad.elementValues();
for(AnnotationDesc.ElementValuePair e : evp) {
System.out.println(" NAME: " + e.element() +
", VALUE=" + e.value()) ;
}
}
}
}
Start方法迭代了在源文件中發現的所有類(和接口)。因爲在源代碼元素上的所有註釋都是與AnnotationDesc接口相關聯的,所以可以編寫一個方法來處理註釋,而不用考慮該註釋關聯的是哪個代碼元素。showAnnotations方法打印出同當前類相關聯的所有註釋,然後處理該類中的所有方法。doclet API使得處理這些源代碼元素很容易。爲了執行doclet,可以按照如下方式向命令行的程序傳遞doclet名稱和類名稱:
javadoc -doclet AnnotationViewer ServerCommandProcessor.java
doclet在屏幕上顯示如下內容:
Loading source file ServerCommandProcessor.java...
Constructing Javadoc information...
Annotations for class [ServerCommandProcessor]
NAME: CodeTag.authorName(), VALUE="Dilbert"
NAME: CodeTag.lastModificationDate(), VALUE="May 7, 2006"
Annotations for method [ServerCommandProcessor.setParams(java fang. String)]
NAME: CodeTag.authorName(), VALUE="Dilbert"
NAME: CodeTag.lastModificationDate(), VALUE="May 10, 2006"
NAME: CodeTag.bugFixes(), VALUE="BUG0170"
Annotations for method [ServerCommandProcessor.executeCommand (java.lang. Strinq,
java.lang.Object[])]
要在運行時訪問註釋,就必須使用反射API。通過AnnotatedElement接口已經內置了這種支持,該接口是通過反射類AccessibleObject、Class、Constructor、Field、Method和Package來實現的。所有這些元素都可以有註釋。AnotatedElement接口定義了表1-10所示的方法。