什麼是泛型編程?

什麼是泛型編程?

泛型在 Java 5 出現,實現了參數化類型,主要作用是使得類或接口更加通用。比如 Java 中的容器類,通過泛型實現了對各種類型的兼容,成為極其通用的類庫。如果我們要設計自己的框架,泛型基本上已經算是標配了。

類使用泛型

Java 在運行時對泛型進行擦除,運行時所有的泛型類型都退化為 Object 類型。這樣的設計主要是為了保證對 Java 5 之前的代碼進行兼容。儘管如此,使用泛型後,在編譯階段仍會對類型進行檢查,確保使用泛型的地方都是類型正確的。

@Getter@Setterpublic class TestDO { T value;}

如下代碼中,SubTestDO 傳遞給父類的具體類型為默認類型 Object 類型,而SubTestDOSimple 傳遞給父類的具體類型則是 String 類型。這兩個子類都對泛型做了具體化,是泛型類型的常規使用。

/** 無泛型,即為 Object */class SubTestDO extends TestDO {}/** 繼承時指定父類實際類型 */class SubTestDOSimple extends TestDO {}

在定義 TestDO 的子類時,子類也可以使用泛型。子類的泛型可以獨自使用,也可以傳遞給父類。如 SubTestDOTrans 定義了泛型 T 並把 T 傳遞給父類,而 SubTestDOComp 定義了泛型 T 要求必須是 HashMap 的子類,但傳遞給父類的泛型變成了 List

/** 繼承時傳遞泛型 */class SubTestDOTrans extends TestDO {}/** 繼承時傳遞複雜類型 */public class SubTestDOComp extends SubTestDOTrans> {}

接口使用泛型可以定義適用於各種場景的通用接口,在設計模式中具有重要作用。如工廠模式就可以實現根據返回值類型自動轉型,涉及到方法的泛型,在後文給出解釋。

如下代碼中,IGeneric 接口使用了泛型,並用泛型定義了方法。如此定義後,IGeneric 接口成為了一個通用接口,方法的入參類型與實現類或子接口對接口的定義保持一致,大大提高了通用性。接口可以繼承,在繼承中泛型可以有形式的變化,並定義新的泛型。

/** 普通泛型接口,泛型 T */public interface IGeneric {public void test(T t);}

ISubGenericSimple 接口在繼承接口時,直接確定了泛型的類型為 String 類型,ISubGenericSimple 接口退化為一個普通接口,子接口或實現類不再需要對泛型進行處理。這是通過子接口對泛型進行具體化,也是架構設計中的常用手段。

/** 繼承時指定泛型 */interface ISubGenericSimple extends IGeneric{}

ISubGenericTrans 接口同樣使用了泛型,並把泛型傳遞給父接口。這種一般用於父接口的擴展,在子接口中可以定義一些特有方法,同時又完全兼容父接口的引用。

/** 繼承時傳遞泛型 */interface ISubGenericTrans extends IGeneric{}
/** 繼承時指定泛型為特定類型的子類 */interface ISubGenericSubType extends IGeneric{}

ISubGenericCompType 接口使用了一個相對複雜的泛型定義。子接口定義了泛型 T,而傳遞給父接口的泛型變成了 TestDO。這種接口繼承方式在大型軟件架構中有比較廣泛的應用,通俗理解為父接口使用容器但元素類型為泛型,而子接口定義容器的元素類型,在組件化繼承體系有比較明確的應用。這樣的繼承關係,可以確保父接口完成容器的操作方法而不用關心具體元素類型,子接口可以有少許定製邏輯甚至可以不定義方法。

/** 繼承時指定複雜的泛型類型 */interface ISubGenericCompType 
extends IGeneric>{}

方法使用泛型

方法也可以使用泛型,並且有很大的現實意義,在工廠模式、建造器模式中都可以使用泛型方法。

/** 泛型 T 僅用於入參 */static  String toString(T t){return null;}

getInstance 方法不但在入參中使用了泛型,也把這個泛型作為返回值的類型。類似 getInstance 方法的定義有很多,比如 Spring 的 getBean 方法就重載了這種形式的應用。在確保方法通用性的基礎上,也保證了返回值類型與入參的一致性。

/** 返回 T 類型 */static  T getInstance(Class clazz) throws Exception {return clazz.newInstance();}

getTestDO 方法是另一種形式的泛型方法,這裡定義了返回值的泛型必須是 TestDO 及其子類。這種泛型方法常用於繼承體系明確,需要對類型進行向下轉型的場景。在方法內部需要對返回值進行強制轉型,如果類型不匹配便會拋出類型轉換異常,因此要求調用者必須瞭解方法的定義和使用要求。

/** 返回 TestDO 的子類類型 */static  
T getTestDO(){return (T) new SubTestDOTrans<>();}

下面 getTestDO 的重載方法對泛型做了一定程度的退化,雖然定義了泛型 T,但這個泛型僅僅是容器的元素類型,方法的返回值是 TestDO。這種類型的泛型方法可用於複雜對象的組裝,比上面的重載方法更安全。

/** 返回 TestDO 類型 */static  TestDO getTestDO(T v){ TestDO testDO = new TestDO<>(); testDO.setValue(v);return testDO;}

泛型擦除

前面提到過 Java 會在運行時對泛型進行擦除,也就是抹去泛型信息,全部變為 Object 類型。因此,在運行時,TestDO 與 TestDO 在類型上並沒有多大區別。雖然實際存儲的 value 對象的真正類型是不同的,但是都會當做 Object 進行處理。也是因為擦除,引用類型對象時,我們可以使用 TestDO.class 卻不能使用 TestDO.class。

泛型通配

前面的泛型都明確了泛型的類型為 T,並使用 T 定義字段、參數和返回值。在對具體類型依賴不那麼強烈的情況下,比如,我們僅僅是要求類型是某個類型的子類即可,最終處理時使用父類類型而不依賴於子類類型,則可以使用通配符 ? 來定義泛型。

在如下代碼中,set1 的元素類型為 TestDO 的子類。我們只關心 Set 中的元素是 TestDO 類型即可,並不關心實際元素是 SubTestDOSimple 還是 SubTestDOComp。而 set2 的元素類型可以是任何類型。

Set extends TestDO> set1 = new HashSet<>();Set> set2 = new HashSet<>();

泛型逆變

前面提到的泛型定義用到了 extends 來確定泛型的邊界,要求其必須是某個類型的子類。實際上,泛型還支持向上確定邊界,也就是泛型為某個類型的父類。在如下代碼中,定義了 Set 的元素類型為 SubTestDOComp 的父類。

Set super SubTestDOComp> testDOs = new HashSet<>();

使用泛型創建類型安全的分層結構

在架構設計中,常常涉及複雜的數據結構與設計分層,使用泛型對類型進行定義和約束,可以確保抽象層的設計有足夠的通用性,同時最終產生的具體類型依然保持正確的數據類型。

比如在一個架構設計中,可能會分為抽象層、基礎層、實現層三個層次。

在抽象層定義極度抽象的業務流程,需要這裡的類型定義都具有較好的通用性,這時可以大量使用泛型和抽象類,只定義流程而不涉及具體類型。

在基礎層定義一些基礎服務,可能會在 core 層類型的基礎上做一定程度的擴展,封裝出多個適用範圍不同的業務處理流程。在這一層,也會使用泛型,但會更加具體。

而在實現層不會再使用泛型,而是給出真正的類型,並實現具體的邏輯。

/** 抽象層,指定泛型 K,V */abstract class AbstractGenericImpl implements IGeneric> {public Map map = new HashMap<>();}/** 基礎層,繼承 AbstractGenericImpl */class BasicGenericImpl extends AbstractGenericImpl>{ @Overridepublic void test(Map> data) { map.putAll(data); }}/** 實現層,繼承 BasicGenericImpl */public class ComplexGenericImpl extends BasicGenericImpl { @Overridepublic void test(Map> data) {super.test(data); }}

總結

泛型在當今時代的 Java 開發中已經被廣泛應用,如 Spring、MyBatis、Dubbo 等開源框架都使用了大量泛型來實現通用功能。在開發中,既要掌握開源框架的泛型使用方法,也要熟悉泛型的定義和使用,並對泛型有足夠深刻的理解。如果你要開發自己的框架,那麼泛型一定是標配,需要好好理解和掌握。


分享到:


相關文章: