在Java项目中去理解通用工具为什么能通用,以及如何写出类似的工具类

java 复制代码
public static <T> String convert(String idString,
                                     Function<List<String>, List<T>> queryFunction,
                                     Function<T, String> idGetter,
                                     Function<T, String> nameGetter){
                                     //具体实现
                                     }

工具类原文链接:我用夸克网盘给你分享了「逗号分隔ID转名称」,链接:https://pan.quark.cn/s/5996dcb37237

这个convert方法是一个典型的通用工具方法,核心是通过「泛型」和「函数式接口」实现对不同场景的适配。我们可以从「泛型设计」「参数作用」「逻辑流程」三个角度拆解,理解它为什么能通用,以及如何写出类似的工具类。

一、先看懂:泛型 <T> 是啥?

方法定义中的 <T> 是「泛型参数」,你可以把它理解为一个"占位符",代表任意实体类类型 (比如SubjectUserDept等)。

  • 为什么需要它?

    工具类的目的是"通用",如果不写泛型,方法就只能固定处理某一种实体(比如只能处理Subject),无法复用。有了<T>,这个方法可以同时处理UserDept等任何实体,只要它们有"ID"和"名称"字段。

  • 举例:

    当处理学科时,T就是Subject;处理用户时,T就是User。方法会根据实际传入的类型自动适配,不需要重复写多个类似的方法。

二、再拆参数:3个Function到底在干嘛?

方法的4个参数中,后3个Function是核心,它们负责把"可变的逻辑"从工具类中抽离出来(让工具类只保留通用逻辑)。

我们用一个具体场景(把课程的"学科ID字符串"转"学科名称")来对应参数,就很容易理解:

参数 类型 作用 例子(学科场景)
idString String 原始输入:逗号分隔的ID字符串(比如"1,2,3") 课程的subjectInfo字段(存学科ID)
queryFunction Function<List<String>, List<T>> 传入"根据ID列表查实体"的逻辑 ids -> subjectMapper.selectByIds(ids)(查学科列表)
idGetter Function<T, String> 传入"从实体中取ID"的逻辑 Subject::getId(从学科实体中取ID)
nameGetter Function<T, String> 传入"从实体中取名称"的逻辑 Subject::getName(从学科实体中取名称)
java 复制代码
    public String getSubjectNames(TbCourse tbCourse) {
        String subjectInfo = tbCourse.getSubjectInfo(); // 逗号分隔的学科 ID 字符串

        return IdToNameConverter.convert(
                subjectInfo,
                // 1. 查询函数:根据 ID 列表查学科
                ids -> tbCourseMapper.selectByIds(ids),
                // 2. 从学科实体取 ID
                TbCourse::getId,
                // 3. 从学科实体取名称
                TbCourse::getName
        );
    }
关键:Function是"输入→输出"的转换器

Function<A, B> 是Java的函数式接口,代表"接收A类型参数,返回B类型结果"的转换规则。

比如 idGetterFunction<T, String>,意思是"接收一个T类型的实体(比如Subject),返回一个String类型的ID"。

三、逻辑流程:通用工具类的"不变"与"变"

这个方法的核心逻辑是固定的流程,但流程中的"具体操作"通过参数传入(即"变"的部分)。整体步骤如下:

复制代码
原始ID字符串 → 拆分ID列表 → 查实体列表(用queryFunction) → 建ID→名称映射(用idGetter和nameGetter) → 拼接名称字符串
分步拆解(结合学科场景):
  1. 处理空输入 :如果idStringnull或空,直接返回空字符串(避免空指针)。

    (通用逻辑:所有场景都需要处理空值)

  2. 拆分ID列表 :把"1,2,3"拆成["1", "2", "3"](去空格、过滤空值)。

    (通用逻辑:所有"逗号分隔ID"场景都需要拆分)

  3. 查询实体列表 :调用queryFunction,传入拆分后的ID列表,得到实体列表(比如List<Subject>)。

    (可变逻辑:查学科用subjectMapper,查用户用userMapper,由参数传入)

  4. 构建ID→名称映射 :遍历实体列表,用idGetter取ID、nameGetter取名称,存到Map中(比如{"1":"数学", "2":"语文"})。

    (可变逻辑:不同实体的ID和名称字段不同,由参数传入)

  5. 拼接名称字符串 :按原始ID顺序,从Map中取名称拼接(比如"数学, 语文")。

    (通用逻辑:所有场景都需要按原顺序拼接)

四、为什么这样写能成为"通用工具类"?

通用工具类的核心设计思路是:把"不变的通用逻辑"写死在工具类里,把"变化的具体逻辑"通过参数传进来

  • 不变的逻辑:拆分ID、处理空值、拼接名称(这些是所有"ID转名称"场景共有的)。
  • 变化的逻辑:查哪个表、ID字段叫什么、名称字段叫什么(这些因场景而异,通过参数传入)。

这种设计让一个方法能顶多个用,比如同样的convert方法,既能处理"学科ID转名称",也能处理"用户ID转用户名""部门ID转部门名",不需要重复写代码。

五、自己写通用工具类的3个关键步骤

  1. 识别"通用逻辑"和"可变逻辑"

    比如"ID转名称"中,拆分、拼接是通用的;查询、取字段是可变的。

  2. 用泛型<T>适配不同类型

    如果你需要处理多种实体(User、Dept等),用泛型代替具体类型,让工具类"不绑定"某一种实体。

  3. 用函数式接口传递可变逻辑

    Function(转换)、Predicate(判断)等接口,把可变的操作通过参数传入(就像把"具体做什么"告诉工具类)。

总结

这个convert方法之所以通用,是因为它没有硬编码任何具体业务(比如只查学科表),而是通过泛型适配不同实体,通过Function参数接收具体操作。理解了"泛型占位"和"函数传参"这两个点,就能慢慢写出灵活的通用工具类了。