Java中的List 里有可能存String類型元素嗎?


這其實是我遇到的一個線上bug,在這里分享給大家。

如果是用反射,那就很簡單了,畢竟泛型只是在編譯期進行約束,對運行期是無能為力的。

想想看,如果不使用反射,有沒有辦法做到呢?

問題起因

在我們公司的實際業務中,有一段類似於這樣邏輯的代碼,文章最后會放出做測試構造的getList()方法:

    /**
     * 主要業務邏輯
     */
    public static void main(String[] args) {
        // 從數據庫查詢數據列表,不用關注里面的實現細節
        List<DataBO> list = getList();

        // 獲取所有“a”字段的值的集合
        List<Integer> integerList = toList(list, "a");

        if (integerList.contains(1)) {
            System.out.println("集合里包含1,處理對應的邏輯");
        } else {
            System.out.println("集合里不包含1,處理對應的邏輯");
        }
    }

    /**
     * 這是公司提供的一個公共工具方法,獲取集合中,每個對象的某個字段的值的集合
     *
     * @param list 數據對象集合
     * @param key 字段
     * @return 值的集合
     */
    public static <T> List<T> toList(List<DataBO> list, String key) {
        return list.stream()
                .filter(x -> x.get(key) != null)
                .map(x -> (T)x.get(key))
                .collect(Collectors.toList());
    }

其中的DataBO對象簡化如下:

public class DataBO {

    /** 數據庫的一條數據,key是列,value是值 */
    private Map<String, Object> map = new HashMap<>();

    public Object get(String key) {
        return map.get(key);
    }

    public void set(String key, Object value) {
        map.put(key, value);
    }

    @Override
    public String toString() {
        return "DataBO{" + "map=" + map + '}';
    }

}

原本我這里的業務需求是,取列表數據中,所有“a”字段的值出來,判斷其中是否含有1。

已知數據庫里“a”字段定義為int類型,並且確認了有一條數據在“a”字段上存的是1。但是代碼上線一跑,出bug了。

查出來怎么就走到“不包含1”的分支里去了呢?也沒有報錯,難道底層服務的getList()方法有什么特殊處理,把數據庫a=1的那條數據給過濾掉了嗎?

問題定位

於是我加了點日志,把listintergerList的元素打印出來,看看里面到底存了什么東西。於是又上線一版,觀察一看,神奇的事情出現了,里面明明有1啊??!為啥會走到下面“不包含1”的分支呢?見鬼了!

在謎底揭開前,大家看這個日志,先猜猜看,有可能是什么情況呢?

--------------分割線-----------------

於是我只能本地debug了一下,才發現數據庫查到的集合里,“a”字段返回的是個字符串"1"!而ArrayList的contains()方法,底層是用equals()去比較是否存在的。"1".equals(1),結果肯定是false,所以認為不存在。

好吧,雖然數據庫的“a”字段定義為int類型,但是底層服務估計哪里有bug,把Integer類型的字段,轉換成了String類型返回給上層服務了。

但轉念一向,不對啊,我明明定義的是List<Integer>類型的變量,如果是這樣的話,就算查出來"a"字段不是個Integer類型的值,那toList()方法也應該是拋個java.lang.ClassCastException才對,怎么可能正常往下走呢?List<Integer>變量指向的對象里,為什么會存進去一個字符串呢?為什么toList()方法的.map(x -> (T)x.get(key))這一行沒有報錯呢?

問題解析

問題很明顯就是出在了toList()方法里,那個強制類型轉換為泛型,並沒有生效。開頭我們說了,java的泛型,只是在編譯期進行約束,對運行期是無能為力的。那么我們首先就應該想到的就是java的泛型擦除機制,我們對demo類進行編譯、再反編譯看看。

反編譯可以發現,原來toList()方法中,強制類型轉換被擦除了。所以返回的其實並不是List<Integer>對象,而是List對象,沒有泛型限制。很明顯是這個方法有bug,其實就是泛型方法使用錯誤了。

問題修復

本來這個線上bug到這里就已經搞清楚了,如果只是要快速修復上線也很容易就能解決,把toList()方法返回的集合改成List<String>,然后判斷集合是否包含字符串"1"就行。

但我們想,如果后面又有別的同事遇到這個問題了怎么辦呢,也會一臉懵逼,最好還是希望toList()方法拋出個java.lang.ClassCastException,而且還要做到原來這個方法的效果,該怎么修改這個方法呢?

我們可以增加一個參數,告訴方法你希望返回一個什么類型的值:

這樣的話,如果toList()方法還是返回原來的List<Integer>,就會拋異常:

而且如果前后限制的類型不一致,編譯期也會報錯,泛型就起作用了:

到此這個問題徹底解決。

補充下本文用於測試構造的getList()方法:

    /**
     * 查數據庫,獲取數據對象的集合
     *
     * @return 數據對象的集合
     */
    public static List<DataBO> getList() {
        // 這個list是從數據庫查出來的
        List<DataBO> list = new ArrayList<>();
        DataBO db1 = new DataBO();
        db1.set("a", "1");
        DataBO db2 = new DataBO();
        db2.set("a", 2);
        list.add(db1);
        list.add(db2);
        return list;
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM