常用標準標註


Java提供了一些標準標註,先前經常看到的@Override就是標準標註,它在原始碼中提供編譯器的訊息是,被標註的方法必須是父類別或介面中已定義的方法,請編譯器協助是否真的為重新定義方法。例如,在重新定義Threadrun()方法時:

public class WorkerThread extends Thread {
    public void Run() {
        //...
    }
}

這個程式範例中,本來重新定義run()方法,結果打成了Run()方法,這變成在WorkerThread中定義新方法。為了避免這類錯誤,可以加上@Override,編譯器看到@Override這個標註,瞭解必須檢查父類別中是否存在Run()方法,但父類別實際上並沒有這個方法,所以會回報錯誤:

@Override要求編譯器檢查是否為重新定義


如果某個方法原先存在於API中,後來不建議再使用,可以於該方法上標註@Deprecated。例如:

public class Some {
    @Deprecated
    public void doSome() {
        ...
    }
}

編譯後的.class會儲存這個資訊,若有使用者後續又想呼叫或重新定義這個方法,編譯器會提出警訊(IDE通常會在方法加上刪除線表示):

Note: XXX.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

在JDK5之後加入泛型支援,對於支援泛型的API,建議明確指定泛型真正型態,如果沒有指定的話,編譯器會提出警訊。例如程式碼若含有以下片段:

public void doSome() {
     List list = new ArrayList();
     list.add("Some");
}

由於ListArrayList支援泛型,但這邊沒有指定泛型真正型態,編譯時會出現以下訊息:

Note: xxx.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

如果不想看到這個警訊,可以使用@SuppressWarnings指定抑制unckecked的警訊產生:

@SuppressWarnings(value={"unchecked"})
public void doSome() {
     List list = new ArrayList();
     list.add("Some");
}

@SuppressWarningsvalue可以指定要抑制的警訊種類。例如你真的想呼叫@Deprecated標示過的方法,又不想看到警訊,可以如下:

@SuppressWarnings(value={"deprecation"})

也可以一次指定抑制多項警訊:

@SuppressWarnings(value={"unchecked", "deprecation"})

JDK7新增了@SafeVarargs標註,這得先談談一個問題,你有沒有可能建立List<String>[]陣列實例?答案是不行,無論是new List<String>[10]List<String>[] lists = {new ArrayList<String>()}的語法,編譯器都會直接給你一個error: generic array creation的錯誤訊息,然而宣告List<String>[] lists是可以的,只是實際上不會有人這麼做,可以宣告List<String>[] lists是為了支援可變長度引數,例如你可能這麼宣告:

public class Util {
    public static <T> void doSome(List<String>... varargs) {
        ...略
    }
}

程式碼中使用了泛型宣告可變長度引數,在JDK6中這個程式碼可以順利編譯,也不會有任何警訊,而varargs實際上就是List<String>[]型態,但如果你這麼使用:

List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
Util.doSome(list1, list2);

編譯器會於呼叫doSome()時發出警示訊息:

Main.java:7: warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
        Util.doSome(list1, list2);
                   ^
1 warning

Java泛型語法是提供編譯器資訊,使其可在編譯時期檢查型態錯誤,編譯器只能List<String>的型態資訊,在編譯時期幫你檢查呼叫doSome()時,傳入的list1list2是否為List<String>型態,然而設計doSome()的人在實作流程時,是有可能發生編譯器無法檢查出來的執行時期型態錯誤。例如SafeVarargs的API文件中就有個範例:

    public static <T> void doSome(List<String>... varargs) {
        Object[] array = varargs;
        List<Integer> tmpList = Arrays.asList(42);
        array[0] = tmpList; // 語意不對,不過編譯器不會有警訊
        String s = varargs[0].get(0); // 執行時期發生 ClassCastException
    }


這類問題稱為heap pollution,也就是編譯器無法檢查出一些執行時期的型態錯誤,問題是即使編譯器提醒身為doSome()的客戶端可能會有這類問題發生又如何?問開發doSome()的人會不會發生問題?自己看doSome()的原始碼?最好的方式,就是對開發doSome()的人加以提醒,而不是讓使用doSome()的人提心吊膽,因此在JDK7中,同樣的Util類別編譯時,會發生以下警訊:

Util.java:3: warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
    public static <T> void doSome(List<String>... varargs) {
                                                                            ^
1 warning

在使用泛型定義不定長度引數時,編譯器會提示開發人員,有沒有注意到heap pollution問題,如果開發人員確定避免了這個問題,則可以使用@SafeVarargs加以標註。例如:

public class Util {
    @SafeVarargs
    public static <T> void doSome(List<String>... varargs) {
        ...略
    }
}
   
加上@SafeVarargs後,編譯Util類別就不會發生警訊,由於開發人員要在確定避免了heap pollution的情況下,才會加上@SafeVarargs,呼叫Util.doSome()的使用者就不用提心吊膽,也就是如下呼叫Util.doSome()就不會再發生警訊。

List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
Util.doSome(list1, list2);

雖然在JDK7中,也可以如下抑制警訊:

public class Util {
    @SuppressWarnings(value={"unchecked"})
    public static <T> void doSome(List<String>... varargs) {
        ...略
    }
}

不過這不僅抑制了使用泛型宣告不定長度引數的警訊,連同其它unchecked警訊也會一併抑制,所以並不鼓勵用這個方式解決使用泛型宣告不定長度引數的警訊。

JDK8為了支援Lambda,也提出了一個@FunctionalInterface標註,讓編譯器可協助檢查interface是否可做為Lambda的目標型態,這在介紹Lambda時還會詳細介紹。