1. 程式人生 > >Java 註解指導手冊 – 終極嚮導

Java 註解指導手冊 – 終極嚮導

原文連結 原文作者:Dani Buiza 譯者:Toien Liu  校對:深海

編者的話:註解是java的一個主要特性且每個java開發者都應該知道如何使用它。

現在,是時候彙總這些和註解相關的資訊到一篇文章了,祝大家閱讀愉快。

目錄

  1. 什麼是註解
  2. 介紹
  3. 消費器
  4. 註解語法和註解元素
  5. 在什麼地方使用
  6. 使用案例
  7. 內建註解
  8. Java 8 與註解
  9. 自定義註解
  10. 提取註解
  11. 註解整合
  12. 使用註解的知名類庫
  13. 小結
  14. 下載
  15. 資料

在這篇文章中我們將闡述什麼是Java註解,它們如何工作,怎麼使用它們。

我們將揭開Java註解的面紗,包括內建註解或稱元註解,還將討論Java8中與之相關的的新特性。

最後,我們將實現自定義的註解,編寫一個使用註解的處理程式(消費器),它通過java反射使用註解。

我們還會列出一些基於註解,知名且被廣泛應用的第三方類庫如:Junit,JAXB,Spring,Hibernate。

在文章的最後,會有一個壓縮檔案包含了文章中的所有示例,實現這些例子使用的軟體版本如下所示:

  • Eclipse Luna 4.4
  • JRE Update 8.20
  • Junit 4
  • Hibernate 4.3.6
  • FindBugs 3.0.0

1.什麼是註解?

註解早在J2SE1.5就被引入到Java中,主要提供一種機制,這種機制允許程式設計師在編寫程式碼的同時可以直接編寫元資料。

在引入註解之前,程式設計師們描述其程式碼的形式尚未標準化,每個人的做法各異:transient關鍵字、註釋、介面等。這顯然不是一種優雅的方式,隨之而來的一種嶄新的記錄元資料的形式——註解被引入到Java中。

其它因素也促成了這個決定:當時不同型別的應用程式使用XML作為標準的程式碼配置機制,這其實並不是最佳方式,因為程式碼和XML的解耦以及未來對這種解耦應用的維護並不低廉。另外,由於非保留字的使用,例如“@deprecated”自從Java1.4便開始在Java文件中使用。我非常確定這是一個現在在註解中使用“@”原因。

包含註解的設計和開發的Java規範主要有以下兩篇:

2. 介紹

解釋何為註解的最佳方式就是元資料這個詞:描述資料自身的資料。註解就是程式碼的元資料,他們包含了程式碼自身的資訊。

註解可以被用在包,類,方法,變數,引數上。自Java8起,有一種註解幾乎可以被放在程式碼的任何位置,叫做型別註解。我們將會在後面談到具體用法。

被註解的程式碼並不會直接被註解影響。這隻會向第三系統提供關於自己的資訊以用於不同的需求。

註解會被編譯至class檔案中,而且會在執行時被處理程式提取出來用於業務邏輯。當然,建立在執行時不可用的註解也是可能的,甚至可以建立只在原始檔中可用,在編譯時不可用的註解。

3.消費器

理解註解的目的以及如何使用它都會帶來困難,因為註解本身並不包含任何功能邏輯,它們也不會影響自己註解的程式碼,那麼,它們到底為什麼而存在呢?

這個問題的解釋就是我所稱的註解消費器。它們是利用被註解程式碼並根據註解資訊產生不同行為的系統或者應用程式。

例如,在Java自帶的內建註解(元註解)中,消費器是執行被註解程式碼的JVM。還有其他稍後談到的其他例子,例如JUnit,消費器是讀取,分析被註解程式碼的JUnit處理程式,它還可以決定測試單元和方法執行順序。我們會在JUnit章節更深入。

消費器使用Java中的反射機制來讀取和分析被註解的原始碼。使用的主要的包有:java.lang, java.lang.reflect。我們將會在本篇指南中介紹如何用反射從頭開始建立一個自定義的消費器。

4. 註解語法和元素

宣告一個註解需要使用“@”作為字首,這便向編譯器說明,該元素為註解。例如:

@Annotation
public void annotatedMehod() {
...
 }

上述的註解名稱為Annotation,它正在註解annotatedMethod方法。編譯器會處理它。註解可以以鍵值對的形式持有有很多元素,即註解的屬性。

@Annotation(
 info = "I am an annotation",
 counter = "55"
)
public void annotatedMehod() {
...
 }

如果註解只包含一個元素(或者只需要指定一個元素的值,其它則使用預設值),可以像這樣宣告:

@Annotation("I am an annotation")
public void annotatedMehod() {
...
 }

就像我們看到的一樣,如果沒有元素需要被指定,則不需要括號。多個註解可以使用在同一程式碼上,例如類:

@ Annotation (info = "U a u O")
@ Annotation2
class AnnotatedClass { ... }

一些java本身提供的開箱即用的註解,我們稱之為內建註解。也可以定義你自己的註解,稱之為子定義註解。我們會在下一章討論。

5. 在什麼地方使用

註解基本上可以在Java程式的每一個元素上使用:類,域,方法,包,變數,等等。

自Java8,誕生了通過型別註解的理念。在此之前,註解是限於在前面討論的元素的宣告上使用。從此,無論是型別還是宣告都可以使用註解,就像:

@MyAnnotation String str = "danibuiza";

我們將會在Java8關聯章節看到這種機制的更多細節。

6. 使用案例

註解可以滿足許多要求,最普遍的是:

  • 向編譯器提供資訊:註解可以被編譯器用來根據不同的規則產生警告,甚至錯誤。一個例子是Java8中@FunctionalInterface註解,這個註解使得編譯器校驗被註解的類,檢查它是否是一個正確的函式式介面。
  • 文件:註解可以被軟體應用程式計算程式碼的質量例如:FindBugs,PMD或者自動生成報告,例如:用來Jenkins, Jira,Teamcity。
  • 程式碼生成:註解可以使用程式碼中展現的元資料資訊來自動生成程式碼或者XML檔案,一個不錯的例子是JAXB。
  • 執行時處理:在執行時檢查的註解可以用做不同的目的,像單元測試(JUnit),依賴注入(Spring),校驗,日誌(Log4j),資料訪問(Hibernate)等等。

在這篇手冊中我們將展現幾種註解可能的用法,包括流行的Java類庫是如何使用它們的。

7. 內建註解

Java語言自帶了一系列的註解。在本章中我們將闡述最重要的一部分。這個清單隻涉及了Java語言最核心的包,未包含標準JRE中所有包和庫如JAXB或Servlet規範。

以下討論到的註解中有一些被稱之為Meta註解,它們的目的註解其他註解,並且包含關於其它註解的資訊。

  • @Retention:這個註解注在其他註解上,並用來說明如何儲存已被標記的註解。這是一種元註解,用來標記註解並提供註解的資訊。可能的值是:
    • SOURCE:表明這個註解會被編譯器忽略,並只會保留在原始碼中。
    • CLASS:表明這個註解會通過編譯駐留在CLASS檔案,但會被JVM在執行時忽略,正因為如此,其在執行時不可見。
    • RUNTIME:表示這個註解會被JVM獲取,並在執行時通過反射獲取。

我們會在稍後展開幾個例子。

  • @Target:這個註解用於限制某個元素可以被註解的型別。例如:
    • ANNOTATION_TYPE 表示該註解可以應用到其他註解上
    • CONSTRUCTOR 表示可以使用到構造器上
    • FIELD 表示可以使用到域或屬性上
    • LOCAL_VARIABLE表示可以使用到區域性變數上。
    • METHOD可以使用到方法級別的註解上。
    • PACKAGE可以使用到包宣告上。
    • PARAMETER可以使用到方法的引數上
    • TYPE可以使用到一個類的任何元素上。
  • @Documented:被註解的元素將會作為Javadoc產生的文件中的內容。註解都預設不會成為成為文件中的內容。這個註解可以對其它註解使用。
  • @Inherited:在預設情況下,註解不會被子類繼承。被此註解標記的註解會被所有子類繼承。這個註解可以對類使用。
  • @Deprecated:說明被標記的元素不應該再度使用。這個註解會讓編譯器產生警告訊息。可以使用到方法,類和域上。相應的解釋和原因,包括另一個可取代的方法應該同時和這個註解使用。
  • @SuppressWarnings:說明編譯器不會針對指定的一個或多個原因產生警告。例如:如果我們不想因為存在尚未使用的私有方法而得到警告可以這樣做:
@SuppressWarnings( "unused")
private String myNotUsedMethod(){
 ...
}

通常,編譯器會因為沒呼叫該方而產生警告; 用了註解抑制了這種行為。該註解需要一個或多個引數來指定抑制的警告型別。

  • @Override:向編譯器說明被註解元素是重寫的父類的一個元素。在重寫父類元素的時候此註解並非強制性的,不過可以在重寫錯誤時幫助編譯器產生錯誤以提醒我們。比如子類方法的引數和父類不匹配,或返回值型別不同。
  • @SafeVarargs:斷言方法或者構造器的程式碼不會對引數進行不安全的操作。在Java的後續版本中,使用這個註解時將會令編譯器產生一個錯誤在編譯期間防止潛在的不安全操作。

8. Java 8 與註解

Java8帶來了一些優勢,同樣註解框架的能力也得到了提升。在本章我們將會闡述,並就java8帶來的3個註解做專題說明和舉例:

@Repeatable註解,關於型別註解的宣告,函式式介面註解@FunctionalInterface(與Lambdas結合使用)。

  • @Repeatable:說明該註解標識的註解可以多次使用到同一個元素的宣告上。

看一個使用的例子。首先我們創造一個能容納重複的註解的容器:

/**
 * Container for the {@link CanBeRepeated} Annotation containing a list of values
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE_USE )
public @interface RepeatedValues
{
 CanBeRepeated[] value();
}

接著,建立註解本身,然後標記@Repeatable

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE_USE )
@Repeatable( RepeatedValues.class )
public @interface CanBeRepeated
{

 String value();
}

最後,我們可以這樣重複地使用:

@CanBeRepeated( "the color is green" )
@CanBeRepeated( "the color is red" )
@CanBeRepeated( "the color is blue" )
public class RepeatableAnnotated
{

}

如果我們嘗試去掉@Repeatable

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE_USE )
public @interface CannotBeRepeated
{

 String value();
}

@CannotBeRepeated( "info" )
/*
 * if we try repeat the annotation we will get an error: Duplicate annotation of non-repeatable type
 *
 * @CannotBeRepeated. Only annotation types marked
 *
 * @Repeatable can be used multiple times at one target.
 */
// @CannotBeRepeated( "more info" )
public class RepeatableAnnotatedWrong
{

}

我們會得到編譯器的錯誤資訊:

Duplicate annotation of non-repeatable type
  • 自Java8開始,我們可以在型別上使用註解。由於我們在任何地方都可以使用型別,包括 new操作符,casting,implements,throw等等。註解可以改善對Java程式碼的分析並且保證更加健壯的型別檢查。這個例子說明了這一點:
@SuppressWarnings( "unused" )
public static void main( String[] args )
{
 // type def
 @TypeAnnotated
 String cannotBeEmpty = null;

 // type
 List<@TypeAnnotated String> myList = new ArrayList<String>();

 // values
 String myString = new @TypeAnnotated String( "this is annotated in java 8" );

}

// in method params
public void methodAnnotated( @TypeAnnotated int parameter )
{
 System.out.println( "do nothing" );
}

所有的這些在Java8之前都是不可能的。

  • @FunctionalInterface:這個註解表示一個函式式介面元素。函式式介面是一種只有一個抽象方法(非預設)的介面。編譯器會檢查被註解元素,如果不符,就會產生錯誤。例子如下:
// implementing its methods
@SuppressWarnings( "unused" )
MyCustomInterface myFuncInterface = new MyCustomInterface()
{

 @Override
 public int doSomething( int param )
 {
 return param * 10;
 }
};

// using lambdas
@SuppressWarnings( "unused" )
 MyCustomInterface myFuncInterfaceLambdas = ( x ) -> ( x * 10 );
}

@FunctionalInterface
interface MyCustomInterface
{
/*
 * more abstract methods will cause the interface not to be a valid functional interface and
 * the compiler will thrown an error:Invalid '@FunctionalInterface' annotation;
 * FunctionalInterfaceAnnotation.MyCustomInterface is not a functional interface
 */
 // boolean isFunctionalInterface();

 int doSomething( int param );
}

這個註解可以被使用到類,介面,列舉和註解本身。它的被JVM保留並在runtime可見,這個是它的宣告:

 @Documented
 @Retention(value=RUNTIME)
 @Target(value=TYPE)
public @interface FunctionalInterface

9. 自定義註解

正如我們之前多次提及的,可以定義和實現自定義註解。本章我們即將探討。
首先,定義一個註解:

public @interface CustomAnnotationClass

這樣建立了一個新的註解型別名為 CustomAnnotationClass。關鍵字:@interface說明這是一個自定義註解的定義。

之後,你需要為此註解定義一對強制性的屬性,保留策略和目標。還有一些其他屬性可以定義,不過這兩個是最基本和重要的。它們在第8章,描述註解的註解時討論過,它們同樣也是Java內建的註解。

所以,我們為自定義的註解設定屬性:

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface CustomAnnotationClass implements CustomAnnotationMethod

在保留策略中 RUNTIME 告訴編譯器這個註解應該被被JVM保留,並且能通過反射在執行時分析。通過 TYPE 我們又設定該註解可以被使用到任何類的元素上。

之後,我們定義兩個註解的成員:

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface CustomAnnotationClass
{

 public String author() default "danibuiza";

 public String date();

}

以上我們僅定義了預設值為“danibuiza”的 author 屬性和沒有預設值的date屬性。我們應強調所有的方法宣告都不能有引數和throw子句。這個返回值的型別被限制為之前提過的字串,類,列舉,註解和儲存這些型別的陣列。

現在我們可以像這樣使用剛建立的自定義註解:

@CustomAnnotationClass( date = "2014-05-05" )
public class AnnotatedClass
{
...
}

在另一種類似的用法中我們可以建立一種註解方法的註解,使用Target METHOD:

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface CustomAnnotationMethod
{

 public String author() default "danibuiza";

 public String date();

 public String description();

}

這種註解可以使用在方法宣告上:

@CustomAnnotationMethod( date = "2014-06-05", description = "annotated method" )
public String annotatedMethod()
 {
 return "nothing niente";
}

@CustomAnnotationMethod( author = "friend of mine", date = "2014-06-05", description = "annotated method" )
public String annotatedMethodFromAFriend()
{
 return "nothing niente";
}

有很多其它屬性可以用在自定義註解上,但是 目標 (Target)和 保留策略(Retention Policy)是最重要的兩個。

10. 獲取註解

Java反射API包含了許多方法來在執行時從類,方法或者其它元素獲取註解。介面AnnotatedElement包含了大部分重要的方法,如下:

  • getAnnotations(): 返回該元素的所有註解,包括沒有顯式定義該元素上的註解。
  • isAnnotationPresent(annotation): 檢查傳入的註解是否存在於當前元素。
  • getAnnotation(class): 按照傳入的引數獲取指定型別的註解。返回null說明當前元素不帶有此註解。

class 通過java.lang.Class被實現,java.lang.reflect.Method 和 java.lang.reflect.Field,所以可以基本上被和任何Java元素使用。

現在,我們將看一個怎麼讀取註解的例子:
我們寫一個程式,從一個類和它的方法中讀取所有的存在的註解:

public static void main( String[] args ) throws Exception
{

 Class<AnnotatedClass> object = AnnotatedClass.class;
 // Retrieve all annotations from the class
 Annotation[] annotations = object.getAnnotations();
 for( Annotation annotation : annotations )
 {
 System.out.println( annotation );
 }

 // Checks if an annotation is present
 if( object.isAnnotationPresent( CustomAnnotationClass.class ) )
 {

 // Gets the desired annotation
 Annotation annotation = object.getAnnotation( CustomAnnotationClass.class );

 System.out.println( annotation );

 }
 // the same for all methods of the class
 for( Method method : object.getDeclaredMethods() )
 {

 if( method.isAnnotationPresent( CustomAnnotationMethod.class ) )
 {

 Annotation annotation = method.getAnnotation( CustomAnnotationMethod.class );

 System.out.println( annotation );

 }

 }
}

輸出如下:

@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05)

@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05)

@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=friend of mine, date=2014-06-05, description=annotated method)
@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=danibuiza, date=2014-06-05, description=annotated method)

在這個程式中,我們可以看到 getAnnotations()方法來獲取所有某個物件(方法,類)上的所有註解的用法。展示了怎樣使用isAnnotationPresent()方法和getAnnotation()方法檢查是否存在特定的註解,和如何獲取它。

11. 註解中的繼承

註解在Java中可以使用繼承。這種繼承和普通的面向物件繼承幾乎沒有共同點。

如果一個註解在Java中被標識成繼承,使用了保留註解@Inherited,說明它註解的這個類將自動地把這個註解傳遞到所有子類中而不用在子類中宣告。通常,一個類繼承了父類,並不繼承父類的註解。這完全和使用註解的目的一致的:提供關於被註解的程式碼的資訊而不修改它們的行為。

我們通過一個例子更清楚地說明。首先,我們定義一個自動繼承的自定義註解。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InheritedAnnotation
{

}

有一個父類名為:AnnotatedSuperClass,已經被自定義的註解給註解上了:

@InheritedAnnotation
public class AnnotatedSuperClass
{

 public void oneMethod()
 {

 }

}

一個子類繼承父類:

@InheritedAnnotation
public class AnnotatedSuperClass
{

 public void oneMethod()
 {

 }

}

子類 AnnotatedSubClass 展示了自動繼承的註解 @InheritedAnnotation。我們看到下面的程式碼通過 isAnnotationPresent() 方法測試出了當前註解。

<pre>System.out.println( "is true: " + AnnotatedSuperClass.class.isAnnotationPresent( InheritedAnnotation.class ) );

System.out.println( "is true: " + AnnotatedSubClass.class.isAnnotationPresent( InheritedAnnotation.class ) );</pre>
<pre>

輸出如下:

is true: true
is true: true

我們可以看到子類雖然並沒有宣告註解,但還是被自動地註解上了。

如果我們嘗試註解在一個介面中:

@InheritedAnnotation
public interface AnnotatedInterface
{

 public void oneMethod();

}

一個實現了該介面的類:

public class AnnotatedImplementedClass implements AnnotatedInterface
{

 @Override
 public void oneMethod()
 {

 }

}

經過 isAnnotationPresent() 方法測試:

System.out.println( "is true: " + AnnotatedInterface.class.isAnnotationPresent( InheritedAnnotation.class ) );

System.out.println( "is true: " + AnnotatedImplementedClass.class.isAnnotationPresent( InheritedAnnotation.class ) );

結果如下:

is true: true
is true: false

這個結果說明繼承註解和介面在一起使用時,介面中的註解在實現類中:僅僅被忽略。實現類並不繼承介面的註解;介面繼承僅僅適用於類繼承。正如 AnnotatedSubClass。
@Inheriated註解僅在存在繼承關係的類上產生效果,在介面和實現類上並不工作。這條同樣也適用在方法,變數,包等等。只有類才和這個註解連用。

註解不能繼承註解,如果你嘗試這麼做了,就會得到編譯器丟擲的錯誤:

Annotation type declaration cannot have explicit superinterfaces

12. 使用註解的知名類庫

在這一章我們將展示知名類庫是如何利用註解的。一些類庫如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit。它們使用註解來完成程式碼質量分析,單元測試,XML解析,依賴注入和許多其它的工作。

在這篇手冊中我們將討論以下類庫的部分內容:

12.1. Junit

這個框架用於完成Java中的單元測試。自JUnit4開始,註解被廣泛應用,成為Junit的設計的主幹之一。

基本上,JUnit處理程式通過反射讀取類和測試套件,按照在方法上,類上的註解順序地執行它們。當然還有一些用來修改測試執行的註解。其它註解都用來執行測試,阻止執行,改變執行順序等等。

用到的註解相當多,但是我們將會看到最重要的幾個:

  • @Test:這個註解向JUnit說明這個被註解的方法一定是一個可執行的測試方法。這個註解只能標識在方法上,並且被JVM保留至執行時。
    @Test
    public void testMe()
    {
     //test assertions
     assertEquals(1,1);
    }
    

    從這個例子可以看到我們如何在JUnit中使用這類註解。

  • @Before:這個註解用來向JUnit說明被標記的方法應該在所有測試方法之前被執行。這對於在測試之前設定測試環境和初始化非常有用。同樣只適用於方法上:
    @Before
    public void setUp()
     {
     // initializing variables
     count = 0;
     init();
    }
    
  • @After:這個註解用來向JUnit說明被註解的方法應該在所有單元測試之後執行。這個註解通常用來銷燬資源,關閉,釋放資源或者清理,重置等工作。
    @After
    public void destroy()
    {
     // closing input stream
     stream.close();
    }
    
  • @Ignore:這個方法用來向JUnit說明被註解的方法應該不被當作測試單元執行。即使它被註解成為一個測試方法,也只能被忽略。
    @Ignore
    @Test
    public void donotTestMe()
    {
     count = -22;
     System.out.println( "donotTestMe(): " + count );
    }
    

    這個方法可能在在開發除錯階段使用,但一旦開始進入釋出階段變需要將被忽略的程式碼去掉。

  • @FixMethodOrder:指定執行的順序,正常情況下Junit處理程式負責它按照完全隨機的無法預知的順序執行。當所有的測試方法都相互獨立的時候,不推薦使用這個註解。但是,當測試的場景需要測試方法按照一定規則的時候,這個註解就派上用場了。
    @FixMethodOrder( MethodSorters.NAME_ASCENDING )
    public class JunitAnnotated
    

12.2. Hibernate ORM

Hibernate可能是一個用得最廣泛的物件關係對映類庫。它提供了物件模型和關係型資料庫的對映框架。使用註解作為設計的一部分。

在本章我們將討論一兩個由Hibernate提供的註解並解釋它的處理程式如何處理它們。

下面的程式碼段使用了@Entity和@Table。這兩個是用來向消費器(Hibernate處理程式)說明被註解的類是一個實體類,以及它對映的SQL表名。實際上,這個註解僅僅是指明瞭主表,還可以有說明字表的註解。

@Entity
@Table( name = "hibernate_annotated" )
public class HibernateAnnotated

接下來的程式碼段展示瞭如何向Hibernate處理程式說明被標記的元素是表的主鍵,並對映名為“id”的列,並且主鍵是自動生成的。

@Id
@GeneratedValue
@Column( name = "id" )
private int id;

為了指定標準的SQL表列名,我們可以寫如下註解:

@Column( name = "description" )
private String description;

這說明被標記的元素對映的是這個類所對應的表中名為“description”的一列。

12.3. Spring MVC

Spring是個被廣泛使用的Java企業級應用框架。其一項重要的特性就是在Java程式使用依賴注入。

Spring使用註解作為XML(Spring的早期版本使用的基於XML配置)的一種替代方式。現在兩者都是可行的。你可以使用XML檔案或者註解配置你的專案。在我看來兩者都各有優勢。

我們將在下例中展示兩個可用註解:

@Component
public class DependencyInjectionAnnotation
{

 private String description;

 public String getDescription()
 {
 return description;
 }

 @Autowired
 public void setDescription( String description )
 {
 this.description = description;
 }

}

在次程式碼片段中我們可以找到兩個使用在了類和方法上的註解。

  • @Component:說明被標記的元素,在本例中是一個類,是一個自動檢測的目標。這意味著被註解的類,將會被Spring容器例項化並管理。
  • @Autowired:Spring容器將會嘗試通過型別(這是一種元素匹配機制)使用這個set方法來自動裝配。此註解也可以使用在構造器和屬性上,Spring也會根據註解的地方不同採取不同的操作。

12.4. Findbugs

這是一個用來測量程式碼質量,並提供一系列可能提高它的工具。它會根據預定義(或者自定義)的違反規則來檢查程式碼。Findbugs提供一系列註解來允許開發者來改變預設行為。

它主要使用反射讀取程式碼(和包含的註解)並決定基於它們,應該採取什麼行為。

一個列子是 edu.umd.cs.findbugs.annotations.SuppressFBWarnings 註解期待一種違反規定的鍵值並把它當作引數。這非常像 java.lang.SuppressWarnings。被用來向 Findbugs 說明當執行程式碼分析的時候,忽略指定的違反規則。

這是一個例子:

@SuppressFBWarnings( "HE_EQUALS_USE_HASHCODE" )
public class FindBugsAnnotated
{

 @Override
 public boolean equals( Object arg0 )
 {
 return super.equals( arg0 );
 }

}

這個類已經重現了 Object 的 equals 方法,但是並沒有重寫 hashCode 方法。這通常會導致問題,因為 hashCode 和 equals 兩者應該被同時重寫,否則在使用這個物件作為 HashMap 的key值的時候會導致錯誤。所以,Findbugs會在違反規則報告中創造一個錯誤條目。

如果註解 @SuppressFBWarnings 設定了 HE_EQUALS_USE_HASHCODE 值以後,處理程式就不會在丟擲這型別的錯誤了。

Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode()
Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode()

這個類重寫了 equals 方法,但是沒有重寫 hashCode 方法,繼承自 java.lang.Object 的 hashCode 方法(返回每個物件被JVM賦予的特定值)。因此,這個類幾乎違反了相同的物件必須 hashcode 相同的一致性。

如果你認為這個類的例項不會插入到雜湊表,推薦的做法是像這樣實現 hashCode 方法:

public int hashCode() {
 assert false : "hashCode not designed";
 return 42; // any arbitrary constant will do
 }
Rank: Troubling (14), confidence: High
Pattern: HE_EQUALS_USE_HASHCODE
Type: HE, Category: BAD_PRACTICE (Bad practice)

這個錯誤包含了該問題的一種解釋並且提示如何處理它。在這種情況下,解決方案應該是實現 hashCode 方法。

12.5. JAXB

JAXB是一個用來相互轉換和對映XML檔案與Java物件的類庫。實際上,這個類庫與標準JRE一起提供,不需要任何額外的下載和配置。可以直接通過引入 java.xml.bind.annotation 包下的類直接使用。

JAXB使用註解來告知處理程式(或者是JVM)XML檔案與程式碼的相互轉化。例如,註解可以用來在程式碼上標記XML節點,XMl屬性,值等等。我們將看到一個例子:

首先,我們宣告一個類說明它應該是XML檔案中的一個節點:

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlType( propOrder = { "brand", "model", "year", "km" } )
@XmlRootElement( name = "Car" )
class Car
...

使用註解@XmlType,@XmlRoootElement。它們用來告知 JAXB 處理程式 Car 這個類在轉換後,將會轉換成為XML中的一個節點。這個@XmlType說明了屬性在XML中的順序。JAXB將會基於這些註解執行合適的操作。

除了分離的 getter 和 setter 屬性,再也不需要向這個類中新增其它東西來完成轉換。現在我們需要一個消費器程式來執行轉換成XML:

Car car = new Car();
car.setBrand( "Mercedes" );
car.setModel( "SLK" );
car.setYear( 2011 );
car.setKm( 15000 );

Car carVW = new Car();
carVW.setBrand( "VW" );
carVW.setModel( "Touran" );
carVW.setYear( 2005 );
carVW.setKm( 150000 );

/* init jaxb marshaler */
JAXBContext jaxbContext = JAXBContext.newInstance( Car.class );
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

/* set this flag to true to format the output */
jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );

/* marshaling of java objects in xml (output to standard output) */
jaxbMarshaller.marshal( car, System.out );
jaxbMarshaller.marshal( carVW, System.out );

程式產生的輸入如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
 <brand>Mercedes</brand>
 <model>SLK</model>
 <year>2011</year>
 <km>15000</km>
</Car>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Car>
 <brand>VW</brand>
 <model>Touran</model>
 <year>2005</year>
 <km>150000</km>
</Car>

13. 總結

在這篇文件中,我們解釋了Java註解是Java1.5開始一個非常重要的特性。基本上,註解都是作為包含程式碼資訊的元資料而被標記到程式碼中。它們不會改變或者影響程式碼的任何意義,而是被第三方稱為消費器的程式通過反射的方式使用。

我們列出了Java預設的內建註解,一些稱為元註解例如:@Target或者 @Retention,又有@Override,@SuppressWarnings,還有一些Java8相關的註解,比如:@Repeatable,@FunctionalInterface和型別註解。我們還展現了一兩個結合使用反射的例子,並描述了一些使用註解的類庫例如Spring, Junit,Hibernate。

註解是Java中一種分析元資料的強大機制,可以在不同的程式中擔任不同的作用,例如校驗,依賴注入,單元測試。

14. 下載

這是一個java註解的教程。

15. 資料

這是一些非常有用的關於Java註解的資料:


Toien

A normal coding monkey trying do something different.

Latest posts by Toien