最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java泛型高級玩法之通配符、上下界與類型擦除避坑實(shí)戰(zhàn)

 更新時間:2025年12月07日 09:24:20   作者:程序員丘山  
今天這篇文章,不跟大家聊純概念,就結(jié)合實(shí)際開發(fā)中踩過的坑、寫過的通用數(shù)據(jù)校驗(yàn)工具類,把泛型通配符的用法、類型擦除的坑,還有避坑的實(shí)操技巧,都講清楚吧

引言

做Java開發(fā)的朋友應(yīng)該都有體會:泛型這東西入門容易,真要用到項目里——比如寫個通用工具類、處理各種類型的集合時,動不動就踩坑。要么通配符用錯編譯報錯,要么類型擦除導(dǎo)致運(yùn)行時拋ClassCastException,要么想創(chuàng)建個泛型數(shù)組直接編譯不過。

今天這篇文章,我不跟大家聊純概念,就結(jié)合我實(shí)際開發(fā)中踩過的坑、寫過的通用數(shù)據(jù)校驗(yàn)工具類,把泛型通配符(?、? extends T、? super T)的用法、類型擦除的坑,還有避坑的實(shí)操技巧,都講清楚。保證都是實(shí)戰(zhàn)干貨,代碼能直接跑,看完就能用到自己的項目里。

一、泛型通配符:3種寫法的實(shí)戰(zhàn)區(qū)別(PECS原則真的很好用)

泛型通配符的核心就是解決“不同類型集合怎么通用處理”的問題,很多人搞不懂 ?、? extends T、? super T的區(qū)別,其實(shí)記住PECS原則(生產(chǎn)者用Extends,消費(fèi)者用Super)就夠了。我結(jié)合自己寫過的工具方法,給大家講透每個通配符的用法和坑。

1. 無界通配符 ?:只做通用讀取,別想著寫數(shù)據(jù)

無界通配符?就是匹配任意類型的泛型,我一般只用在“只讀取不寫入”的通用工具里,比如打印任意集合、判斷集合是否為空。

我當(dāng)初踩過一個坑:用?定義的List,想往里面加個字符串,結(jié)果直接編譯報錯。后來才明白,編譯器根本不知道這個List具體存的是啥類型,為了保證類型安全,除了null,啥都不讓加。

實(shí)戰(zhàn)場景:通用集合打印工具

需求很簡單:寫個方法,不管傳過來的是List、List還是List<自定義對象>,都能打印里面的元素。

import java.util.List;

/**
 * 無界通配符實(shí)戰(zhàn):通用集合打印工具
 * 快速打印集合,不用重復(fù)寫循環(huán)
 */
public class WildcardDemo {
    // 無界通配符?,匹配任意類型的List
    public static void printList(List<?> list) {
        if (list == null || list.isEmpty()) {
            System.out.println("集合為空");
            return;
        }
        for (Object obj : list) {
            // 只能讀成Object類型,因?yàn)椴恢谰唧w是啥類型
            System.out.println("元素值:" + obj);
        }
        
        // 這里是我踩過的坑:想加個字符串,編譯直接報錯
        // list.add("test"); // 編譯錯誤:沒法確定list的具體類型,寫入會亂套
        // 唯一能加的只有null,因?yàn)閚ull是所有類型的子類
        list.add(null);
    }

    public static void main(String[] args) {
        List<String> strList = List.of("Java", "泛型", "通配符");
        List<Integer> intList = List.of(1, 2, 3);
        
        printList(strList); // 正常打印字符串列表
        printList(intList); // 正常打印整數(shù)列表
    }
}

為啥這么寫?

無界通配符的核心就是“通用只讀”,比如打印、統(tǒng)計長度、判空這些操作,不用關(guān)心集合里具體存的是啥,用?就夠了。但千萬別想著往里面寫數(shù)據(jù),除了null,寫啥都報錯。

2. 上界通配符? extends T:只讀取,不寫入(生產(chǎn)者場景)

? extends T的意思是“匹配T或者T的子類”,我一般用在“從集合里讀數(shù)據(jù)”的場景,比如給Integer、Long、Double這些數(shù)值類型的集合求和——這些集合都是“生產(chǎn)”數(shù)值的,所以用extends。

我之前犯過一個錯:用? extends Number的List,想往里加個Integer,結(jié)果編譯報錯?,F(xiàn)在想通了,編譯器不知道這個List是存Integer、Long還是Double,要是加了Integer,萬一原本是Long的List,不就亂套了?

實(shí)戰(zhàn)場景:數(shù)值集合求和工具

需求:寫個方法,能給Integer、Long、Double的List求和,返回double類型結(jié)果。

import java.util.List;

/**
 * 上界通配符實(shí)戰(zhàn):數(shù)值集合求和
 * 做報表統(tǒng)計時經(jīng)常用這個方法,不用給每種數(shù)值類型都寫一遍求和
 */
public class UpperBoundDemo {
    // 上界通配符:匹配Number或其子類(Integer、Long、Double都算)
    public static double sum(List<? extends Number> numberList) {
        double total = 0.0;
        for (Number num : numberList) {
            // 讀數(shù)據(jù):所有子類都能轉(zhuǎn)成Number,安全
            total += num.doubleValue();
        }
        
        // 踩坑點(diǎn):想加Integer,編譯報錯
        // numberList.add(10); // 編譯器不知道list是存Integer還是Long,不敢讓加
        // 就算加Number也不行
        // numberList.add(10.0); // 同樣報錯
        
        return total;
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3);
        List<Double> doubleList = List.of(1.5, 2.5);
        
        System.out.println("整數(shù)列表求和:" + sum(intList)); // 輸出6.0
        System.out.println("小數(shù)列表求和:" + sum(doubleList)); // 輸出4.0
    }
}

核心要點(diǎn)? extends T就是“生產(chǎn)者”——集合里的元素都是T的子類,能安全讀成T類型,但絕對不能寫。比如求和、導(dǎo)出數(shù)據(jù)、遍歷取值這些場景,用這個通配符準(zhǔn)沒錯。

3. 下界通配符? super T:只寫入,讀取只能拿Object(消費(fèi)者場景)

? super T的意思是“匹配T或者T的父類”,我一般用在“往集合里寫數(shù)據(jù)”的場景,比如批量往List里插Integer——不管這個List是List、List還是List,都能插,因?yàn)檫@些都是Integer的父類。

這里也有個坑:用? super T的List讀數(shù)據(jù)時,只能讀成Object類型,想轉(zhuǎn)成T類型會報錯。因?yàn)榫幾g器不知道這個List是存T的哪個父類,沒法確定類型。

實(shí)戰(zhàn)場景:數(shù)據(jù)批量插入工具

需求:寫個方法,能往任意“能存Integer”的集合里批量插整數(shù)。

import java.util.ArrayList;
import java.util.List;

/**
 * 下界通配符實(shí)戰(zhàn):數(shù)據(jù)批量插入
 * 用這個方法往不同類型的集合里插數(shù)據(jù)
 */
public class LowerBoundDemo {
    // 下界通配符:匹配Integer或其父類(Number、Object)
    public static void batchAddInteger(List<? super Integer> list, int count) {
        for (int i = 1; i <= count; i++) {
            // 寫數(shù)據(jù):Integer能存到任意父類集合里,安全
            list.add(i);
        }
        
        // 讀數(shù)據(jù):只能拿到Object類型,這是坑點(diǎn)
        for (Object obj : list) {
            System.out.println("插入的元素:" + obj);
        }
        
        // 我踩過的坑:想直接讀成Integer,編譯報錯
        // for (Integer num : list) { // 編譯錯誤:list可能是Number或Object類型,沒法轉(zhuǎn)Integer
        //     System.out.println(num);
        // }
    }

    public static void main(String[] args) {
        // 場景1:List<Integer>(直接存Integer)
        List<Integer> intList = new ArrayList<>();
        batchAddInteger(intList, 3); // 插入1、2、3
        
        // 場景2:List<Number>(Integer的父類)
        List<Number> numList = new ArrayList<>();
        batchAddInteger(numList, 2); // 插入1、2
        
        // 場景3:List<Object>(Integer的頂級父類)
        List<Object> objList = new ArrayList<>();
        batchAddInteger(objList, 1); // 插入1
    }
}

核心要點(diǎn)? super T就是“消費(fèi)者”——集合能接收T類型的數(shù)據(jù),所以寫數(shù)據(jù)絕對安全,但讀數(shù)據(jù)只能拿Object。比如批量插入、批量賦值、數(shù)據(jù)入庫這些場景,就用這個通配符。

4. PECS原則速查表

通配符類型能干嘛我常用的場景讀寫注意點(diǎn)
?匹配任意類型打印、判空、統(tǒng)計長度讀:只能拿Object;寫:只能加null
? extends T匹配T或其子類求和、導(dǎo)出數(shù)據(jù)、遍歷取值讀:能轉(zhuǎn)T;寫:啥都不能加(除了null)
? super T匹配T或其父類批量插入、批量賦值、數(shù)據(jù)入庫讀:只能拿Object;寫:能加T類型

二、類型擦除:泛型最坑的地方

很多人不知道,泛型其實(shí)是Java的“語法糖”——編譯的時候,編譯器會把所有泛型信息都擦除掉,換成Object或者限定類型。這就導(dǎo)致了一堆坑,我給大家列幾個我踩過的高頻坑,一個個說怎么解決。

1. 先搞懂類型擦除是啥

編譯的時候,編譯器會把泛型代碼改成“原始代碼”:

  • List會被擦成List,讀數(shù)據(jù)時自動加(String)強(qiáng)制轉(zhuǎn)換;
  • List<? extends Number>會被擦成List;
  • 泛型類、泛型方法里的都會被擦成Object(有上限就擦成上限類型)。

舉個例子,咱們寫的泛型代碼:

// 編譯前
List<String> strList = new ArrayList<>();
strList.add("Java");
String str = strList.get(0);

// 編譯后(編譯器自動改的)
List strList = new ArrayList();
strList.add("Java");
String str = (String) strList.get(0); // 自動加了強(qiáng)制轉(zhuǎn)換

看出來了吧?泛型只在編譯期管類型安全,運(yùn)行時JVM根本不知道泛型是啥,這就是所有坑的根源。

2. 我踩過的5個高頻坑(附解決方案)

坑1:泛型數(shù)組創(chuàng)建失?。ㄗ畛R姷目樱?/h4>

我當(dāng)初想創(chuàng)建一個List[]數(shù)組,結(jié)果編譯直接報錯;后來想強(qiáng)行轉(zhuǎn)類型,運(yùn)行時又拋ClassCastException。

踩坑代碼

/**
 * 我踩過的坑:泛型數(shù)組創(chuàng)建失敗
 */
public class TypeErasureDemo1 {
    public static void main(String[] args) {
        // 坑1:直接創(chuàng)建泛型數(shù)組,編譯報錯
        // List<String>[] strArr = new List<String>[10]; // 編譯錯誤:Generic array creation
        
        // 坑2:強(qiáng)行轉(zhuǎn)類型,運(yùn)行時報錯
        List<String>[] strArr2 = (List<String>[]) new List[10];
        strArr2[0] = new ArrayList<String>();
        // 運(yùn)行時坑:數(shù)組實(shí)際是List[],能存任意List
        List<Integer> intList = new ArrayList<>();
        intList.add(123);
        strArr2[0] = (List<String>) intList; // 編譯過了,運(yùn)行時拋ClassCastException
    }
}

為什么?

數(shù)組是“協(xié)變”的(比如String[]是Object[]的子類),但泛型是“不變”的(List不是List的子類)。類型擦除后,JVM分不清List[]和List[],所以編譯器直接不讓創(chuàng)建泛型數(shù)組。

我的解決方案:別用數(shù)組,改用List<List>!這是我現(xiàn)在最常用的辦法,簡單又安全。

/**
 * 我的解決方案:用List代替泛型數(shù)組
 */
public class TypeErasureDemo1Fix {
    public static void main(String[] args) {
        // 用List<List<String>>代替List<String>[]
        List<List<String>> strListContainer = new ArrayList<>();
        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strListContainer.add(strList);
        
        // 類型安全:想存List<Integer>直接編譯報錯,不會有運(yùn)行時問題
        // List<Integer> intList = List.of(1);
        // strListContainer.add(intList); // 編譯錯誤,類型對不上
    }
}

坑2:用instanceof判斷泛型類型

我當(dāng)初想判斷一個List是不是List,寫了instanceof List,結(jié)果編譯報錯。

踩坑代碼

/**
 * 我踩過的坑:instanceof判斷泛型類型
 */
public class TypeErasureDemo2 {
    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        
        // 坑:instanceof沒法判斷泛型類型,編譯報錯
        // if (strList instanceof List<String>) { // 編譯錯誤
        
        // 看似能寫,但沒意義——只能判斷是不是List,沒法判斷泛型
        if (strList instanceof List<?>) {
            System.out.println("是List,但不知道存的是啥");
        }
    }
}

為什么?

運(yùn)行時泛型信息已經(jīng)被擦除了,JVM只知道是List,不知道是List還是List,所以instanceof根本判斷不了。

我的解決方案:傳個Class類型令牌,遍歷集合判斷每個元素的類型。

import java.util.List;

/**
 * 我的解決方案:用類型令牌判斷元素類型
 */
public class TypeErasureDemo2Fix {
    // 我項目里的通用方法:判斷List里的元素是不是指定類型
    public static <T> boolean isListOfType(List<?> list, Class<T> type) {
        if (list.isEmpty()) {
            return false; // 空集合沒法判斷
        }
        for (Object obj : list) {
            if (!type.isInstance(obj)) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        List<String> strList = List.of("Java", "泛型");
        List<Integer> intList = List.of(1, 2);
        
        // 用類型令牌判斷,靠譜!
        System.out.println("是不是String列表:" + isListOfType(strList, String.class)); // true
        System.out.println("是不是String列表:" + isListOfType(intList, String.class)); // false
    }
}

坑3:泛型類里定義靜態(tài)泛型變量

我當(dāng)初在泛型類里寫了個static T staticValue,結(jié)果編譯直接報錯,后來才知道靜態(tài)變量和泛型實(shí)例沒關(guān)系。

踩坑代碼

/**
 * 我踩過的坑:泛型類里的靜態(tài)變量不能用泛型
 */
public class TypeErasureDemo3<T> {
    // 坑:靜態(tài)變量用T,編譯報錯
    // private static T staticValue; // Compile error: Cannot make a static reference to the non-static type T
    
    // 勉強(qiáng)能寫,但沒意義
    private static List<?> staticList = new ArrayList<>();
}

為什么?

靜態(tài)變量屬于“類”,不是屬于“實(shí)例”。比如我創(chuàng)建TypeErasureDemo3和TypeErasureDemo3,這兩個實(shí)例共享同一個靜態(tài)變量,編譯器沒法給靜態(tài)變量綁定具體的T類型。

我的解決方案:靜態(tài)方法單獨(dú)定義泛型參數(shù),別用類的泛型參數(shù);靜態(tài)變量要么用具體類型,要么用?。

import java.util.ArrayList;
import java.util.List;

/**
 * 我的解決方案:靜態(tài)方法自己定義泛型參數(shù)
 */
public class TypeErasureDemo3Fix {
    // 靜態(tài)變量:用具體類型,別用泛型
    private static List<String> staticStrList = new ArrayList<>();

    // 靜態(tài)泛型方法:自己定義<T>,和類無關(guān)
    public static <T> void addElement(List<T> list, T element) {
        list.add(element);
    }

    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        
        // 靜態(tài)方法能支持不同類型,好用!
        addElement(strList, "Java");
        addElement(intList, 123);
        
        System.out.println(strList); // [Java]
        System.out.println(intList); // [123]
    }
}

坑4:try-catch捕獲泛型異常

我當(dāng)初想自定義一個泛型異常,然后用catch捕獲GenericException,結(jié)果編譯報錯。

踩坑代碼

/**
 * 我踩過的坑:捕獲泛型異常
 */
public class TypeErasureDemo4 {
    // 自定義泛型異常(編譯不報錯,但用不了)
    static class GenericException<T> extends Exception {
        private T errorData;
        
        public GenericException(T errorData) {
            this.errorData = errorData;
        }
    }

    public static void main(String[] args) {
        try {
            throw new GenericException<>("測試異常");
        } catch (GenericException<String> e) { // 編譯錯誤:不讓捕泛型異常
            e.printStackTrace();
        }
    }
}

為什么?

異常處理是運(yùn)行時的事,類型擦除后,GenericException和GenericException都變成了GenericException,JVM分不清,所以編譯器直接不讓捕。

我的解決方案:別定義泛型異常,改用Object存錯誤數(shù)據(jù),然后寫個泛型方法取數(shù)據(jù)。

/**
 * 我的解決方案:非泛型異常+泛型getter
 */
public class TypeErasureDemo4Fix {
    // 非泛型異常,用Object存任意類型數(shù)據(jù)
    static class DataException extends Exception {
        private Object errorData;
        
        public DataException(Object errorData) {
            this.errorData = errorData;
        }
        
        // 泛型方法:安全取數(shù)據(jù),自己控制類型轉(zhuǎn)換
        public <T> T getErrorData(Class<T> type) {
            if (type.isInstance(errorData)) {
                return type.cast(errorData);
            }
            throw new ClassCastException("類型對不上");
        }
    }

    public static void main(String[] args) {
        try {
            throw new DataException("字符串錯誤");
            // throw new DataException(12345); // 也能傳整數(shù)
        } catch (DataException e) {
            // 安全取數(shù)據(jù),不會亂轉(zhuǎn)
            String errorMsg = e.getErrorData(String.class);
            System.out.println("錯誤信息:" + errorMsg);
        }
    }
}

坑5:泛型方法重載

我當(dāng)初寫了兩個processData方法,一個接List,一個接List,結(jié)果編譯報錯,說方法簽名重復(fù)。

踩坑代碼

/**
 * 我踩過的坑:泛型方法重載沖突
 */
public class TypeErasureDemo5 {
    // 方法1:處理String列表
    public static void processData(List<String> list) {
        System.out.println("處理字符串");
    }
    
    // 方法2:處理Integer列表,編譯報錯
    // public static void processData(List<Integer> list) { // 編譯錯誤:簽名擦除后一樣
    //     System.out.println("處理整數(shù)");
    // }
}

為什么?

類型擦除后,List和List都變成了List,兩個方法的簽名都是processData(List),編譯器分不清,所以不讓重載。

我的解決方案:要么加個Class參數(shù)區(qū)分簽名,要么直接改方法名。我一般加類型令牌,不用改方法名。

import java.util.List;

/**
 * 我的解決方案:加類型令牌區(qū)分重載
 */
public class TypeErasureDemo5Fix {
    // 通用方法:加類型令牌,判斷類型后處理
    public static <T> void processData(List<T> list, Class<T> type) {
        if (type == String.class) {
            System.out.println("處理字符串列表:" + list);
        } else if (type == Integer.class) {
            System.out.println("處理整數(shù)列表:" + list);
        } else {
            System.out.println("處理其他類型");
        }
    }

    public static void main(String[] args) {
        List<String> strList = List.of("Java", "泛型");
        List<Integer> intList = List.of(1, 2);
        
        // 傳類型令牌,就能區(qū)分了
        processData(strList, String.class);
        processData(intList, Integer.class);
    }
}

三、實(shí)戰(zhàn)案例:通用數(shù)據(jù)校驗(yàn)工具類

光說不練假把式,我把上面的知識點(diǎn)都揉進(jìn)一個“通用數(shù)據(jù)校驗(yàn)工具類”里,這是我實(shí)際項目里用來校驗(yàn)各種數(shù)據(jù)的,能避開前面說的所有坑,大家可以直接拿去改改用。

1. 需求說明

我做的這個工具類,要實(shí)現(xiàn)這幾個功能:

  • 校驗(yàn)任意類型的List里的元素是否符合規(guī)則(比如字符串非空、數(shù)值大于0);
  • 批量把符合規(guī)則的數(shù)據(jù)寫到另一個集合里;
  • 能獲取校驗(yàn)失敗的數(shù)據(jù),還能拋出帶錯誤數(shù)據(jù)的異常;
  • 避開泛型的各種坑,保證類型安全。

2. 完整代碼

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * 通用數(shù)據(jù)校驗(yàn)工具類
 * 功能:校驗(yàn)任意類型數(shù)據(jù)、批量寫入有效數(shù)據(jù)、返回?zé)o效數(shù)據(jù)、拋出帶錯誤信息的異常
 * 避坑點(diǎn):通配符用法、類型擦除、靜態(tài)泛型、異常處理
 */
public class GenericDataValidator<T> {
    // 靜態(tài)常量:不用泛型,避免靜態(tài)泛型坑
    private static final String DEFAULT_ERROR_MSG = "數(shù)據(jù)校驗(yàn)失敗";
    // 類型令牌:解決類型擦除后沒法判斷類型的問題
    private final Class<T> typeToken;

    // 構(gòu)造方法:傳入類型令牌,必須的!
    public GenericDataValidator(Class<T> typeToken) {
        this.typeToken = typeToken;
    }

    /**
     * 校驗(yàn)所有元素是否符合規(guī)則(上界通配符:只讀取,生產(chǎn)者)
     * @param dataList 待校驗(yàn)集合(T或T的子類)
     * @param rule 校驗(yàn)規(guī)則(比如字符串非空、數(shù)值大于0)
     * @return true=全部符合,false=有不符合的
     */
    public boolean validateAll(List<? extends T> dataList, Predicate<T> rule) {
        if (dataList == null || dataList.isEmpty()) {
            return true;
        }
        for (T data : dataList) {
            // 先判斷類型對不對,避免類型擦除導(dǎo)致的轉(zhuǎn)換異常
            if (!typeToken.isInstance(data)) {
                throw new ClassCastException("集合里有不是" + typeToken.getName() + "類型的數(shù)據(jù)");
            }
            // 再校驗(yàn)規(guī)則
            if (!rule.test(data)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 批量寫入有效數(shù)據(jù)(下界通配符:只寫入,消費(fèi)者)
     * @param targetList 目標(biāo)集合(T或T的父類)
     * @param sourceList 源數(shù)據(jù)集合
     * @param rule 校驗(yàn)規(guī)則
     * @return 成功寫入的數(shù)量
     */
    public int addValidData(List<? super T> targetList, List<? extends T> sourceList, Predicate<T> rule) {
        // 先判空,避免空指針
        Objects.requireNonNull(targetList, "目標(biāo)集合不能為null");
        Objects.requireNonNull(sourceList, "源數(shù)據(jù)集合不能為null");
        
        int successCount = 0;
        for (T data : sourceList) {
            if (rule.test(data)) {
                targetList.add(data); // 下界通配符,寫入安全
                successCount++;
            }
        }
        return successCount;
    }

    /**
     * 獲取校驗(yàn)失敗的數(shù)據(jù)(靜態(tài)泛型方法:自己定義<T>,避開靜態(tài)泛型坑)
     * @param dataList 待校驗(yàn)集合
     * @param rule 校驗(yàn)規(guī)則
     * @param <T> 數(shù)據(jù)類型
     * @return 失敗的數(shù)據(jù)列表
     */
    public static <T> List<T> getInvalidData(List<? extends T> dataList, Predicate<T> rule) {
        List<T> invalidList = new ArrayList<>();
        if (dataList == null || dataList.isEmpty()) {
            return invalidList;
        }
        for (T data : dataList) {
            if (!rule.test(data)) {
                invalidList.add(data);
            }
        }
        return invalidList;
    }

    /**
     * 自定義異常:非泛型,用Object存錯誤數(shù)據(jù)(避開泛型異??樱?
     */
    public static class ValidationException extends RuntimeException {
        private final Object invalidData; // 存任意類型的錯誤數(shù)據(jù)

        public ValidationException(String message, Object invalidData) {
            super(message);
            this.invalidData = invalidData;
        }

        // 泛型方法:安全獲取錯誤數(shù)據(jù)
        public <E> E getInvalidData(Class<E> type) {
            if (type.isInstance(invalidData)) {
                return type.cast(invalidData);
            }
            throw new ClassCastException("錯誤數(shù)據(jù)類型不對,想要:" + type.getName());
        }
    }

    /**
     * 嚴(yán)格校驗(yàn):失敗就拋異常
     */
    public void strictValidate(List<? extends T> dataList, Predicate<T> rule) {
        List<T> invalidData = getInvalidData(dataList, rule);
        if (!invalidData.isEmpty()) {
            // 拋非泛型異常,帶錯誤數(shù)據(jù)
            throw new ValidationException(DEFAULT_ERROR_MSG, invalidData.get(0));
        }
    }

    // 測試方法
    public static void main(String[] args) {
        // 1. 字符串校驗(yàn)示例(校驗(yàn)非空非空白)
        GenericDataValidator<String> strValidator = new GenericDataValidator<>(String.class);
        List<String> strList = List.of("Java", "", "泛型", "   ", "高級特性");
        // 校驗(yàn)規(guī)則:非空且非空白
        Predicate<String> strRule = s -> s != null && !s.isBlank();
        
        // 校驗(yàn)所有數(shù)據(jù)
        boolean allValid = strValidator.validateAll(strList, strRule);
        System.out.println("字符串是否全部有效:" + allValid); // false
        
        // 獲取無效數(shù)據(jù)
        List<String> invalidStr = GenericDataValidator.getInvalidData(strList, strRule);
        System.out.println("無效字符串:" + invalidStr); // ["", "   "]
        
        // 批量寫入有效數(shù)據(jù)到Object集合(下界通配符)
        List<Object> targetList = new ArrayList<>();
        int successCount = strValidator.addValidData(targetList, strList, strRule);
        System.out.println("成功寫入有效字符串?dāng)?shù)量:" + successCount); // 3
        System.out.println("目標(biāo)集合內(nèi)容:" + targetList); // [Java, 泛型, 高級特性]
        
        // 嚴(yán)格校驗(yàn),失敗拋異常
        try {
            strValidator.strictValidate(strList, strRule);
        } catch (ValidationException e) {
            String invalidData = e.getInvalidData(String.class);
            System.out.println("校驗(yàn)失敗,錯誤數(shù)據(jù):" + invalidData); // ""
        }

        // 2. 整數(shù)校驗(yàn)示例(校驗(yàn)大于0)
        GenericDataValidator<Integer> intValidator = new GenericDataValidator<>(Integer.class);
        List<Integer> intList = List.of(10, 20, -5, 30);
        Predicate<Integer> intRule = num -> num > 0;
        
        boolean intAllValid = intValidator.validateAll(intList, intRule);
        System.out.println("整數(shù)是否全部有效:" + intAllValid); // false
        List<Integer> invalidInt = GenericDataValidator.getInvalidData(intList, intRule);
        System.out.println("無效整數(shù):" + invalidInt); // [-5]
    }
}

3. 這個工具類的亮點(diǎn)

  • 通配符用得對:讀取數(shù)據(jù)用? extends T,寫入數(shù)據(jù)用? super T,嚴(yán)格遵守PECS原則,不會有編譯報錯;
  • 避開了所有類型擦除的坑:用類型令牌判斷類型、靜態(tài)方法自己定義泛型、非泛型異常存錯誤數(shù)據(jù)、用List代替數(shù)組;
  • 類型安全:所有操作都做了類型校驗(yàn),不會出現(xiàn)運(yùn)行時ClassCastException;
  • 實(shí)用性強(qiáng):我在項目里校驗(yàn)表單數(shù)據(jù)、批量入庫數(shù)據(jù)時,都是直接用這個工具類,改改校驗(yàn)規(guī)則就行。

四、我的泛型避坑清單

最后給大家整理個避坑清單,都是我踩過的教訓(xùn),記下來能少走很多彎路:

坑的場景為啥會坑我咋解決的
泛型數(shù)組創(chuàng)建失敗類型擦除后數(shù)組分不清泛型類型用List<List>代替泛型數(shù)組
instanceof判斷泛型類型運(yùn)行時沒泛型信息傳Class類型令牌,遍歷判斷元素類型
靜態(tài)變量用泛型靜態(tài)變量屬于類,沒法綁定實(shí)例泛型靜態(tài)方法自己定義泛型參數(shù),靜態(tài)變量用具體類型
catch捕獲泛型異常運(yùn)行時泛型信息被擦除,JVM分不清不用泛型異常,用Object存錯誤數(shù)據(jù),手動轉(zhuǎn)型
泛型方法重載沖突擦除后方法簽名一樣加Class參數(shù)區(qū)分,或改方法名
通配符寫入數(shù)據(jù)報錯違反PECS原則讀數(shù)據(jù)用? extends T,寫數(shù)據(jù)用? super T

五、總結(jié)

泛型這東西,真的不用死記硬背概念,核心就兩點(diǎn):

  • 通配符記住PECS原則——生產(chǎn)者(讀數(shù)據(jù))用extends,消費(fèi)者(寫數(shù)據(jù))用super;
  • 類型擦除記住“編譯期管類型,運(yùn)行時沒泛型”,遇到坑就用類型令牌、List代替數(shù)組、非泛型異常這些辦法補(bǔ)。

到此這篇關(guān)于Java泛型高級玩法之通配符、上下界與類型擦除避坑實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Java泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解

    Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解

    這篇文章主要為大家詳細(xì)介紹了Java基礎(chǔ)學(xué)習(xí)中構(gòu)造方法的概述及注意事項,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下
    2022-08-08
  • 解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問題

    解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問題

    這篇文章主要介紹了解決springboot整合cxf-jaxrs中json轉(zhuǎn)換的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 淺談Java 繼承接口同名函數(shù)問題

    淺談Java 繼承接口同名函數(shù)問題

    這篇文章主要介紹了淺談Java 繼承接口同名函數(shù)問題。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java 通過mave命令下載jar包的示例代碼

    Java 通過mave命令下載jar包的示例代碼

    這篇文章主要介紹了Java 通過mave命令下載jar的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • 關(guān)于@SpringBootApplication詳解

    關(guān)于@SpringBootApplication詳解

    這篇文章主要介紹了關(guān)于@SpringBootApplication的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Springboot FatJa原理機(jī)制源碼解析

    Springboot FatJa原理機(jī)制源碼解析

    這篇文章主要為大家介紹了Springboot FatJa原理機(jī)制源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程

    idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程

    這篇文章主要介紹了idea2020.3配置maven環(huán)境并配置Tomcat的詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Java中的stream流的概念解析及實(shí)際運(yùn)用總結(jié)

    Java中的stream流的概念解析及實(shí)際運(yùn)用總結(jié)

    流是指傳輸時的數(shù)據(jù),Java為流準(zhǔn)備了很多內(nèi)置類,尤其是IO輸入輸出流非常常用,這里我們來看一下Java中的stream流的概念解析及實(shí)際運(yùn)用總結(jié)
    2016-06-06
  • Java中MyBatis的結(jié)果映射詳解

    Java中MyBatis的結(jié)果映射詳解

    這篇文章主要介紹了Java中MyBatis的結(jié)果映射詳解,MyBatis 支持對各種單表查詢、關(guān)聯(lián)查詢等各種復(fù)雜查詢的結(jié)果進(jìn)行映射,MyBatis 是一款優(yōu)秀的持久層框架,它的強(qiáng)大之處正是 SQL 語句映射,這一章介紹常用的結(jié)果映射,需要的朋友可以參考下
    2023-08-08
  • mybatisPlus如何進(jìn)行連接問題

    mybatisPlus如何進(jìn)行連接問題

    MyBatisPlus提供了leftJoin方法支持左連接查詢,通過QueryWrapper構(gòu)建查詢條件,指定連接的表名和連接條件,通過select方法指定要查詢的字段
    2025-03-03

最新評論

安康市| 军事| 营口市| 新泰市| 大港区| 观塘区| 鲜城| 江安县| 革吉县| 安塞县| 称多县| 渭南市| 蒙城县| 黄骅市| 凤阳县| 循化| 河东区| 舞阳县| 尖扎县| 拜泉县| 库尔勒市| 同仁县| 于都县| 潼南县| 舟山市| 宣威市| 武安市| 阜新市| 灵宝市| 上杭县| 紫金县| 衡山县| 当阳市| 沅陵县| 邵阳县| 鄢陵县| 宝清县| 图片| 清水县| 康马县| 旺苍县|