Java泛型高级玩法:通配符、上下界与类型擦除避坑实战(纯干货,附完整工具类)

做Java开发的朋友应该都有体会:泛型这东西入门容易,真要用到项目里------比如写个通用工具类、处理各种类型的集合时,动不动就踩坑。要么通配符用错编译报错,要么类型擦除导致运行时抛ClassCastException,要么想创建个泛型数组直接编译不过。

今天这篇文章,我不跟大家聊纯概念,就结合我实际开发中踩过的坑、写过的通用数据校验工具类,把泛型通配符(?、? extends T、? super T)的用法、类型擦除的坑,还有避坑的实操技巧,都讲清楚。保证都是实战干货,代码能直接跑,看完就能用到自己的项目里。

一、泛型通配符:3种写法的实战区别(PECS原则真的很好用)

泛型通配符的核心就是解决"不同类型集合怎么通用处理"的问题,很多人搞不懂 ?、? extends T、? super T的区别,其实记住PECS原则(生产者用Extends,消费者用Super)就够了。我结合自己写过的工具方法,给大家讲透每个通配符的用法和坑。

1. 无界通配符 ?:只做通用读取,别想着写数据

无界通配符?就是匹配任意类型的泛型,我一般只用在"只读取不写入"的通用工具里,比如打印任意集合、判断集合是否为空。

我当初踩过一个坑:用?定义的List,想往里面加个字符串,结果直接编译报错。后来才明白,编译器根本不知道这个List具体存的是啥类型,为了保证类型安全,除了null,啥都不让加。

实战场景:通用集合打印工具

需求很简单:写个方法,不管传过来的是List、List还是List<自定义对象>,都能打印里面的元素。

java 复制代码
import java.util.List;

/**
 * 无界通配符实战:通用集合打印工具
 * 快速打印集合,不用重复写循环
 */
public class WildcardDemo {
    // 无界通配符?,匹配任意类型的List
    public static void printList(List<?> list) {
        if (list == null || list.isEmpty()) {
            System.out.println("集合为空");
            return;
        }
        for (Object obj : list) {
            // 只能读成Object类型,因为不知道具体是啥类型
            System.out.println("元素值:" + obj);
        }
        
        // 这里是我踩过的坑:想加个字符串,编译直接报错
        // list.add("test"); // 编译错误:没法确定list的具体类型,写入会乱套
        // 唯一能加的只有null,因为null是所有类型的子类
        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); // 正常打印整数列表
    }
}

为啥这么写?

无界通配符的核心就是"通用只读",比如打印、统计长度、判空这些操作,不用关心集合里具体存的是啥,用?就够了。但千万别想着往里面写数据,除了null,写啥都报错。

2. 上界通配符 ? extends T:只读取,不写入(生产者场景)

? extends T的意思是"匹配T或者T的子类",我一般用在"从集合里读数据"的场景,比如给Integer、Long、Double这些数值类型的集合求和------这些集合都是"生产"数值的,所以用extends

我之前犯过一个错:用? extends Number的List,想往里加个Integer,结果编译报错。现在想通了,编译器不知道这个List是存Integer、Long还是Double,要是加了Integer,万一原本是Long的List,不就乱套了?

实战场景:数值集合求和工具

需求:写个方法,能给Integer、Long、Double的List求和,返回double类型结果。

java 复制代码
import java.util.List;

/**
 * 上界通配符实战:数值集合求和
 * 做报表统计时经常用这个方法,不用给每种数值类型都写一遍求和
 */
public class UpperBoundDemo {
    // 上界通配符:匹配Number或其子类(Integer、Long、Double都算)
    public static double sum(List<? extends Number> numberList) {
        double total = 0.0;
        for (Number num : numberList) {
            // 读数据:所有子类都能转成Number,安全
            total += num.doubleValue();
        }
        
        // 踩坑点:想加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("整数列表求和:" + sum(intList)); // 输出6.0
        System.out.println("小数列表求和:" + sum(doubleList)); // 输出4.0
    }
}

核心要点
? extends T就是"生产者"------集合里的元素都是T的子类,能安全读成T类型,但绝对不能写。比如求和、导出数据、遍历取值这些场景,用这个通配符准没错。

3. 下界通配符 ? super T:只写入,读取只能拿Object(消费者场景)

? super T的意思是"匹配T或者T的父类",我一般用在"往集合里写数据"的场景,比如批量往List里插Integer------不管这个List是List、List还是List,都能插,因为这些都是Integer的父类。

这里也有个坑:用? super T的List读数据时,只能读成Object类型,想转成T类型会报错。因为编译器不知道这个List是存T的哪个父类,没法确定类型。

实战场景:数据批量插入工具

需求:写个方法,能往任意"能存Integer"的集合里批量插整数。

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 下界通配符实战:数据批量插入
 * 用这个方法往不同类型的集合里插数据
 */
public class LowerBoundDemo {
    // 下界通配符:匹配Integer或其父类(Number、Object)
    public static void batchAddInteger(List<? super Integer> list, int count) {
        for (int i = 1; i <= count; i++) {
            // 写数据:Integer能存到任意父类集合里,安全
            list.add(i);
        }
        
        // 读数据:只能拿到Object类型,这是坑点
        for (Object obj : list) {
            System.out.println("插入的元素:" + obj);
        }
        
        // 我踩过的坑:想直接读成Integer,编译报错
        // for (Integer num : list) { // 编译错误:list可能是Number或Object类型,没法转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
    }
}

核心要点
? super T就是"消费者"------集合能接收T类型的数据,所以写数据绝对安全,但读数据只能拿Object。比如批量插入、批量赋值、数据入库这些场景,就用这个通配符。

4. PECS原则速查表

通配符类型 能干嘛 我常用的场景 读写注意点
? 匹配任意类型 打印、判空、统计长度 读:只能拿Object;写:只能加null
? extends T 匹配T或其子类 求和、导出数据、遍历取值 读:能转T;写:啥都不能加(除了null)
? super T 匹配T或其父类 批量插入、批量赋值、数据入库 读:只能拿Object;写:能加T类型

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

很多人不知道,泛型其实是Java的"语法糖"------编译的时候,编译器会把所有泛型信息都擦除掉,换成Object或者限定类型。这就导致了一堆坑,我给大家列几个我踩过的高频坑,一个个说怎么解决。

1. 先搞懂类型擦除是啥

编译的时候,编译器会把泛型代码改成"原始代码":

  • List会被擦成List,读数据时自动加(String)强制转换;
  • List<? extends Number>会被擦成List;
  • 泛型类、泛型方法里的都会被擦成Object(有上限就擦成上限类型)。

举个例子,咱们写的泛型代码:

java 复制代码
// 编译前
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); // 自动加了强制转换

看出来了吧?泛型只在编译期管类型安全,运行时JVM根本不知道泛型是啥,这就是所有坑的根源。

2. 我踩过的5个高频坑(附解决方案)

坑1:泛型数组创建失败(最常见的坑)

我当初想创建一个List[]数组,结果编译直接报错;后来想强行转类型,运行时又抛ClassCastException。

踩坑代码

java 复制代码
/**
 * 我踩过的坑:泛型数组创建失败
 */
public class TypeErasureDemo1 {
    public static void main(String[] args) {
        // 坑1:直接创建泛型数组,编译报错
        // List<String>[] strArr = new List<String>[10]; // 编译错误:Generic array creation
        
        // 坑2:强行转类型,运行时报错
        List<String>[] strArr2 = (List<String>[]) new List[10];
        strArr2[0] = new ArrayList<String>();
        // 运行时坑:数组实际是List[],能存任意List
        List<Integer> intList = new ArrayList<>();
        intList.add(123);
        strArr2[0] = (List<String>) intList; // 编译过了,运行时抛ClassCastException
    }
}

为什么?

数组是"协变"的(比如String[]是Object[]的子类),但泛型是"不变"的(List不是List的子类)。类型擦除后,JVM分不清List[]和List[],所以编译器直接不让创建泛型数组。

我的解决方案

别用数组,改用List<List>!这是我现在最常用的办法,简单又安全。

java 复制代码
/**
 * 我的解决方案:用List代替泛型数组
 */
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>直接编译报错,不会有运行时问题
        // List<Integer> intList = List.of(1);
        // strListContainer.add(intList); // 编译错误,类型对不上
    }
}

坑2:用instanceof判断泛型类型

我当初想判断一个List是不是List,写了instanceof List,结果编译报错。

踩坑代码

java 复制代码
/**
 * 我踩过的坑: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,但不知道存的是啥");
        }
    }
}

为什么?

运行时泛型信息已经被擦除了,JVM只知道是List,不知道是List还是List,所以instanceof根本判断不了。

我的解决方案

传个Class类型令牌,遍历集合判断每个元素的类型。

java 复制代码
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:泛型类里定义静态泛型变量

我当初在泛型类里写了个static T staticValue,结果编译直接报错,后来才知道静态变量和泛型实例没关系。

踩坑代码

java 复制代码
/**
 * 我踩过的坑:泛型类里的静态变量不能用泛型
 */
public class TypeErasureDemo3<T> {
    // 坑:静态变量用T,编译报错
    // private static T staticValue; // Compile error: Cannot make a static reference to the non-static type T
    
    // 勉强能写,但没意义
    private static List<?> staticList = new ArrayList<>();
}

为什么?

静态变量属于"类",不是属于"实例"。比如我创建TypeErasureDemo3和TypeErasureDemo3,这两个实例共享同一个静态变量,编译器没法给静态变量绑定具体的T类型。

我的解决方案

静态方法单独定义泛型参数,别用类的泛型参数;静态变量要么用具体类型,要么用?。

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 我的解决方案:静态方法自己定义泛型参数
 */
public class TypeErasureDemo3Fix {
    // 静态变量:用具体类型,别用泛型
    private static List<String> staticStrList = new ArrayList<>();

    // 静态泛型方法:自己定义<T>,和类无关
    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<>();
        
        // 静态方法能支持不同类型,好用!
        addElement(strList, "Java");
        addElement(intList, 123);
        
        System.out.println(strList); // [Java]
        System.out.println(intList); // [123]
    }
}

坑4:try-catch捕获泛型异常

我当初想自定义一个泛型异常,然后用catch捕获GenericException,结果编译报错。

踩坑代码

java 复制代码
/**
 * 我踩过的坑:捕获泛型异常
 */
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();
        }
    }
}

为什么?

异常处理是运行时的事,类型擦除后,GenericException和GenericException都变成了GenericException,JVM分不清,所以编译器直接不让捕。

我的解决方案

别定义泛型异常,改用Object存错误数据,然后写个泛型方法取数据。

java 复制代码
/**
 * 我的解决方案:非泛型异常+泛型getter
 */
public class TypeErasureDemo4Fix {
    // 非泛型异常,用Object存任意类型数据
    static class DataException extends Exception {
        private Object errorData;
        
        public DataException(Object errorData) {
            this.errorData = errorData;
        }
        
        // 泛型方法:安全取数据,自己控制类型转换
        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); // 也能传整数
        } catch (DataException e) {
            // 安全取数据,不会乱转
            String errorMsg = e.getErrorData(String.class);
            System.out.println("错误信息:" + errorMsg);
        }
    }
}

坑5:泛型方法重载

我当初写了两个processData方法,一个接List,一个接List,结果编译报错,说方法签名重复。

踩坑代码

java 复制代码
/**
 * 我踩过的坑:泛型方法重载冲突
 */
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("处理整数");
    // }
}

为什么?

类型擦除后,List和List都变成了List,两个方法的签名都是processData(List),编译器分不清,所以不让重载。

我的解决方案

要么加个Class参数区分签名,要么直接改方法名。我一般加类型令牌,不用改方法名。

java 复制代码
import java.util.List;

/**
 * 我的解决方案:加类型令牌区分重载
 */
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("处理整数列表:" + list);
        } else {
            System.out.println("处理其他类型");
        }
    }

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

三、实战案例:通用数据校验工具类

光说不练假把式,我把上面的知识点都揉进一个"通用数据校验工具类"里,这是我实际项目里用来校验各种数据的,能避开前面说的所有坑,大家可以直接拿去改改用。

1. 需求说明

我做的这个工具类,要实现这几个功能:

  1. 校验任意类型的List里的元素是否符合规则(比如字符串非空、数值大于0);
  2. 批量把符合规则的数据写到另一个集合里;
  3. 能获取校验失败的数据,还能抛出带错误数据的异常;
  4. 避开泛型的各种坑,保证类型安全。

2. 完整代码

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * 通用数据校验工具类
 * 功能:校验任意类型数据、批量写入有效数据、返回无效数据、抛出带错误信息的异常
 * 避坑点:通配符用法、类型擦除、静态泛型、异常处理
 */
public class GenericDataValidator<T> {
    // 静态常量:不用泛型,避免静态泛型坑
    private static final String DEFAULT_ERROR_MSG = "数据校验失败";
    // 类型令牌:解决类型擦除后没法判断类型的问题
    private final Class<T> typeToken;

    // 构造方法:传入类型令牌,必须的!
    public GenericDataValidator(Class<T> typeToken) {
        this.typeToken = typeToken;
    }

    /**
     * 校验所有元素是否符合规则(上界通配符:只读取,生产者)
     * @param dataList 待校验集合(T或T的子类)
     * @param rule 校验规则(比如字符串非空、数值大于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) {
            // 先判断类型对不对,避免类型擦除导致的转换异常
            if (!typeToken.isInstance(data)) {
                throw new ClassCastException("集合里有不是" + typeToken.getName() + "类型的数据");
            }
            // 再校验规则
            if (!rule.test(data)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 批量写入有效数据(下界通配符:只写入,消费者)
     * @param targetList 目标集合(T或T的父类)
     * @param sourceList 源数据集合
     * @param rule 校验规则
     * @return 成功写入的数量
     */
    public int addValidData(List<? super T> targetList, List<? extends T> sourceList, Predicate<T> rule) {
        // 先判空,避免空指针
        Objects.requireNonNull(targetList, "目标集合不能为null");
        Objects.requireNonNull(sourceList, "源数据集合不能为null");
        
        int successCount = 0;
        for (T data : sourceList) {
            if (rule.test(data)) {
                targetList.add(data); // 下界通配符,写入安全
                successCount++;
            }
        }
        return successCount;
    }

    /**
     * 获取校验失败的数据(静态泛型方法:自己定义<T>,避开静态泛型坑)
     * @param dataList 待校验集合
     * @param rule 校验规则
     * @param <T> 数据类型
     * @return 失败的数据列表
     */
    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存错误数据(避开泛型异常坑)
     */
    public static class ValidationException extends RuntimeException {
        private final Object invalidData; // 存任意类型的错误数据

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

        // 泛型方法:安全获取错误数据
        public <E> E getInvalidData(Class<E> type) {
            if (type.isInstance(invalidData)) {
                return type.cast(invalidData);
            }
            throw new ClassCastException("错误数据类型不对,想要:" + type.getName());
        }
    }

    /**
     * 严格校验:失败就抛异常
     */
    public void strictValidate(List<? extends T> dataList, Predicate<T> rule) {
        List<T> invalidData = getInvalidData(dataList, rule);
        if (!invalidData.isEmpty()) {
            // 抛非泛型异常,带错误数据
            throw new ValidationException(DEFAULT_ERROR_MSG, invalidData.get(0));
        }
    }

    // 测试方法
    public static void main(String[] args) {
        // 1. 字符串校验示例(校验非空非空白)
        GenericDataValidator<String> strValidator = new GenericDataValidator<>(String.class);
        List<String> strList = List.of("Java", "", "泛型", "   ", "高级特性");
        // 校验规则:非空且非空白
        Predicate<String> strRule = s -> s != null && !s.isBlank();
        
        // 校验所有数据
        boolean allValid = strValidator.validateAll(strList, strRule);
        System.out.println("字符串是否全部有效:" + allValid); // false
        
        // 获取无效数据
        List<String> invalidStr = GenericDataValidator.getInvalidData(strList, strRule);
        System.out.println("无效字符串:" + invalidStr); // ["", "   "]
        
        // 批量写入有效数据到Object集合(下界通配符)
        List<Object> targetList = new ArrayList<>();
        int successCount = strValidator.addValidData(targetList, strList, strRule);
        System.out.println("成功写入有效字符串数量:" + successCount); // 3
        System.out.println("目标集合内容:" + targetList); // [Java, 泛型, 高级特性]
        
        // 严格校验,失败抛异常
        try {
            strValidator.strictValidate(strList, strRule);
        } catch (ValidationException e) {
            String invalidData = e.getInvalidData(String.class);
            System.out.println("校验失败,错误数据:" + invalidData); // ""
        }

        // 2. 整数校验示例(校验大于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("整数是否全部有效:" + intAllValid); // false
        List<Integer> invalidInt = GenericDataValidator.getInvalidData(intList, intRule);
        System.out.println("无效整数:" + invalidInt); // [-5]
    }
}

3. 这个工具类的亮点

  1. 通配符用得对:读取数据用? extends T,写入数据用? super T,严格遵守PECS原则,不会有编译报错;
  2. 避开了所有类型擦除的坑:用类型令牌判断类型、静态方法自己定义泛型、非泛型异常存错误数据、用List代替数组;
  3. 类型安全:所有操作都做了类型校验,不会出现运行时ClassCastException;
  4. 实用性强:我在项目里校验表单数据、批量入库数据时,都是直接用这个工具类,改改校验规则就行。

四、我的泛型避坑清单

最后给大家整理个避坑清单,都是我踩过的教训,记下来能少走很多弯路:

坑的场景 为啥会坑 我咋解决的
泛型数组创建失败 类型擦除后数组分不清泛型类型 用List<List>代替泛型数组
instanceof判断泛型类型 运行时没泛型信息 传Class类型令牌,遍历判断元素类型
静态变量用泛型 静态变量属于类,没法绑定实例泛型 静态方法自己定义泛型参数,静态变量用具体类型
catch捕获泛型异常 运行时泛型信息被擦除,JVM分不清 不用泛型异常,用Object存错误数据,手动转型
泛型方法重载冲突 擦除后方法签名一样 加Class参数区分,或改方法名
通配符写入数据报错 违反PECS原则 读数据用? extends T,写数据用? super T

五、总结

泛型这东西,真的不用死记硬背概念,核心就两点:

  1. 通配符记住PECS原则------生产者(读数据)用extends,消费者(写数据)用super;
  2. 类型擦除记住"编译期管类型,运行时没泛型",遇到坑就用类型令牌、List代替数组、非泛型异常这些办法补。
相关推荐
JIngJaneIL1 小时前
基于Java失物招领系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·vue
期待のcode1 小时前
MyBatis-Plus基本CRUD
java·spring boot·后端·mybatis
❀͜͡傀儡师1 小时前
maven 仓库的Central Portal Namespaces 怎么验证
java·maven·nexus
豐儀麟阁贵1 小时前
9.3获取字符串信息
java·开发语言·前端·算法
武子康1 小时前
大数据-175 Elasticsearch Term 精确查询与 Bool 组合实战:range/regexp/fuzzy 全示例
大数据·后端·elasticsearch
YJlio1 小时前
第9章小结(9.19):Sysinternals 安全工具组合拳与脚本清单
java·学习·平面
甜鲸鱼1 小时前
【Spring Boot + OpenAPI 3】开箱即用的 API 文档方案(SpringDoc + Knife4j)
java·spring boot·后端
foxsen_xia1 小时前
go(基础10)——错误处理
开发语言·后端·golang
robch1 小时前
Java后端优雅的实现分页搜索排序-架构2
java·开发语言·架构