來源:codeceo
www.codeceo.com/5-annotations-every-java-developer-should-know.htmlhtml
劃重點java
自 JDK5 推出以來,註解已成爲Java生態系統不可缺乏的一部分。雖然開發者爲Java框架(例如Spring的@Autowired)開發了無數的自定義註解,但編譯器承認的一些註解很是重要。面試
在本文中,咱們將看到5個Java編譯器支持的註解,並瞭解其指望用途。順便,咱們將探索其建立背後的基本原理,圍繞其用途的一些特質,以及正確應用的一些例子。雖然其中有些註解比其餘註解更爲常見,但非初學Java開發人員都應該消化了解每一個註解。後端
@Override數組
@FunctionalInterface安全
@SuppressWarnings多線程
@SafeVarargs架構
@Deprecated框架
首先,咱們將深刻研究Java中最經常使用的註解之一:@Override。ide
覆蓋方法的實現或爲抽象方法提供實現的能力是任何面向對象(OO)語言的核心。因爲Java是OO語言,具備許多常見的面向對象的抽象機制,因此在非終極超類定義的非最終方法或接口中的任何方法(接口方法不能是最終的)均可以被子類覆蓋。點擊這裏閱讀 Java 10 新特性實戰教程。
雖然開始時覆蓋方法看起來很簡單,可是若是執行不正確,則可能會引入許多微小的bug。例如,用覆蓋類類型的單個參數覆蓋Object#equals方法就是一種常見的錯誤:
public class Foo { public boolean equals(Foo foo) { // Check if the supplied object is equal to this object } }
因爲全部類都隱式地從Object類繼承,Foo類的目的是覆蓋Object#equals方法,所以Foo可被測試是否與Java中的任何其餘對象相等。雖然咱們的意圖是正確的,但咱們的實現則並不是如此。
實際上,咱們的實現根本不覆蓋Object#equals方法。相反,咱們提供了方法的重載:咱們不是替換Object類提供的equals方法的實現,而是提供第二個方法來專門接受Foo對象,而不是Object對象。
咱們的錯誤能夠用簡單實現來舉例說明,該實現對全部的相等檢查都返回true,但當提供的對象被視爲Object(Java將執行的操做,例如在Java Collections Framework即JCF中)時,就永遠不會調用它:
public class Foo { public boolean equals(Foo foo) { return true; } } Object foo = new Foo(); Object identicalFoo = new Foo(); System.out.println(foo.equals(identicalFoo)); // false
這是一個很是微妙但常見的錯誤,能夠被編譯器捕獲。咱們的意圖是覆蓋Object#equals方法,但由於咱們指定了一個類型爲Foo而不是Object類型的參數,因此咱們實際上提供了重載的Object#equals方法,而不是覆蓋它。爲了捕獲這種錯誤,咱們引入@Override註解,它指示編譯器檢查覆蓋實際有沒有執行。若是沒有執行有效的覆蓋,則會拋出錯誤。所以,咱們能夠更新Foo類,以下所示:
public class Foo { @Override public boolean equals(Foo foo) { return true; } }
若是咱們嘗試編譯這個類,咱們如今收到如下錯誤:
$ javac Foo.java Foo.java:3: error: method does not override or implement a method from a supertype @Override ^1 error
實質上,咱們已經將咱們已經覆蓋方法的這一隱含的假設轉變爲由編譯器進行的顯性驗證。若是咱們的意圖被錯誤地實現,那麼Java編譯器會發出一個錯誤——不容許咱們不正確實現的代碼被成功編譯。一般,若是如下任一條件不知足,則Java編譯器將針對使用@Override註解的方法發出錯誤(引用自Override註解文檔):
該方法確實會覆蓋或實如今超類中聲明的方法。
該方法的簽名與在Object中聲明的任何公共方法(即equals或hashCode方法)的簽名覆蓋等價(override-equivalent)。
所以,咱們也可使用此註解來確保子類方法實際上也覆蓋超類中的非最終具體方法或抽象方法:
public abstract class Foo { public int doSomething() { return 1; } public abstract int doSomethingElse(); } public class Bar extends Foo { @Override public int doSomething() { return 10; } @Override public int doSomethingElse() { return 20; } } Foo bar = new Bar(); System.out.println(bar.doSomething()); // 10 System.out.println(bar.doSomethingElse()); // 20
@Override註解不只不限於超類中的具體或抽象方法,並且還可用於確保接口的方法也被覆蓋(從JDK 6開始):
public interface Foo { public int doSomething(); } public class Bar implements Foo { @Override public int doSomething() { return 10; } } Foo bar = new Bar(); System.out.println(bar.doSomething()); // 10
一般,覆蓋非final類方法、抽象超類方法或接口方法的任何方法均可以使用@Override進行註解。有關有效覆蓋的更多信息,請參閱《Overriding and Hiding》文檔 以及《Java Language Specification (JLS)》的第9.6.4.4章節。
隨着JDK 8中lambda表達式的引入,函數式接口在Java中變得愈來愈流行。這些特殊類型的接口能夠用lambda表達式、方法引用或構造函數引用代替。根據@FunctionalInterface文檔,函數式接口的定義以下:
一個函數式接口只有一個抽象方法。因爲默認方法有一個實現,因此它們不是抽象的。
例如,如下接口被視爲函數式接口:
public interface Foo { public int doSomething(); } public interface Bar { public int doSomething(); public default int doSomethingElse() { return 1; } }
所以,下面的每個均可以用lambda表達式代替,以下所示:
public class FunctionalConsumer { public void consumeFoo(Foo foo) { System.out.println(foo.doSomething()); } public void consumeBar(Bar bar) { System.out.println(bar.doSomething()); } } FunctionalConsumer consumer = new FunctionalConsumer(); consumer.consumeFoo(() -> 10); // 10 consumer.consumeBar(() -> 20); // 20
重點要注意的是,抽象類,即便它們只包含一個抽象方法,也不是函數式接口。更多信息,請參閱首席Java語言架構師Brian Goetz編寫的《Allow lambdas to implement abstract classes》。與@Override註解相似,Java編譯器提供了@FunctionalInterface註解以確保接口確實是函數式接口。例如,咱們能夠將此註解添加到上面建立的接口中:
@FunctionalInterface public interface Foo { public int doSomething(); } @Functional Interfacepublic interface Bar { public int doSomething(); public default int doSomethingElse() { return 1; } }
若是咱們錯誤地將接口定義爲非函數接口並用@FunctionalInterface註解了錯誤的接口,則Java編譯器會發出錯誤。例如,咱們能夠定義如下帶註解的非函數式接口:
@FunctionalInterface public interface Foo { public int doSomething(); public int doSomethingElse(); }
若是咱們試圖編譯這個接口,則會收到如下錯誤:
$ javac Foo.java Foo.java:1: error: Unexpected @FunctionalInterface annotation @FunctionalInterface ^ Foo is not a functional interface multiple non-overriding abstract methods found in interface Foo1 error
使用這個註解,咱們能夠確保咱們不會錯誤地建立本來打算用做函數式接口的非函數式接口。須要注意的是,即便在@FunctionalInterface註解不存在的狀況下,接口也能夠用做函數式接口(能夠替代爲lambdas,方法引用和構造函數引用),正如咱們前面的示例中所見的那樣。這相似於@Override註解,即一個方法是能夠被覆蓋的,即便它不包含@Override註解。在這兩種狀況下,註解都是容許編譯器執行指望意圖的可選技術。
有關@FunctionalInterface註解的更多信息,請參閱@FunctionalInterface文檔和《JLS》的第4.6.4.9章節。點擊這裏閱讀 Java 10 新特性實戰教程。
警告是全部編譯器的重要組成部分,爲開發人員提供的反饋——可能危險的行爲或在將來的編譯器版本中可能會出現的錯誤。例如,在Java中使用泛型類型而沒有其關聯的正式泛型參數(稱爲原始類型)會致使警告,就像使用不推薦使用的代碼同樣(請參閱下面的@Deprecated部分)。雖然這些警告很重要,但它們可能並不老是適用甚至並不老是正確的。例如,可能會有對不安全的類型轉換髮生警告的狀況,可是基於使用它的上下文,咱們能夠保證它是安全的。
爲了忽略某些上下文中的特定警告,JDK 5中引入了@SuppressWarnings註解。此註解接受一個或多個字符串參數——描述要忽略的警告名稱。雖然這些警告的名稱一般在編譯器實現之間有所不一樣,但有3種警告在Java語言中是標準化的(所以在全部Java編譯器實現中都很常見):
unchecked:表示類型轉換未經檢查的警告(編譯器沒法保證類型轉換是安全的),致使發生的可能緣由有訪問原始類型的成員(參見《JLS》4.8章節)、窄參考轉換或不安全的向下轉換(參見《JLS》5.1.6章節)、未經檢查的類型轉換(參見《JLS》5.1.9章節)、使用帶有可變參數的泛型參數(參見《JLS》8.4.1章節和下面的@SafeVarargs部分)、使用無效的協變返回類型(參見《JLS》8.4.8.3章節)、不肯定的參數評估(參見《JLS》15.12.4.2章節),未經檢查的方法引用類型的轉換(參見《JLS》15.13.2章節)、或未經檢查的lambda類型的對話(參見《JLS》15.27.3章節)。
deprecation:表示使用了已棄用的方法、類、類型等的警告(參見《JLS》9.6.4.6章節和下面的@Deprecated部分)。
removal:表示使用了最終廢棄的方法、類、類型等的警告(參見《JLS》9.6.4.6章節和下面的@Deprecated部分)。
爲了忽略特定的警告,能夠將@SuppressedWarning註解與抑制警告(以字符串數組的形式提供)的一個或多個名字添加到發生警告的上下文中:
public class Foo { public void doSomething(@SuppressWarnings("rawtypes") List myList) { // Do something with myList } }
@SuppressWarnings註解可用於如下任何一種狀況:
類型
域
方法
參數
構造函數
局部變量
模塊
通常來講,@SuppressWarnings註解應該應用於最直接的警告範圍。例如,若是方法中的局部變量應忽略警告,則應將@SuppressWarnings註解應用於局部變量,而不是包含局部變量的方法或類:
public class Foo { public void doSomething() { @SuppressWarnings("rawtypes") List myList = new ArrayList(); // Do something with myList } }
可變參數在Java中是一種頗有用的技術手段,但在與泛型參數一塊兒使用時,它們也可能會致使一些嚴重的問題。因爲泛型在Java中是非特定的,因此具備泛型類型的變量的實際(實現)類型不能在運行時被判定。因爲沒法作出此判斷,所以變量可能會存儲非其實際類型的引用到類型,如如下代碼片斷所示(摘自《Java Generics FAQs》):
List ln = new ArrayList<Number>(); ln.add(1); List<String> ls = ln; // unchecked warning String s = ls.get(0); // ClassCastException
在將ln分配給ls後,堆中存在變量ls,該變量具備List
public class Foo { public <T> void doSomething(T... args) { // ... } }
在這種狀況下,Java編譯器會在調用站點內部建立一個數組來存儲可變數量的參數,可是T的類型並未實現,所以在運行時會丟失。實質上,到doSomething的參數其實是Object[]類型。若是依賴T的運行時類型,那麼這會致使嚴重的問題,以下面的代碼片斷所示:
public class Foo { public <T> void doSomething(T... args) { Object\[\] objects = args; String string = (String) objects\[0\]; } } Foo foo = new Foo(); foo.<Number>doSomething(1, 2);
若是執行此代碼片斷,那麼將致使ClassCastException,由於在調用站點傳遞的第一個Number參數不能轉換爲String(相似於獨立堆污染示例中拋出的ClassCastException)。一般,可能會出現如下狀況:編譯器沒有足夠的信息來正確肯定通用可變參數的確切類型,這會致使堆污染,這種污染能夠經過容許內部可變參數數組從方法中轉義來傳播,以下面摘自《Effective Java》第3版 pp.147的例子:
public static <T> T\[\] toArray(T... args) { return args; }
在某些狀況下,咱們知道方法其實是類型安全的,不會形成堆污染。若是能夠在保證的狀況下作出這個決定,那麼咱們可使用@SafeVarargs註解來註解該方法,從而抑制與可能的堆污染相關的警告。可是,這引出了一個問題:何時通用可變參數方法會被認爲是類型安全的?Josh Bloch在《Effective Java》第3版第147頁的基礎上提供了一個完善的解決方案——基於方法與內部建立的用於存儲其可變參數的數組的交互:
若是方法沒有存儲任何東西到數組(這會覆蓋參數)且不容許對數組的引用進行轉義(這會使得不受信任的代碼能夠訪問數組),那麼它是安全的。換句話說,若是可變參數數組僅用於從調用者向方法傳遞可變數量的參數——畢竟,這是可變參數的目的——那麼該方法是安全的。
所以,若是咱們建立了如下方法(來自pp.149同上),那麼咱們能夠用@SafeVarags註解來合理地註解咱們的方法:
@SafeVarargsstatic <T> List<T> flatten(List<? extends T>... lists) { List<T> result = new ArrayList<>(); for (List<? extends T> list : lists) { result.addAll(list); } return result; }
有關@SafeVarargs註解的更多信息,請參閱@SafeVarargs文檔,《JLS》9.6.4.7章節以及《Effective Java》第3版中的Item32。點擊這裏閱讀 Java 10 新特性實戰教程。
在開發代碼時,有時候代碼會變得過期和不該該再被使用。在這些狀況下,一般會有個替補的更適合手頭的任務,且雖然現存的對過期代碼的調用可能會保留,可是全部新的調用都應該使用替換方法。這個過期的代碼被稱爲不推薦使用的代碼。在某些緊急狀況下,不建議使用的代碼可能會被刪除,應該在將來的框架或庫版本從其代碼庫中刪除棄用的代碼以前當即轉換爲替換代碼。
爲了支持不推薦使用的代碼的文檔,Java包含@Deprecated註解,它會將一些構造函數、域、局部變量、方法、軟件包、模塊、參數或類型標記爲已棄用。若是棄用的元素(構造函數,域,局部變量等)被使用了,則編譯器發出警告。例如,咱們能夠建立一個棄用的類並按以下所示使用它:
@Deprecatedpublic class Foo {} Foo foo = new Foo();
若是咱們編譯此代碼(在命名爲Main.java的文件中),咱們會收到如下警告:
$ javac Main.javaNote: Main.java uses or overrides a deprecated API.Note: Recompile with -Xlint:deprecation for details.
一般,每當使用@Deprecated註解的元素時,都會引起警告,除了用於如下五種狀況:
聲明自己就被聲明爲是棄用的(即遞歸調用)。
聲明被註解禁止棄用警告(即@SuppressWarnings(「deprecation」)註解,如上所述,應用於使用棄用元素的上下文。
使用和聲明都在同一個最外面的類中(即,若是類調用其自己的棄用方法)。
用在import聲明中,該聲明導入一般不同意使用的類型或構件(即,在將已棄用的類導入另外一個類時)。
exports或opens指令內。
正如前面所說的,在某些狀況下,當不推薦使用的元素將被刪除,則調用代碼應當即刪除不推薦使用的元素(稱爲terminally deprecated code)。在這種狀況下,可使用forRemoval參數提供的@Deprecated註解,以下所示:
@Deprecated(forRemoval = true)public class Foo {}
使用此最終棄用代碼會致使一系列更嚴格的警告:
$ javac Main.java Main.java:7: warning: \[removal\] Foo in com.foo has been deprecated and marked for removal Foo foo = new Foo(); ^ Main.java:7: warning: \[removal\] Foo in com.foo has been deprecated and marked for removal Foo foo = new Foo(); ^2 warnings
除了標準@Deprcated註解所描述的相同異常以外,老是會發出最終棄用的警告。咱們還能夠經過爲註解提供since變量來添加文檔到@Deprecated註解中:
@Deprecated(since = "1.0.5", forRemoval = true)public class Foo {}
可使用@deprecated JavaDoc元素(注意小寫字母d)進一步文檔化已棄用的元素,如如下代碼片斷所示:
/** * Some test class. * * @deprecated Replaced by {@link com.foo.NewerFoo}. * * @author Justin Albano */@Deprecated(since = "1.0.5", forRemoval = true)public class Foo {}
JavaDoc工具將生成如下文檔:
有關@Deprecated註解的更多信息,請參閱@Deprecated文檔和《JLS》9.6.4.6章節。
自JDK 5引入註解以來,註解一直是Java不可缺乏的一部分。雖然有些註解比其餘註解更受歡迎,但本文中介紹的這5種註解是新手級別以上的開發人員都應該理解和掌握的:
@Override
@FunctionalInterface
@SuppressWarnings
@SafeVarargs
@Deprecated
雖然每種方法都有其獨特的用途,但全部這些註解使得Java應用程序更具可讀性,並容許編譯器對咱們的代碼執行一些其餘隱含的假設。隨着Java語言的不斷髮展,這些通過實踐驗證的註解可能服務多年,幫助確保更多的應用程序按開發人員的意圖行事。
推薦去個人博客閱讀更多:
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
以爲不錯,別忘了點贊+轉發哦!