仿制药的美丽与陌生
最近,我正在為Oracle認(rèn)證專家Java SE 7程序員考試做準(zhǔn)備,而我恰巧在Java泛型領(lǐng)域遇到了一些看起來(lái)很奇怪的結(jié)構(gòu)。 但是,我也看到了一些巧妙而優(yōu)雅的代碼。 我發(fā)現(xiàn)這些示例值得分享,這不僅是因?yàn)樗鼈兛梢允鼓脑O(shè)計(jì)選擇更容易,并且使結(jié)果代碼更健壯和可重用,而且還因?yàn)槠渲幸恍┎涣?xí)慣泛型時(shí)非常棘手。 我決定將這篇文章分為四個(gè)章節(jié),這些章節(jié)幾乎反映了我在學(xué)習(xí)和工作經(jīng)歷中對(duì)仿制藥的經(jīng)驗(yàn)。
您了解泛型嗎?
當(dāng)我們環(huán)顧四周時(shí),我們可以看到泛型在Java Universe的許多不同框架中都大量使用。 它們從Web應(yīng)用程序框架到Java本身的集合。 由于這個(gè)話題已經(jīng)在我之前被很多人解釋過(guò)了,所以我只會(huì)列出我認(rèn)為有價(jià)值的資源,然后轉(zhuǎn)到有時(shí)根本沒(méi)有提及或解釋不充分的東西(通常在網(wǎng)上發(fā)布的注釋或文章中) 。 因此,如果您不了解核心泛型概念,則可以查看以下一些材料:
- Katherine Sierra和Bert Bates 授予SCJP Sun Java 6考試程序員認(rèn)證
- 對(duì)我而言,本書(shū)的主要目的是為參加Oracle提供的OCP考試做好準(zhǔn)備。
- 課程: Oracle的泛型(已更新)
- 資源由Oracle本身提供。
- Maurice Naftalin和Philip Wadler的Java泛型和集合
- O'Reilly Media生產(chǎn)的另一本很棒的Java書(shū)籍。
泛型不允許做什么?
假設(shè)您了解泛型并且想了解更多信息,那么請(qǐng)轉(zhuǎn)到無(wú)法完成的工作。 令人驚訝的是,有很多東西無(wú)法與泛型一起使用。 在使用泛型時(shí),我選擇了以下六個(gè)避免陷阱的示例。
類型為<T>靜態(tài)字段
許多沒(méi)有經(jīng)驗(yàn)的程序員經(jīng)常犯的一個(gè)錯(cuò)誤是試圖聲明靜態(tài)成員。 在下面的示例中可以看到,任何嘗試都會(huì)導(dǎo)致編譯器錯(cuò)誤,如下所示: Cannot make a static reference to the non-static type T 。
public class StaticMember<T> {// causes compiler errorstatic T member; }類型為<T>實(shí)例
另一個(gè)錯(cuò)誤是嘗試通過(guò)在泛型類型上調(diào)用new來(lái)實(shí)例化任何類型。 這樣,編譯器將導(dǎo)致錯(cuò)誤提示: Cannot instantiate the type T
public class GenericInstance<T> {public GenericInstance() {// causes compiler errornew T();} }與原始類型不兼容
使用泛型時(shí)最大的限制之一似乎是它們與原始類型不兼容。 的確,您不能在聲明中直接使用基元,但是,可以用適當(dāng)?shù)陌b器類型替換基元,然后就可以了。 整個(gè)情況在以下示例中呈現(xiàn):
public class Primitives<T> {public final List<T> list = new ArrayList<>();public static void main(String[] args) {final int i = 1;// causes compiler error// final Primitives<int> prim = new Primitives<>();final Primitives<Integer> prim = new Primitives<>();prim.list.add(i);} }在編譯期間, Primitives類的第一個(gè)實(shí)例化將失敗,并出現(xiàn)與以下錯(cuò)誤類似的錯(cuò)誤: Syntax error on token "int", Dimensions expected after this token 。 使用包裝器類型和少量自動(dòng)裝箱魔術(shù)可以繞過(guò)此限制。
<T>類型的數(shù)組
使用泛型的另一個(gè)明顯限制是無(wú)法實(shí)例化泛型類型的數(shù)組。 考慮到數(shù)組對(duì)象的基本特征,原因很明顯–它們?cè)谶\(yùn)行時(shí)保留其類型信息。 如果違反了它們的運(yùn)行時(shí)類型完整性,則運(yùn)行時(shí)異常ArrayStoreException可以挽救這一天。
public class GenericArray<T> {// this one is finepublic T[] notYetInstantiatedArray;// causes compiler errorpublic T[] array = new T[5]; }但是,如果嘗試直接實(shí)例化通用數(shù)組,則將出現(xiàn)如下編譯錯(cuò)誤: Cannot create a generic array of T 。
通用異常類
有時(shí),程序員可能需要傳遞泛型類型的實(shí)例以及引發(fā)異常。 在Java中這是不可能的。 下面的示例描述了這種努力。
// causes compiler error public class GenericException<T> extends Exception {}當(dāng)您嘗試創(chuàng)建此類異常時(shí),最終會(huì)收到類似以下消息: The generic class GenericException<T> may not subclass java.lang.Throwable 。
關(guān)鍵字super和extends替代含義
值得一提的最后一個(gè)限制,特別是對(duì)于新手來(lái)說(shuō),是泛型時(shí)關(guān)鍵字super和extends的替代含義。 為了生成利用泛型的精心設(shè)計(jì)的代碼,了解這一點(diǎn)非常有用。
- <? extends T>
- 含義:通配符是指擴(kuò)展類型T和類型T本身的任何類型。
- <? super T>
- 含義:通配符是指T的任何超級(jí)類型以及T類型本身。
一點(diǎn)點(diǎn)的美
關(guān)于Java我最喜歡的事情之一是它的強(qiáng)類型。 眾所周知,泛型是在Java 5中引入的,它們使我們更容易使用集合(它們不僅在集合中被用于更多領(lǐng)域,但這是設(shè)計(jì)階段泛型的核心論點(diǎn)之一) 。 即使泛型僅提供編譯時(shí)保護(hù),并且不輸入字節(jié)碼,但它們提供了相當(dāng)有效的方式來(lái)確保類型安全。 以下示例顯示了泛型的一些不錯(cuò)的功能或用例。
泛型適用于類和接口
這可能一點(diǎn)都不令人驚訝,但是是的-接口和泛型是兼容的構(gòu)造。 盡管將泛型與接口結(jié)合使用非常普遍,但我發(fā)現(xiàn)這一事實(shí)實(shí)際上是非常酷的功能。 這使程序員可以在考慮類型安全和代碼重用的情況下創(chuàng)建效率更高的代碼。 例如,考慮以下來(lái)自包java.lang接口Comparable示例:
public interface Comparable<T> {public int compareTo(T o); }泛型的簡(jiǎn)單介紹使得有可能從compareTo方法中省略check實(shí)例,從而使代碼更具凝聚力并提高了可讀性。 通常,泛型有助于使代碼更易于閱讀和理解,并有助于引入類型順序。
泛型允許邊界的優(yōu)雅使用
關(guān)于限制通配符,有一個(gè)很好的例子說(shuō)明了在庫(kù)類Collections可以實(shí)現(xiàn)的目標(biāo)。 此類聲明方法copy ,該方法在以下示例中定義,并使用有界通配符來(lái)確保列表的復(fù)制操作的類型安全。
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... }讓我們仔細(xì)看看。 方法copy被聲明為返回void的靜態(tài)泛型方法。 它接受兩個(gè)參數(shù)-目標(biāo)和源(并且兩者都是有界的)。 目標(biāo)必須存儲(chǔ)僅屬于T或T類型本身的超類型的類型。 另一方面,源必定僅由T類型的擴(kuò)展類型或T類型本身構(gòu)成。 這兩個(gè)約束保證了集合和復(fù)制操作都保持類型安全。 我們不需要使用數(shù)組,因?yàn)樗鼈兺ㄟ^(guò)拋出上述ArrayStoreException異常防止了任何類型安全沖突。
泛型支持多邊界
不難想象為什么人們會(huì)只使用一種簡(jiǎn)單的邊界條件而不是使用更多的條件。 實(shí)際上,這樣做很容易。 考慮下面的示例:我需要?jiǎng)?chuàng)建一個(gè)接受參數(shù)的方法,該參數(shù)既是Comparable也是數(shù)字List 。 開(kāi)發(fā)人員將被迫創(chuàng)建不必要的接口ComparableList,以便在通用時(shí)間內(nèi)履行上述合同。
public class BoundsTest {interface ComparableList extends List, Comparable {}class MyList implements ComparableList { ... }public static void doStuff(final ComparableList comparableList) {}public static void main(final String[] args) {BoundsTest.doStuff(new BoundsTest().new MyList());} }在執(zhí)行此任務(wù)之后,我們可以忽略這些限制。 使用泛型可以使我們創(chuàng)建滿足所需合同的具體類,但使doStuff方法盡可能開(kāi)放。 我發(fā)現(xiàn)的唯一缺點(diǎn)是語(yǔ)法過(guò)于冗長(zhǎng)。 但是由于它仍然保持很好的可讀性和易于理解性,因此我可以忽略此缺陷。
public class BoundsTest {class MyList<T> implements List<T>, Comparable<T> { ... }public static <T, U extends List<T> & Comparable<T>> void doStuff(final U comparableList) {}public static void main(final String[] args) {BoundsTest.doStuff(new BoundsTest().new MyList<String>());} }有點(diǎn)奇怪
我決定為這篇文章的最后一章專門介紹到目前為止我遇到的兩種最奇怪的構(gòu)造或行為。 您極有可能永遠(yuǎn)不會(huì)遇到這樣的代碼,但是我發(fā)現(xiàn)它很有趣,值得一提。 因此,事不宜遲,讓我們見(jiàn)識(shí)怪異的東西。
尷尬的代碼
與任何其他語(yǔ)言構(gòu)造一樣,您可能最終會(huì)遇到一些看起來(lái)很奇怪的代碼。 我想知道最奇怪的代碼是什么樣的,甚至是否可以通過(guò)編譯。 我能想到的最好的是下面的代碼。 您能猜出此代碼是否可以編譯?
public class AwkwardCode<T> {public static <T> T T(T T) {return T;} }即使這是一個(gè)非常糟糕的編碼示例,也可以成功編譯,并且應(yīng)用程序可以正常運(yùn)行。 第一行聲明泛型類AwkwardCode ,第二行聲明泛型方法T 方法T是返回T實(shí)例的通用方法。 它采用T類型的參數(shù),不幸的是稱為T 該參數(shù)也會(huì)在方法主體中返回。
通用方法調(diào)用
最后一個(gè)示例顯示了與泛型結(jié)合使用時(shí)類型推斷的工作原理。 當(dāng)我看到一段代碼不包含方法調(diào)用的通用簽名卻聲稱要通過(guò)編譯時(shí),我偶然發(fā)現(xiàn)了這個(gè)問(wèn)題。 當(dāng)某人對(duì)泛型只有一點(diǎn)點(diǎn)經(jīng)驗(yàn)時(shí),這樣的代碼可能一見(jiàn)鐘情。 您能否解釋以下代碼的行為?
public class GenericMethodInvocation {public static void main(final String[] args) {// 1. returns trueSystem.out.println(Compare.<String> genericCompare("1", "1"));// 2. compilation errorSystem.out.println(Compare.<String> genericCompare("1", new Long(1)));// 3. returns falseSystem.out.println(Compare.genericCompare("1", new Long(1)));} }class Compare {public static <T> boolean genericCompare(final T object1, final T object2) {System.out.println("Inside generic");return object1.equals(object2);} }好吧,讓我們分解一下。 第一次調(diào)用genericCompare非常簡(jiǎn)單。 我表示參數(shù)類型將是什么類型的方法,并提供該類型的兩個(gè)對(duì)象-此處沒(méi)有奧秘。 由于Long不是String對(duì)genericCompare第二次調(diào)用無(wú)法編譯。 最后,對(duì)genericCompare第三次調(diào)用返回false 。 這很奇怪,因?yàn)樵摲椒ū宦暶鳛榻邮軆蓚€(gè)相同類型的參數(shù),但是最好將其傳遞給String文字和Long對(duì)象。 這是由編譯期間執(zhí)行的類型擦除過(guò)程引起的。 由于方法調(diào)用未使用泛型的<String>語(yǔ)法,因此編譯器無(wú)法告訴您要傳遞兩種不同的類型。 始終記住,最接近的共享繼承類型用于查找匹配的方法聲明。 意思是,當(dāng)genericCompare接受object1和object2 ,它們被genericCompare轉(zhuǎn)換為Object ,但由于運(yùn)行時(shí)多態(tài)性而被比較為String和Long實(shí)例–因此該方法返回false 。 現(xiàn)在讓我們修改一下代碼。
public class GenericMethodInvocation {public static void main(final String[] args) {// 1. returns trueSystem.out.println(Compare.<String> genericCompare("1", "1"));// 2. compilation errorSystem.out.println(Compare.<String> genericCompare("1", new Long(1)));// 3. returns falseSystem.out.println(Compare.genericCompare("1", new Long(1)));// compilation errorCompare.<? extends Number> randomMethod();// runs fineCompare.<Number> randomMethod();} }class Compare {public static <T> boolean genericCompare(final T object1, final T object2) {System.out.println("Inside generic");return object1.equals(object2);}public static boolean genericCompare(final String object1, final Long object2) {System.out.println("Inside non-generic");return object1.equals(object2);}public static void randomMethod() {} }此新代碼示例通過(guò)添加genericCompare方法的非泛型版本并定義一個(gè)不執(zhí)行任何操作并從GenericMethodInvocation類的main方法調(diào)用兩次的新randomMethod來(lái)修改Compare類。 由于我提供了與給定調(diào)用匹配的新方法,因此該代碼使對(duì)genericCompare的第二次調(diào)用genericCompare可能。 但這引發(fā)了一個(gè)關(guān)于另一個(gè)奇怪行為的問(wèn)題–第二個(gè)呼叫是否通用? 事實(shí)證明–不,不是。 但是,仍然可以使用泛型的<String>語(yǔ)法。 為了更清楚地展示此功能,我使用此通用語(yǔ)法創(chuàng)建了對(duì)randomMethod新調(diào)用。 再次由于類型擦除過(guò)程而實(shí)現(xiàn)了這一點(diǎn)-擦除了這種通用語(yǔ)法。
但是,當(dāng)有界通配符出現(xiàn)在舞臺(tái)上時(shí),情況會(huì)發(fā)生變化。 編譯器以編譯器錯(cuò)誤的形式向我們發(fā)送清晰的消息,提示: Wildcard is not allowed at this location ,這使得無(wú)法編譯代碼。 為了使代碼編譯并運(yùn)行,您必須注釋掉第12行。以這種方式修改代碼后,它將產(chǎn)生以下輸出:
Inside generic true Inside non-generic false Inside non-generic false翻譯自: https://www.javacodegeeks.com/2014/06/beauty-and-strangeness-of-generics.html
總結(jié)
- 上一篇: 证券备案登记的机构(证券备案)
- 下一篇: Jax-RS自定义异常处理