2005年,sun公司推出了jdk1.5,同時推出的注解功能吸引了很多人的目光,使用注解編寫代碼,能夠減輕java程序員繁瑣配置的痛苦。
使用注解可以編寫出更加易於維護,bug更少的代碼。
注解是什么呢?按照官方的說法,注解就是元標簽,可以添加到你的代碼,並應用於包聲明、類型聲明、構造函數、方法、字段、參數和變量。
注解提供了一種非常有用的方法來顯示你編寫的方法是否依賴於其他方法,它們是否完整,編寫出的類是否引用了其他類,等等。
按照Oracle官方的說法,基於注解編寫出的java代碼會根據源代碼中的注解生成模板代碼,從而避免我們在大多數情況下編寫模板代碼。這導致了一種聲明式編程風格,在這種風格中,程序員說要做什么功能,工具就寫出相應的代碼來實現它。
簡而言之,注解是一種機制,用於將元標簽與程序元素相關聯,並允許編譯器或虛擬機從這些注解元素中提取程序行為,並在必要時生成相互依賴的代碼。
現在開始我們的java注解學習之旅。
內置注解
java會內置一些已經實現好的注解,可以直接使用,內置的注解主要用於給java編譯器提供指令。
java內置的注解有五個:
- @Deprecated
- @Override
- @SuppressWarnings
- @SafeVarargs
- @FunctionalInterface
@Deprecated注解主要用於給類、方法、變量打上不建議使用的標簽,如果你的代碼使用了不建議使用的類、方法、變量,編譯器就會給你一個警告。
下面是使用@Deprecated的示例:
public class AnnotationTest {
public static void main(String args[]){
MyAnnotation myAnnotation = new MyAnnotation();
int age = myAnnotation.age;
myAnnotation.eat();
}
}
@Deprecated
class MyAnnotation {
@Deprecated
int age;
@Deprecated
void eat(){
}
}
定義了一個MyAnnotation,在類名,變量名和方法名上都使用的@Deprecated注解。
使用了@Deprecated標識的類、方法或變量時,
@Deprecated還有另外一個用處,就是在javadoc文檔中寫明類、方法或變量為什么不建議使用,並且給出替代方法:
@Deprecated
/**
@deprecated 已廢棄,請使用MyNewComponent.
*/
class MyComponent {
}
@Override注解在方法上使用,標識這個方法重寫父類的方法。如果這個方法和父類方法不一致,編譯器就會顯示錯誤。
強烈建議在重寫父類的方法上使用@Override注解,不用也不會有什么影響,但是如果不使用@Override注解,當有人修改父類的方法時,你就無法識別出子類的方法是否重寫了父類的方法。而使用了@Override注解,只要父類沒有這個方法,編譯器就會提示父類沒有對應方法的錯誤。
下面是使用@Override注解的示例:
class Anaimal{
public void run(){
}
}
class Cat extends Anaimal{
@Override
public void run(){
}
}
Cat類的run()方法使用了@Override注解,如果將Cat類中的方法改為run1(),編譯器就會顯示The method run1() of type Cat must override or implement a supertype
的錯誤。
如果不使用@Override注解,將Cat類中的方法改為run1(),系統則不會報錯。
@SuppressWarnings注解也是在方法上使用,用於抑制警告,在調用deprecated的方法或者進行不安全的類型轉化時,編譯器會發出一些警告,使用@SuppressWarnings就可以忽略那些警告。
使用示例:
@SuppressWarnings
public void methodWithWarning() {
}
@SafeVarargs注解主要用於抑制參數類型安全檢查警告,這個是jdk1.7新增的功能。
使用示例:
@SafeVarargs
static void testSafeVarargs(List<String> ... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList;
String s = stringLists[0].get(0);
}
如果不使用@SafeVarargs注解,編譯器會給出警告信息:Type safety: Potential heap pollution via varargs parameter stringLists
。
使用@SafeVarargs有個前提,你必須保證某個使用了可變長度參數的方法,在與泛型類一起使用時不會出現類型安全問題。否則在運行行時會拋出 ClassCastException異常。
@SafeVarargs注解只能在滿足如下條件的方法上使用:
- 參數長度可變的方法或構造方法。
- 方法必須聲明為static或final。
@FunctionalInterface注解主要用於編譯級錯誤檢查,加上該注解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
使用示例:
@FunctionalInterface
interface GreetingService
{
void readMessage(String message);
}
創建注解
注解的創建和接口有點類似,都是使用interface關檢字,區別是創建注解時,需要在interface前面加上一個@字符。
下面是創建一個注解的例子:
public @interface MyAnnotation {
}
上面創建注解沒有任何成員變量。
創建帶成員變量的注解:
@interface TestAnnotation{
int age();
String name();
}
TestAnnotation注解有兩個成員變量,age和name。
注解的成員變量以無參數無方法體的方法形式聲明。
使用default關鍵字指定注解成員變量的默認值:
@interface TestAnnotation{
int age() default 2;
String name() default “小明”;
}
使用自定義注解時,如果該注解的變量有默認值,可以不為成員變量指定值,直接使用默認值。
使用示例:
@interface TestAnnotation{
int age() default 2;
String name() default "小明";
}
class MyAnnotation {
@TestAnnotation
public void getInfo(){
}
}
如果注解的變量沒有默認值,在使用時必須為每個變量都指定值,代碼如下:
@interface TestAnnotation{
int age();
String name();
}
class MyAnnotation {
@TestAnnotation(age=2,name="小明")
public void getInfo(){
}
}
元注解
什么是元注解呢?元注解就是注解的注解,也就是用於定義注解的注解,可以理解為注解的基礎數據類型。這玩意真的很拗口,還是看代碼比較舒服。
@Target(ElementType.METHOD)
@interface Test_Target {
public String doTestTarget();
}
@Target就是元注解,用於定義Test_Target注解。
java提供五種元注解,分別是:
- @Retention 指定注解的生命周期,即存活時間。
- @Documented javadoc命令生成的文檔中體現注解的內容
- @Target 指定注解可用於哪些元素,例如類、方法、變量等
- @Inherited 注解的繼承性,
- @Repeatable 可重復使用的注解
@Retention用於指定注解的生命周期,即存活時間。@Retention提供了如下的三個值,在使用@Retention時,必須使用其中的一個值。
- RetentionPolicy.SOURCE 注解只在源碼階段保留,在編譯器進行編譯生成class文件時丟棄,無法通過反射獲取注解信息。
- RetentionPolicy.CLASS 注解只被保留到編譯進行的時候,它並不會被加載到JVM中,無法通過反射獲取注解信息,這是默認值。
- RetentionPolicy.RUNTIME 注解可以保留到程序運行的時候,它會被加載進入到 JVM 中,程序運行時可以通過反射獲取到它們。
下面是使用@Retention定義一個自定義注解的示例:
@Retention(RetentionPolicy.RUNTIME)
@interface Test_Retention{
}
@Test_Retention
class MyAnnotation {
public void getInfo(){
}
}
上面的代碼創建了一個Test_Retention的注解,並且使用@Retention指定Test_Retention注解可以保留到程序運行的時候(RetentionPolicy.RUNTIME),MyAnnotation使用@Test_Retention修飾,因此在運行時可以通過反射獲取到MyAnnotation的注解信息。
@Documented如果類A使用了@Documented元注解注解的注解,那么在使用javadoc生成的類A的文檔會包含有相應的注解信息。
使用示例:
@Documented
@interface TestDocument{
String doTestDocument();
}
@TestDocument (doTestDocument="保留注解信息測試")
class MyAnnotation {
public void getInfo(){
}
}
@Target指定了注解所修飾對象的范圍,可用於變量、參數、方法、包信息等。
@Target元注解提供如下的八個值:
- ElementType.ANNOTATION_TYPE 用於描述注解類型
- ElementType.CONSTRUCTOR 用於注解構造方法
- ElementType.FIELD 用於變量注解
- ElementType.LOCAL_VARIABLE 用於局部變量注解
- ElementType.METHOD 用於方法注解
- ElementType.PACKAGE 用於包注解
- ElementType.PARAMETER 用於方法內的參數注解
- ElementType.TYPE 用於類、接口、枚舉注解
代碼示例:
@Target(ElementType.METHOD)
@interface TestMethodTarget{
}
@Target(ElementType.FIELD)
@interface TestFieldTarget{
}
@Target(ElementType.TYPE)
@interface TestTypeTarget{
}
TestMethodTarget注解只能用於注解類的方法,TestFieldTarget只能用於注解類的成員變量,TestTypeTarget可用於注解類、接口(包括注解類型) 或enum聲明
@Inherited指定了注解可被繼承。某個類使用了被@Inherited修飾的注解,那么那個注解也會用到該類的子類。
代碼示例:
@Inherited
@interface TestInherited{
}
@Repeatable同一個注解可多次使用。例如一個人有多種愛好,跑步、畫畫、看電影等。
示例代碼:
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String hobby default "";
}
@Person(hobby="runing")
@Person(hobby="drawing")
@Person(hobby="watching movies")
public class Tom{
}
上面的代碼,@Repeatable 注解了 Person。而 @Repeatable后面括號中的類相當於一個容器注解。
什么是容器注解呢?就是用來存放其它注解的地方。它本身也是一個注解。
上面詳細介紹了注解的知識以及自定義注解的語法。現在我們來看下如何使用注解進行單元測試。
使用注解這要到反射的知識,關於反射的知識請看我的另外一篇文章“Java反射使用總結”。在得到反射對象后,要調用isAnnotationPresent方法這個對象是否包含指定類型的注解。 Annotation
示例代碼:
public class Marathonrunner {
@RuningTest
public void test5km(){
System.out.println("進行5公里跑步測試");
}
public void test10km(){
System.out.println("進行10公里跑步測試");
}
@RuningTest
public void test21km(){
System.out.println("進行21公里跑步測試");
}
public void test42km(){
System.out.println("進行42公里跑步測試");
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface RuningTest{
}
public class RuntestTool {
public static void main(String args[]){
Marathonrunner xiaoming = new Marathonrunner();
Class clazz = xiaoming.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(RuningTest.class)){
try {
method.setAccessible(true);
method.invoke(xiaoming, null);
} catch (Exception e) {
}
}
}
}
}
運行結果:
進行21公里跑步測試
進行5公里跑步測試
上面的代碼,我們定義了一個RuningTest注解,里面沒有任何變量。這個注解使用@Retention和@Target元注解修飾,其中@Target元注解的值規定了這個RuningTest注解只能在方法上使用,而@Retention元注解值指定了在運行時可以獲取到注解的信息。
定義了一個Marathonrunner馬拉松遠動員類,里面有4個方法。
定義了一個專門用於測試Marathonrunner運動員方法的類。如果我們想測試某個類,只需在那個類上添加@RuningTest注解,不加@RuningTest注解的方法不會進行測試。test5km()和test21km()方法都加了@RuningTest注解,所以被測試到。
注解在spring,mybatis注解中廣發應用。下次專門寫篇文章講下spring中注解的應用。
總結:
本文主要講解了注解的概念,元注解的概念,如何自定義注解,以及如何使用自己定義注解進行單元測試。