Java泛型的使用案例:使用泛型写出通用方法

一直以来对于泛型的理解都不是很深刻,今天在解决一个问题时比较好地运用了一下泛型,记录一下用法和解决问题的思路

0x01 问题导入

在SpringMVC中经常会有查询菜单树的需求,即需要查询下图所示的菜单

image-20230825232455065

假设我们定义菜单实体类如下:

@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_menu")
@Accessors(chain = true)
public class Menu{
    //菜单ID
    @TableId
    private Long id;
    //菜单名称   
    private String menuName;
    //父菜单ID   
    private Long parentId;
    //显示顺序   
    private Integer orderNum;
    
    // 省略了一些不重要的属性,实体类根据数据库生成就行

    @TableField(exist = false)
    private List<Menu> children;

}

那么我们查询菜单树的接口怎么实现呢?

获取数据库中所有菜单可以直接调用MybatisPlus提供的CRUD接口,这里就不赘述了。

主要实现一下构建菜单树的逻辑:

// menus:所有的菜单项;parentId:菜单树第一层的父节点id,可以看作根节点id
private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) {
        return menus.stream()
                .filter(menu -> menu.getParentId().equals(parentId))	// 找出第一层菜单
                .map(menu -> menu.setChildren(getChildren(menu, menus)))	// getChildren中会递归构建菜单树
                .collect(Collectors.toList());		// 返回构建好的菜单树
    }

// 构建menu的子菜单
private List<Menu> getChildren(Menu menu, List<Menu> menus) {
        return menus.stream()
                .filter(m -> m.getParentId().equals(menu.getId()))	// 在menus找menu的子菜单
                .map(m->m.setChildren(getChildren(m, menus)))	// 递归调用查找下一层的子菜单
                .collect(Collectors.toList());	// 最后返回的就是以menus为根节点的菜单树
    }

// 使用方法
// menus是查询出来的所有菜单,0L是我设置的第一层节点的父节点ID
List<Menu> menuTree = buildMenuTree(menus, 0L);

现在我们已经实现了查询菜单树的功能,但现在这两个方法只能针对于Menu类构建树,那么如果有一天我们如果要查询用户树、文章树、标签树等等,岂不是又要重新写一遍这个逻辑代码?

这样显然不是很优雅,正确的打开方式是定义一个通用的方法,不管我们要查的是什么树,通通只要调用这个方法就ok

那么这个通用方法该如何实现呢?这里就需要依靠泛型啦

0x02 使用泛型写出通用方法

首先我们要知道,我们需要实现的方法中还调用了getId()、getParentId()、setChildren()这三个方法,如果直接写一个泛型方法是不行的,这里的解决方法是我们定义一个Tree接口,在接口中定义这三个方法,然后写泛型方法时限定泛型的类型为Tree接口的实现类,这样就可以啦

Tree接口:

public interface Tree<T> {
    Long getId();
    Long getParentId();
    // 注意这里也要用泛型,因为children的类型应该是实现类的类型,实现类是不确定的
    // 我这里方法的返回值为T是因为定义了链式方法@Accessors(chain = true)
    T setChildren(List<T> children);
}

然后我们写一下TreeUtils类,在类中定义生成树的方法,如下所示:

public class TreeUtils{
    // 注意这里的泛型类型参数必须与接口的实现类型保持一致,这样才符合泛型的约束规则
    public static  <T extends Tree<T>> List<T>  getChildren(T menu, List<T> menus){
        return menus.stream()
                .filter(m -> m.getParentId().equals(menu.getId()))
                .map(m -> {
                    m.setChildren(getChildren(m, menus));
                    return m;
                })
                .collect(Collectors.toList());
    }

    public static  <T extends MenuTree<T>> List<T> buildTree(List<T> menus, Long parentId){
        return menus.stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu->{
                    menu.setChildren(getChildren(menu, menus));
                    return menu;
                })
                .collect(Collectors.toList());
    }
}

然后我们让Menu类实现Tree接口

@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_menu")
@Accessors(chain = true)
public class Menu implements MenuTree<Menu>{
    //菜单ID
    @TableId
    private Long id;
    //菜单名称   
    private String menuName;
    //父菜单ID   
    private Long parentId;
    //显示顺序   
    private Integer orderNum;
    
    // 省略了一些不重要的属性,实体类根据数据库生成就行

    @TableField(exist = false)
    private List<Menu> children;

}

这样就可以使用TreeUtils.buildTree()方法代替原来的方法啦,同时只要实现了Tree接口的类都可以这样生成树类型,经验证是可以使用的

0x03 总结

以上就是一个泛型实现通用方法的例子,从这个例子中我们可以总结出以下经验:

  1. 代码中有些重复的业务逻辑可以写一个工具类统一实现
  2. 工具类中一般都需要使用泛型,如果可以直接使用普通的泛型类的话就很简单,但如果有些时候我们的泛型方法需要调用一些特定的方法,这时候我们可以写一个接口声明这些特定的方法,然后使用带约束的泛型来实现泛型方法,就如同我们上面的代码所示。当一个类想要调用我们所写的通用方法时,只需要实现接口即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值