首頁>技術>

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

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

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

泛型是什麼

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

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

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

你可能想到了,在 Java 中,所有的類都繼承了 Object 。於是,你寫出了第一個版本。

public class ApplicationV1 {    // 把敏感欄位設定為空    public static Object removeField(Object obj) throws Exception {        // 需要過濾的敏感欄位        Set<String> fieldSet = new HashSet<String>();        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<String> fieldSet = new HashSet<String>();        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> T removeField(T obj) throws Exception {        // 需要過濾的敏感欄位        Set<String> fieldSet = new HashSet<String>();        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> 、使用型別變數 T obj 、確定型別變數 removeField(new ShopUser(0L, "shopUser", "123456")) 這點非常重要,這裡先按下不表。

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

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

泛型類

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

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

首先,定義一個泛型類。
public class IdGen<T> {    protected T id;    public Generic(T id) {        this.id = id;    }}

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

然後,在例項化、繼承的的時候,指定具體的型別。
public class IdGen<T> {    // ..省略部分程式碼    // 透過繼承,確定泛型變數    static class User extends IdGen<Integer> {        public User(Integer id) {            super(id);        }    }    public static void main(String[] args) {        // 透過例項化,確定泛型變數        IdGen idGen = new IdGen<String>("1");        System.out.println(idGen);        User user = new User(1);        System.out.println(user);    }}

使用者類繼承了 IdGen,在程式碼 extends IdGen<Integer> 中,指定了 Integer 作為 id 的具體型別;而 IdGen 例項化的時候,在程式碼 new IdGen<String>("1") 中,則指定了 String 作為 id 的具體型別。

泛型方法

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

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

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

public class Generic {    public static <T> 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,用在這個方法上。 T obj 使用型別變數 T,定義一個 obj 引數。最後,在呼叫方法的的時候,再確定具體的型別。

泛型萬用字元

泛型萬用字元用 ? 表示,代表不確定的型別,是泛型的一個重要組成。

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

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

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

public class Application {    public static Integer count(List<Integer> list) {        int total = 0;        for (Integer number : list) {            total += number;        }        list.add(total);        return total;    }    public static void main(String[] args) {        // 不傳指定資料,編譯報錯        List<String> 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<Integer> intList = Arrays.asList(0, 1, 2);        print(intList);        // String 集合,可以執行        List<String> strList = Arrays.asList("0", "1", "2");        print(strList);    }}

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

這就是泛型萬用字元。 此外,有些演算法雖然也是通用的,但適用範圍不那麼大。 比如,使用者分為:普通使用者、商家使用者,但使用者有一些特殊功能,其它角色都沒有。這時候,又該怎麼辦呢?

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

泛型萬用字元的上邊界

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

利用 <? 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<Integer>());        // String 不是 Number 的子類,沒法呼叫 print 方法        print(new ArrayList<String>());    }}

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

泛型萬用字元的下邊界

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

利用 <? 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<Number>());        // Long 不是 Integer 的父類,沒法呼叫 print 方法        // print(new ArrayList<String>());    }}

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

寫在最後

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

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

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

13
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 我們一起學RISC-V——07-線上除錯彙編工具