Java高階特性-泛型:泛型的基本用法,怎樣才能少寫 1 萬行程式碼

泛型是 Java 的一個高階特性。在 Mybatis、Hibernate 這種持久化框架,泛型更是無處不在。

然而,泛型畢竟是高階特性,藏在框架的底層程式碼裡面。我們平時都是寫業務程式碼,可能從來沒見過泛型,更別提怎麼用了。

既然如此,我們就一步步學習泛型吧。

泛型是什麼

泛型是一種特殊的型別。你不用一開始就指明引數的具體型別,而是先定義一個型別變數,在使用的時候再確定引數的具體型別。

這好像還是很難理解。沒關係,我們先來看看,在沒有泛型情況下,我們是怎麼做的。

比如,在電商系統中,使用者有兩種型別,分別是普通使用者、商戶使用者。當用戶點選獲取資訊詳情時,系統要先把一些敏感資訊設定為空,像是 password 之類欄位,然後才返回給使用者。

你能寫一個通用方法,把這些敏感欄位設定為空嗎?

你可能想到了,在 Java 中,所有的類都繼承了

Object

。於是,你寫出了第一個版本。

public class ApplicationV1 { // 把敏感欄位設定為空 public static Object removeField(Object obj) throws Exception { // 需要過濾的敏感欄位 Set fieldSet = new HashSet(); fieldSet。add(“password”); // 獲取所有欄位:然後獲取這個類所有欄位 Field[] fields = obj。getClass()。getDeclaredFields(); // 敏感欄位設定為空 for (Field field : fields) { if (fieldSet。contains(field。getName())) { // 開放欄位操作許可權 field。setAccessible(true); // 設定空 field。set(obj, null); } } // 返回物件 return obj; }}

在這個方法中,你把

Object

作為傳入引數,然後用反射操作欄位,把 password 設定為空。程式碼一氣呵成,於是你又寫出了下面的測試程式碼。

public class ApplicationV1 { // 。。。省略部分程式碼 public static void main(String[] args) throws Exception { // 初始化 ShopUser shopUser = new ShopUser(0L, “shopUser”, “123456”); ClientUser clientUser = new ClientUser(0L, “clientUser”, “123456”); // 輸出原始資訊 System。out。println(“過濾前:”); System。out。println(“ ” + shopUser); System。out。println(“ ” + clientUser); // 執行過濾 shopUser = (ShopUser) removeField(shopUser); clientUser = (ClientUser) removeField(clientUser); // 輸出過濾後資訊 System。out。println(“過濾後:”); System。out。println(“ ” + shopUser); System。out。println(“ ” + clientUser); }}執行結果過濾前: ShopUser{id=0, username=‘shopUser’, password=‘123456’} ClientUser{id=0, username=‘clientUser’, password=‘123456’}過濾後: ShopUser{id=null, username=‘shopUser’, password=‘null’} ClientUser{id=null, username=‘clientUser’, password=‘null’}

執行結果看起來沒問題,但很遺憾,這個方法不能用。最顯而易見的問題是,簡潔性不夠。這個方法要強制轉換物件,你看看這兩行測試程式碼:

// 執行過濾 shopUser = (ShopUser) removeField(shopUser); clientUser = (ClientUser) removeField(clientUser);

明明是同一個物件,你過濾掉敏感欄位後,自己還得再轉換一次物件。你想想看,這好歹是一個通用方法,要用在很多地方,當然是越簡單越好。

你又想到了,Java 有方法過載機制,你寫出了第二個版本。

public class ApplicationV2 { /********************** 業務方法 ************************/ public static ShopUser removeField(ShopUser user) throws Exception { // 強轉,並返回物件 return (ShopUser) remove(user); } public static ClientUser removeField(ClientUser user) throws Exception { // 強轉,並返回物件 return (ClientUser) remove(user); } /********************** 核心方法 ************************/ // 把敏感欄位設定為空 public static Object remove(Object obj) throws Exception { // 需要過濾的敏感欄位 Set fieldSet = new HashSet(); fieldSet。add(“password”); // 獲取所有欄位:然後獲取這個類所有欄位 Field[] fields = obj。getClass()。getDeclaredFields(); // 敏感欄位設定為空 for (Field field : fields) { if (fieldSet。contains(field。getName())) { // 開放欄位操作許可權 field。setAccessible(true); // 設定空 field。set(obj, null); } } // 返回物件 return obj; }}

這樣一來,問題好像又解決了。但新問題來了,重複方法特別多,而且如果再加一個供應商使用者,我還得再寫一個方法嗎?這可是通用方法,動不動就改原始碼,也不是辦法呀。

在沒有泛型的情況下,重複程式碼沒法解決,你總得做些沒意義的操作。要不強轉物件,要不就多寫幾個方法。

然而, Java 的 1。5 版本引入了泛型機制,程式碼可以變得更加簡單。 利用泛型,你寫出了第三個版本。

public class ApplicationV3 { // 把敏感欄位設定為空 public static T removeField(T obj) throws Exception { // 需要過濾的敏感欄位 Set fieldSet = new HashSet(); fieldSet。add(“password”); // 獲取所有欄位:然後獲取這個類所有欄位 Field[] fields = obj。getClass()。getDeclaredFields(); // 敏感欄位設定為空 for (Field field : fields) { if (fieldSet。contains(field。getName())) { // 開放欄位操作許可權 field。setAccessible(true); // 設定空 field。set(obj, null); } } // 返回物件 return obj; }}

在第三個版本中,你使用了泛型,呼叫方法時不用強轉物件了,你也不用在原始碼寫這麼多重複方法,程式碼變得更加簡單了。

你再仔細看完上面的程式碼,可以發現, 泛型的使用步驟:定義型別變數

、使用型別變數

T obj

、確定型別變數

removeField(new ShopUser(0L, “shopUser”, “123456”))

這點非常重要,這裡先按下不表。

這就是泛型,你不用把引數的型別寫死在程式碼,而是在使用的時候,再確定具體的型別。使用了泛型,你的程式碼可以變得更簡單、安全。

當然, 泛型很多的用法,分別是:泛型類及介面、泛型方法、萬用字元。 接下來,我們就一個個解鎖吧~

泛型類

當泛型用在類和介面時,就被稱為泛型類、泛型介面。這個最典型的運用就是各種集合類和介面,比如,List、ArrayList 等等。

那麼,我們泛型怎麼用在類上面呢?

首先,定義一個泛型類。

public class IdGen { protected T id; public Generic(T id) { this。id = id; }}

IdGen 是一個 id 生成類。第一行程式碼中,

是泛型標識,代表你定義了一個型別變數 T。第二行程式碼,我使用這個型別變數,把 id 定義成一個泛型。

然後,在例項化、繼承的的時候,指定具體的型別。

public class IdGen { // 。。省略部分程式碼 // 透過繼承,確定泛型變數 static class User extends IdGen { public User(Integer id) { super(id); } } public static void main(String[] args) { // 透過例項化,確定泛型變數 IdGen idGen = new IdGen(“1”); System。out。println(idGen); User user = new User(1); System。out。println(user); }}

使用者類繼承了 IdGen,在程式碼

extends IdGen

中,指定了 Integer 作為 id 的具體型別;而 IdGen 例項化的時候,在程式碼

new IdGen(“1”)

中,則指定了 String 作為 id 的具體型別。

泛型方法

泛型不僅能用在類和介面上,還可以用在方法上。

比如,怎麼把一個類的成員變數轉換成 Map 集合呢?

這時候,我們可以寫一個泛型方法。

public class Generic { public static Map obj2Map(T obj) throws Exception { Map map = new HashMap<>(); // 獲取所有欄位:透過 getClass() 方法獲取 Class 物件,然後獲取這個類所有欄位 Field[] fields = obj。getClass()。getDeclaredFields(); for (Field field : fields) { // 開放欄位操作許可權 field。setAccessible(true); // 設定值 map。put(field。getName(), field。get(obj)); } return map; }}

同樣的,

是泛型標識,代表你定義了一個型別變數 T,用在這個方法上。

T obj

使用型別變數 T,定義一個 obj 引數。最後,在呼叫方法的的時候,再確定具體的型別。

泛型萬用字元

泛型萬用字元用

表示,代表不確定的型別,是泛型的一個重要組成。

有一點很多文章都沒提到,大家一定要記住!!!

使用泛型有三個步驟:定義型別變數、使用型別變數、確定型別變數。在第三步,確定型別變數的時候,如果你沒法明確型別變數,這時候可以用泛型萬用字元。

一般情況下,我們不需要用到泛型萬用字元,因為你能明確地知道型別變數,你看下面程式碼。

public class Application { public static Integer count(List list) { int total = 0; for (Integer number : list) { total += number; } list。add(total); return total; } public static void main(String[] args) { // 不傳指定資料,編譯報錯 List strList = Arrays。asList(“0”, “1”, “2”); int totalNum = count(strList); // 繞過了編譯,執行報錯 List strList1 = Arrays。asList(“0”, “1”, “2”); totalNum = count(strList1); }}

你非常清楚

count()

方法是幹什麼的,所以你在寫程式碼的時候,直接就能指明這是一個 Integer 集合。這樣一來,在呼叫方法的時候,如果不傳指定的資料進來,就沒法透過編譯。退一萬步講,即使你繞過了編譯這一關,程式也很可能沒法執行。

所以,如果你非常清楚自己要幹什麼,可以很明確地知道型別變數,那沒必要用泛型萬用字元。

然而,在一些通用方法中,什麼型別的資料都能傳進來,你沒法確認型別變數,這時候該怎麼辦呢?

你可以使用泛型萬用字元,這樣就不用確認型別變數,從而實現一些通用演算法。

比如,你要寫一個通用方法,把傳入的 List 集合輸出到控制檯,那麼就可以這樣做。

public class Application { public static void print(List<?> list) { for (int i = 0; i < list。size(); i++) { System。out。println(list。get(i)); } } public static void main(String[] args) { // Integer 集合,可以執行 List intList = Arrays。asList(0, 1, 2); print(intList); // String 集合,可以執行 List strList = Arrays。asList(“0”, “1”, “2”); print(strList); }}

List<?> list

代表我不確定 List 集合裝的是什麼型別,有可能是 Integer,有可能是 String,還可能是別的東西。但我不管這些,你只要傳一個 List 集合進來,這個方法就能正常執行。

這就是泛型萬用字元。

此外,有些演算法雖然也是通用的,但適用範圍不那麼大。

比如,使用者分為:普通使用者、商家使用者,但使用者有一些特殊功能,其它角色都沒有。這時候,又該怎麼辦呢?

你可以給泛型萬用字元設定邊界,以此限定型別變數的範圍。

泛型萬用字元的上邊界

上邊界,代表型別變數的範圍有限,只能傳入某種型別,或者它的子類。你看下這幅圖就明白了。

Java高階特性-泛型:泛型的基本用法,怎樣才能少寫 1 萬行程式碼

利用

<? extends 類名>

的方式,可以設定泛型萬用字元的上邊界。你看下這個例子就明白了。

public class TopLine { public static void print(List<? extends Number> list) { for (int i = 0; i < list。size(); i++) { System。out。println(list。get(i)); } } public static void main(String[] args) { // Integer 是 Number 的子類,可以呼叫 print 方法 print(new ArrayList()); // String 不是 Number 的子類,沒法呼叫 print 方法 print(new ArrayList()); }}

你想呼叫

print()

方法中,那麼你可以傳入 Integer 集合,因為 Integer 是 Number 的子類。但 String 不是 Number 的子類,所以你沒法傳入 String 集合。

泛型萬用字元的下邊界

下邊界,代表型別變數的範圍有限,只能傳入某種型別,或者它的父類。你看下這幅圖就明白了。

Java高階特性-泛型:泛型的基本用法,怎樣才能少寫 1 萬行程式碼

利用

<? super 類名>

的方式,可以設定泛型萬用字元的上邊界。你看下這個例子就明白了。

public class LowLine { public static void print(List<? super Integer> list) { for (int i = 0; i < list。size(); i++) { System。out。println(list。get(i)); } } public static void main(String[] args) { // Number 是 Integer 的父類,可以呼叫 print 方法 print(new ArrayList()); // Long 不是 Integer 的父類,沒法呼叫 print 方法 // print(new ArrayList()); }}

你想呼叫

print()

方法中,那麼可以傳入 Number 集合,因為 Number 是 Integer 的父類。但 Long 不是 Integer 的父類,所以你沒法傳入 Long 集合。

寫在最後

泛型是一種特殊的型別,你可以把泛型用在類、介面、方法上,從而實現一些通用演算法。

此外,使用泛型有三個步驟:定義型別變數、使用型別變數、確定型別變數。

在確定型別變數這一步中,你可以用泛型萬用字元來限制泛型的範圍,從而實現一些特殊演算法。