## 7. 其它工具类 ### 7.1. 数组工具类(ArrayTools) | 方法 | 描述 | | --- | --- | | `isEmpty` | 判断数组是否为空 | | `isNotEmpty` | 判断数组是否不为空 | | `isAllElementsNotNull` | 判断数组中所有元素是否不为空 | | `concat` | 拼接数组 | | `repeat` | 重复数组中的元素 | | `fill` | 填充数组 | | `indexOf` | 获取元素在数组中的索引 | | `lastIndexOf` | 获取元素最后出现在数组中的索引 | | `contains` | 判断数组中是否包含某个元素 | ### 7.2. 断言工具类(AssertTools) `AssertTools` 不封装过多判断逻辑,鼓励充分使用项目中的工具类对数据进行判断: ```java AssertTools.checkArgument(StringUtils.hasText(str), "The argument cannot be blank."); AssertTools.checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty."); AssertTools.checkCondition(!CollectionUtils.isEmpty(roles), () -> new InvalidInputException("The roles cannot be empty.")); AssertTools.checkCondition(RegexTools.matches(email, PatternConsts.EMAIL), "must be a well-formed email address"); ``` ### 7.3. 枚举工具 #### ~~7.3.1 枚举类(Enumeration)(已废弃)~~ ~~`Enumeration` 的实现来自于 .net 社区。因为 C# 本身的枚举不带行为,所以继承自 `Enumeration` 类,以实现带行为的枚举常量。~~ **~~但 Java 的枚举可以带行为,故大多数情况下不需要这种设计。~~** #### 7.3.2 Enum 工具类(EnumTools) 用于枚举的 ordinal 和枚举值的转换等操作。 由于不推荐使用枚举的 ordinal,**故大多数方法已废弃**。更推荐的实现是枚举实现 `IWithCode` 之类的接口,在枚举中提供枚举值和枚举码的转换。 ### 7.4. ID 生成器 #### 7.4.1. ID 生成器(IdGenerator) - 提供了 `UUID` 相关的方法 | 方法 | 描述 | | --- | --- | | newUuid | 获取新的 `UUID` | | uuidString | 获取新的 UUID 字符串 | | simpleUuidString | 获取新的 UUID 字符串(无连接符) | | toSimpleString | 将 `UUID` 转换为无连接符的字符串 | - 使用 `IdWorker` *(来自 **Seata** 的雪花算法的变种)* 生成分布式唯一 ID #### 7.4.2. IdWorker 来自 [Apache Seata](https://seata.apache.org/) 的 [`org.apache.seata.common.util.IdWorker`](https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java),是雪花算法的变种。 详细介绍参考以下文章: - [Seata基于改良版雪花算法的分布式UUID生成器分析](https://seata.apache.org/zh-cn/blog/seata-analysis-UUID-generator) - [关于新版雪花算法的答疑](https://seata.apache.org/zh-cn/blog/seata-snowflake-explain) - [在开源项目中看到一个改良版的雪花算法,现在它是你的了。](https://juejin.cn/post/7264387737276203065) - [关于若干读者,阅读“改良版雪花算法”后提出的几个共性问题的回复。](https://juejin.cn/post/7265516484029743138) #### 7.4.3. SnowflakeIdGenerator `SnowflakeIdGenerator` 是原版的雪花算法的实现 ### 7.5. 树构建器(TreeBuilder) `TreeBuilder` 是一个树构建器,用于将列表数据构建为树结构。 `TreeBuilder` 构造器的入参: - **identityGetter**: 从节点中获取其标识的逻辑 - **parentIdentityGetter**: 获取父节点标识的逻辑 - **addChild**: 添加子节点的逻辑 - **defaultComparator**: 默认的 Comparator,用于排序 > **注意:`TreeBuilder` 的 `buildTree` 方法,会直接更改列表中的节点。设计初衷是将查询到的列表,构建成为树结构之后直接返回给前端,如果需要,请在调用之前做深拷贝,然后再将深拷贝的结果作为入参传入。** 以下示例演示 `TreeBuilder` 的使用: #### 7.5.1. 处理相对复杂的 entity 假设有如下的实体类: ```java @AllArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode @ToString class Menu implements Serializable { protected final @Getter String parentMenuCode; protected final @Getter String menuCode; protected final @Getter String title; protected final @Getter int orderNum; private static final long serialVersionUID = 20240917181424L; } @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) class MenuItem extends Menu { private final @Getter String url; private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) { super(parentMenuCode, menuCode, title, orderNum); this.url = url; } static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) { return new MenuItem(parentMenuCode, menuCode, title, url, orderNum); } static MenuItem of(String menuCode, String title, String url, int orderNum) { return new MenuItem(null, menuCode, title, url, orderNum); } private static final long serialVersionUID = 20240917181910L; } @ToString(callSuper = true) class MenuList extends Menu { private List children; private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) { super(parentMenuCode, menuCode, title, orderNum); } static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) { return new MenuList(parentMenuCode, menuCode, title, orderNum); } static MenuList of(String menuCode, String title, int orderNum) { return new MenuList(null, menuCode, title, orderNum); } @SuppressWarnings("unused") static MenuList of(String menuCode, String title, Iterable children, int orderNum) { return of(null, menuCode, title, children, orderNum); } static MenuList of(String parentMenuCode, String menuCode, String title, Iterable children, int orderNum) { final MenuList instance = of(parentMenuCode, menuCode, title, orderNum); children.forEach(instance::addChild); return instance; } public void addChild(Menu child) { if (this.children == null) { this.children = Lists.newArrayList(); } this.children.add(child); } private static final long serialVersionUID = 20240917181917L; } ``` 其中,`Menu` 表示菜单节点,其子类 `MenuItem` 表示菜单项,在树中作为叶子节点,另一子类 `MenuList` 表示菜单列表,其子菜单放在 `children` 字段中。`MenuList` 提供了 `addChild` 方法用于将子菜单添加到 `children` 中。 使用以下方式构建并使用 `TreeBuilder`: ```java // 创建 TreeBuilder TreeBuilder treeBuilder = new TreeBuilder<>( // getMenuCode 方法获取节点标识 Menu::getMenuCode, // getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty() menu -> Optional.ofNullable(menu.getParentMenuCode()), // addChild 方法用于将子节点添加到父节点的 children 中 MenuList::addChild, // 默认的 Comparator,使用 orderNum 进行排序 Comparator.comparing(Menu::getOrderNum)); // 需要的话进行深拷贝 List clonedMenus = menus.stream().map(ObjectUtil::clone).collect(Collectors.toList()); // 按照创建时设置的逻辑,构建树结构 List result = treeBuilder.buildTree(clonedMenus); ``` #### 7.5.2. 处理 POJO `TreeBuilder` 也可以处理 POJO,只需要自定义 `TreeBuilder` 所需的入参即可。 ```java // POJO @Data class Menu implements Serializable { private final String parentMenuCode; private final String menuCode; private final String title; private final int orderNum; private final String url; private List children; private static final long serialVersionUID = 1298482252210272617L; } ``` 使用以下方式构建并使用 `TreeBuilder`: ```java // 创建 TreeBuilder TreeBuilder treeBuilder = new TreeBuilder<>( // getMenuCode 方法获取节点标识 Menu::getMenuCode, // getParentMenuCode 方法获取父节点标识,如果父节点不存在,返回 Optional.empty() menu -> Optional.ofNullable(menu.getParentMenuCode()), // 自定义 addChild 逻辑 (menuList, child) -> { List children = menuList.getChildren(); if (children == null) { children = Lists.newArrayList(); menuList.setChildren(children); } children.add(child); }, // 默认的 Comparator,使用 orderNum 进行排序 Comparator.comparing(Menu::getOrderNum)); // 按照创建时设置的逻辑,构建树结构 List result = treeBuilder.buildTree(clonedMenus); ``` ### 7.6. Ref `Ref` 包装了一个值,表示对该值的应用。 C# 中允许通过 ref 参数修饰符,将值返回给调用端: ```csharp void Method(ref int refArgument) { refArgument = refArgument + 44; } int number = 1; Method(ref number); Console.WriteLine(number); // Output: 45 ``` `Ref` 使 Java 可以达到类似的效果,如: ```java void method(Ref refArgument) { refArgument.transformValue(i -> i + 44); } Ref number = Ref.of(1); method(number); System.out.println(number.getValue()); // Output: 45 ``` 当一个方法需要产生多个结果时,无法有多个返回值,可以使用 `Ref` 作为参数传入,方法内部修改 `Ref` 的值。 调用方在调用方法之后,使用 `getValue()` 获取结果。 ```java String method(Ref intRefArgument, Ref strRefArgument) { intRefArgument.transformValue(i -> i + 44); strRefArgument.setValue("Hello " + strRefArgument.getValue()); return "Return string"; } Ref number = Ref.of(1); Ref str = Ref.of("Java"); String result = method(number, str); System.out.println(number.getValue()); // Output: 45 System.out.println(str.getValue()); // Output: Hello Java System.out.println(result); // Output: Return string ``` ### 7.7 其它 - **`BigDecimals`**: BigDecimal 工具 - **`Numbers`**: 数字工具 - **`OptionalTools`**: Optional 工具 - **`RandomTools`**: 随机工具 - **`RegexTools`**: 正则工具 - **`StringTools`**: 字符串工具 - **`ZipTools`**: zip 工具