2015年7月9日 星期四

Annotation

※自定annotation

public @interface Chess {//自動繼承java.lang.annotation的Annotation
    public String key();
}

用法:
/*如果不是叫key,叫value,而且只有宣告一個屬性的時候,有個特殊能力; 就是@Chess(value="xxx"), value=可以不打,變成@Chess("xxx"),
又或者有其他屬性,且全部都有default時,也可以這樣用,所以叫value是有好處的
*/
@Chess(key="宣告的是String,所以打String內容")
class xxx{}

兩個變數以上要這樣:
public @interface Chess {//自動繼承java.lang.annotation的Annotation
    public String key();
    public int value() default 10;//注意只能使用基本型態,不然會報「only primitive type, String, Class, annotation, enumeration are permitted or 1-dimensional arrays thereof」的錯
}

@Chess(key="ooo", value=8)
//@Chess(key="ooo") 因為有預設值10,所以可以不打,沒預設值一定要有,不然會報錯
class xxx{}

至於定義了annotation要做哪些事,要配合reflection,先介紹完內建的annotation再說

-------------------------------------------------------------------------------------------------------------
以下介紹 java.lang 的 Annotation Types,因為annotation裡面還有annotation,可能要看兩次才有辦法了解

※@Deprecated(已過時)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

只要是有過時的方法、屬性…等(可以參考下面的@Target),就可以加這個annotation。
別人要使用時,就會發現有刪除線和警告,內行的就知道此方法有可能快移除了, 既然要移除了,那就代表有新的方法,記得還要提供別人現在都是用什麼方法

※@FuntionalInterface(1.8功能型介面)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}

1.8 的新同學,在interface裡以前只能定義抽象,1.8可以用static實作方法,還可以用default的東東寫預設值,其他方法當然和以前一模一樣; 但是,加上這個annotation,只能定義一個方法,少於一個或二個以上都不行,當然不包括default和static

※@Override(覆寫)

原始碼:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

要實作一個介面時,有可能打錯字或大小寫不一樣,那就不是Override了,這時可以加這個annotation來保證一定是override

※@SafeVarargs(安全的變數參數)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}

此為1.7才有的,官方舉的範例(我稍為修改過)如下:
public static void doSome(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList;
    String s = stringLists[0].get(0);
}

呼叫時,這樣使用:
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
doSome(list1, list2);

設計者的第3行,傳Integer後,第4行居然把它給array接收,但是編譯器不會發現,
所以到第5行就出 java.lang.Integer cannot be cast to java.lang.String  的錯了
根據這樣的問題,希望設計者能考慮這個問題,所以要設計者增加這個annotation,表示他真的有想過這個問題且已經修正了。
但這不是強制的,如果不修正,且加上這個annotation,執行時照樣會報錯

@SuppressWarnings(抑制警告)

原始碼:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

在1.5有泛型時,如果不給泛型,Eclipse會黃黃的,這不代表你很色,也不代表你拉屎完沒擦屁股,是代表你不指定泛型有可能在runtime時會出錯,所以叫你指定一下,至少在編譯時期就能發現這個錯。
如果你堅持不加泛型,又不想看到黃黃的,就加個@SuppressWarnings(value={"unchecked"})
那麼像unchecked還有什麼呢?我找了好久,找到個連結,這裡
我在網上發現有神人翻譯如下:
all:抑制所有警告
boxing:抑制與封裝/拆裝作業相關的警告
cast:抑制與強制轉型作業相關的警告
dep-ann:抑制與淘汰註釋相關的警告
deprecation:抑制與淘汰的相關警告
fallthrough:抑制與 switch 陳述式中遺漏 break 相關的警告
finally:抑制與未傳回 finally 區塊相關的警告
hiding:抑制與隱藏變數的區域變數相關的警告
incomplete-switch:抑制與 switch 陳述式 (enum case) 中遺漏項目相關的警告
javadoc:抑制與 javadoc 相關的警告
nls:抑制與非 nls 字串文字相關的警告
null:抑制與空值分析相關的警告
rawtypes:抑制與使用 raw 類型相關的警告
resource:抑制與使用 Closeable 類型的資源相關的警告
restriction:抑制與使用不建議或禁止參照相關的警告
serial:抑制與可序列化的類別遺漏 serialVersionUID 欄位相關的警告
static-access:抑制與靜態存取不正確相關的警告
static-method:抑制與可能宣告為 static 的方法相關的警告
super:抑制與置換方法相關但不含 super 呼叫的警告
synthetic-access:抑制與內部類別的存取未最佳化相關的警告
sync-override:抑制因為置換同步方法而遺漏同步化的警告
unchecked:抑制與未檢查的作業相關的警告
unqualified-field-access:抑制與欄位存取不合格相關的警告
unused:抑制與未用的程式碼及停用的程式碼相關的警告

在寫Hibernate時,常常會用到這一段
Query query = session.createQuery("HQL語法");
List<Class名稱> list = query.list();//這行要我unchecked抑制警告

我想說是不是認為query.list()有可能會null,加個if判斷; 還有網路上說可以用
List<Class名稱> list2 = Collections.checkedList(query.list(), Class名稱.class);

結果還是要unchecked抑制警告,反而白忙一場。
有人是這樣解決的,因為Hibernate有一個HibernateUtil,所以他在裡面加個static泛型方法,如下:
public static <T> List<T> list(Query q) {
    @SuppressWarnings("unchecked")//在這裡抑制警告
    List<T> list = q.list();
    return list;
}

使用時,只要像這下面這樣就可以不用抑制警告了
List<class名稱> list = HibernateUtil.list(query);

還可以這樣
List<?> list = query.list();
for(Object o:list){
    ClassName cl = (ClassName) o;
    System.out.println(cl.getEmpno());
    System.out.println(cl.getJob());
}

-------------------------------------------------------------------------------------------------------------
以下介紹 java.lang.annotation 的 Annotation Types

※@Documented(做java文件)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

定義一個Chess的annotation,把它定位在 只能放在方法和TYPE上
@Documented
@Target(value={ElementType.METHOD, ElementType.TYPE})
public @interface Chess {
    public String key();
    public int value() default 32;
}

使用@Chess,和寫一些有的沒的
@Chess(key = "棋", value = 999)
public class Demo {
    /**
    * 哈囉!註解
    * 
    * @return
    */
    @Chess(key = "棋的資訊", value = 881)
    public String getInfo() {
    return "Hello Annotation";
    }
}

在dos裡打上藍色的部分
D:\>cd workspace\Test\src
D:\workspace\Test\src>javadoc -d xxx Demo2.java
Loading source files for package Demo2.java...
javadoc: warning - No source files for package Demo2.java
Constructing Javadoc information...
javadoc: warning - No source files for package Demo2.java
javadoc: error - No public or protected classes found to document.
1 error
2 warnings

D:\workspace\Test\src>javadoc -d xxx Demo.java
Loading source file Demo.java...
Constructing Javadoc information...
Standard Doclet version 1.7.0_60
Building tree for all the packages and classes...
Generating doc\Demo.html...
Demo.java:9: warning - @return tag has no arguments.
Generating doc\package-frame.html...
Generating doc\package-summary.html...
Generating doc\package-tree.html...
Generating doc\constant-values.html...
Building index for all the packages and classes...
Generating doc\overview-tree.html...
Generating doc\index-all.html...
Generating doc\deprecated-list.html...
Building index for all classes...
Generating doc\allclasses-frame.html...
Generating doc\allclasses-noframe.html...
Generating doc\index.html...
Generating doc\help-doc.html...
1 warning
-d xxx 是目錄名稱,也可以不指定,但我生成時檔案加資料夾有16個,會和其他檔案搞混,所以做個資料夾,注意紅色的錯誤,需要有public或protected的class,因為我本來寫在同一個.java裡,但一個.java,只能有一個public,使用protected,也會報「Illegal modifier for the class AA; only public, abstract & final are permitted」的錯,所以只好寫一支新的.java,就可以使用public了。
點index.html就可以看到文件了,中文有些是用\uxxxx的,看起來像是國際化的東東,但是有些又能正常顯示,很詭異

※@Inherited(annotation是否可被繼承)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

先定義一個annotation,記得要用runtime才可以,不然保存在.java和.class沒什麼用
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Chess {
    public String key();
    public String value();
    public int ooo();
}

然後定義父類和子類,子類沒有用annotation
@Chess(key = "keykey", ooo = 10, value = "valval")
class Papa {}
class Son extends Papa{}

測試看看能不能得到父類的annotation
Class cls = Class.forName("Son");
for(Annotation anno:cls.getAnnotations()){
    System.out.println(anno);
}

if(cls.isAnnotationPresent(Chess.class)){
    Chess chess =cls.getAnnotation(Chess.class);
    //以下三行為.annotation的方法名稱
    System.out.println(chess.key());
    System.out.println(chess.value());
    System.out.println(chess.ooo());
}

結果:
@Chess(key=keykey, ooo=10, value=valval)
keykey
valval
10

※@Native

原始碼:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

1.8的新同學,這個我也看沒有,不過反正一定是和C++有關的東西!

※@Repeatable(重覆宣告annotation)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

這也是1.8的新同學,讓annotation可以重覆宣告,例如以前一定要這樣寫
@SuppressWarnings({"unchecked", "unused"})
加上這個annotation就可以這樣用
@Repeatable
@SuppressWarnings("unchecked")
@SuppressWarnings("unused")
也就是有不同的撰寫風格啦

※@Retention(保存範圍)

原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

@Target有一個@Retention,裡面要放RetentionPolicy,總共分成三種範圍,如下:
                         .java        .class        JVM
SOURCE          ✔
CLASS              ✔             ✔
RUNTIME       ✔             ✔              ✔

CLASS為預設,像@Target是RetentionPolicy.RUNTIME,所以會保存在.java、.class、在執行時也會加載到JVM中

※@Target(限制annotation放在哪)

以上的例子,放在方法、類別…等都可以,如果要限制只能放在enum上呢?
原始碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

value裡面要放 ElementType,API 有詳細的介紹,且英文單字不難,但它的連結好像沒做書籤,所以我把它複製並翻譯過來:
ANNOTATION_TYPE   宣告在annotation
CONSTRUCTOR            宣告在建構子
FIELD                              宣告在屬性(包括Enum)
LOCAL_VARIABLE      宣告在區域變數
METHOD                        宣告在方法
PACKAGE                      宣告在package
PARAMETER                 宣告在參數
TYPE                               宣告在類別、介面(包括annotation)、enum
以下兩個為1.8新增的
TYPE_PARAMETER     宣告在TYPE的角括號(<>),(Type parameter declaration)
TYPE_USE                      宣告在各式型態(Use of a type)

所以ElementType.ANNOTATION_TYPE只能宣告在annotation裡; enum可以用ElementType.Field 或 ElementType.Type

我親自試過後,
ANNOTATION_TYPE:宣告在annotation,包括目前自己定義的annotation和annotation裡的方法,但其他的annotation裡的方法不行,如下:
@ABC
@Target(ElementType.ANNOTATION_TYPE)
@interface ABC {
    @ABC//如果這@interface不是ABC就不行
    public String value() default "";
}

CONSTRUCTOR:除了建構子,還包括自己的annotation和annotation裡的方法,如上面的例子,都可以
FIELD:除了全域變數,還包括自己的annotation和annotation裡的方法,API說包括enum,其實指得是enum裡的field
LOCAL_VARIABLE:除了區域變數,還包括自己的annotation和annotation裡的方法
METHOD:除了方法,還包括自己的annotation和annotation裡的方法,還有其他annotation的方法都OK
PACKAGE:直接定義會出現「Package annotations must be in file package-info.java」的錯,所以我只好新增一個叫package-info.java的檔,打開以後是空的,有錯誤提示「The declared package "" does not match the expected package "xxx"」,所以我就宣告個package xxx,然後在前面用annotation就可以了,原來的檔不能定義。
當然還包括自己的annotation和annotation裡的方法
PARAMETER:宣告在()裡,還包括自己的annotation和annotation裡的方法
TYPE:宣告在class、interface、annotation、enum,abstract算class的一種,當然還包括自己的annotation和annotation裡的方法
TYPE_PARAMETER:宣告在泛型類別和泛型方法,還有自己的annotation。注意自己的annotation方法不行,還有List、Set、Map裡的<>也不行
TYPE_USE:只有一個不行java.lang.String不行(不是String不行,只要是寫完整路徑就不行,很奇怪; 我只寫String是OK的,滑鼠移上去,也真的是java.lang.String,但就是一個可以,一個不行),是完整路徑不行喔!譬如java.util.Map不行,但import在上面,只寫Map卻可以
@Target都不寫:TYPE_PARAMETER不行以外,其他都可以
全部都可以:要這樣設@Target({ ElementType.TYPE_USE, ElementType.FIELD, ElementType.LOCAL_VARIABLE }),但兩個以上不知為什麼,Eclipse沒提示

P.S. 自己定義的@interface一直都可以,只有TYPE_PARAMETER,自己的方法不行
List、Set、Map,<>裡定義annotation,只有TYPE_USE可以,連不寫@Target都不行
所以依照這樣的關係,我整理了一張表(我測的是1.8.0_25):

※使用annotation並做事


先寫一支annotation,我加個繼承
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Chess {
    public String key() default "我是key";
    public String value();
    public int ooo() default 32;
}

然後有一個爸爸類,類和方法都有加annotation,爸爸有兩個兒子繼承,第一個兒子覆寫爸爸的方法,第二個兒子什麼都沒有,而且兒子都沒有用@Chess
@Chess(key = "keykey", ooo = 10, value = "valval")
class Papa {
    @Chess("我是value")
    public String getInfo() {
        return "Papa的getInfo()";
    }
}

class FirstSon extends Papa {
    @Override
    public String getInfo() {
        return "FirstSon的getInfo()";
    }
}

class SecondSon extends Papa {
}

寫個測試類來測一下:
System.out.println("----------------Papa-------------------");
Class<?> papaClass = Class.forName("Papa");

System.out.println("從class取得annotation,然後取得一個一個屬性");
Chess papaChess = papaClass.getAnnotation(Chess.class);
System.out.println(papaChess.ooo());
System.out.println(papaChess.key());
System.out.println(papaChess.value());

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : papaClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess papaChessMethod = papaClass.getMethod("getInfo").getAnnotation(
        Chess.class);
System.out.println(papaChessMethod.ooo());
System.out.println(papaChessMethod.key());
System.out.println(papaChessMethod.value());

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : papaClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

System.out.println("----------------SecondSon--------------");
Class<?> secondClass = Class.forName("SecondSon");

Chess secondChess = secondClass.getAnnotation(Chess.class);
System.out.println("從class取得annotation,然後取得一個一個屬性");
if (secondChess != null) {
    System.out.println(secondChess.ooo());
    System.out.println(secondChess.key());
    System.out.println(secondChess.value());
}

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : secondClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess secondChessMethod = secondClass.getMethod("getInfo")
        .getAnnotation(Chess.class);
if (secondChessMethod != null) {
    System.out.println(secondChessMethod.ooo());
    System.out.println(secondChessMethod.key());
    System.out.println(secondChessMethod.value());
}

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : secondClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

System.out.println("----------------FirstSon---------------");
Class<?> firstClass = Class.forName("FirstSon");

Chess firstChess = firstClass.getAnnotation(Chess.class);
System.out.println("從class取得annotation,然後取得一個一個屬性");
if (firstChess != null) {
    System.out.println(firstChess.ooo());
    System.out.println(firstChess.key());
    System.out.println(firstChess.value());
}

System.out.println("從class取得annotations,然後取得全部屬性");
for (Annotation ann : firstClass.getAnnotations()) {
    System.out.println(ann);
}

System.out.println("從方法取得annotation,然後取得一個一個屬性");
Chess firstChessMethod = firstClass.getMethod("getInfo").getAnnotation(
        Chess.class);
if (firstChessMethod != null) {
    System.out.println(firstChessMethod.ooo());
    System.out.println(firstChessMethod.key());
    System.out.println(firstChessMethod.value());
}

System.out.println("從方法取得annotation,然後取得全部屬性");
for (Annotation ann : firstClass.getMethod("getInfo").getAnnotations()) {
    System.out.println(ann);
}

結果:
----------------Papa-------------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------SecondSon--------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------FirstSon---------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
從方法取得annotation,然後取得全部屬性


P.S. 注意第一個兒子他覆寫爸爸的方法,所以通過方法取不到annotation; 而第二個兒子都取得到

如果把@Inherited拿掉的結果如下:
----------------Papa-------------------
從class取得annotation,然後取得一個一個屬性
10
keykey
valval
從class取得annotations,然後取得全部屬性
@Chess(ooo=10, key=keykey, value=valval)
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------SecondSon--------------
從class取得annotation,然後取得一個一個屬性
從class取得annotations,然後取得全部屬性
從方法取得annotation,然後取得一個一個屬性
32
我是key
我是value
從方法取得annotation,然後取得全部屬性
@Chess(ooo=32, key=我是key, value=我是value)
----------------FirstSon---------------
從class取得annotation,然後取得一個一個屬性
從class取得annotations,然後取得全部屬性
從方法取得annotation,然後取得一個一個屬性
從方法取得annotation,然後取得全部屬性

P.S. 第一個兒子什麼都取不到了,第二個兒子通過方法,還是可以取得老爸的annotation
所以我試的結果,@Inherited只有對類別有用的樣子


※java 9 新增 MODULE

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.MODULE)
public @interface Uuu {
    String value();
}

※新增一個 Uuu 使用在 module 上

@Uuu("")
module Xxx {
    ...
}

可以在 module-info 使用

沒有留言:

張貼留言