This commit is contained in:
Looly
2026-01-23 13:19:19 +08:00
parent c860c6e704
commit 768cf399d5
8 changed files with 1457 additions and 77 deletions

View File

@@ -1015,7 +1015,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
}
// endregion
// region ----- filter
// region ----- edit and filter
/**
* 对每个数组元素执行指定操作,返回操作后的元素<br>

View File

@@ -22,8 +22,9 @@ import cn.hutool.v7.core.util.ObjUtil;
import cn.hutool.v7.core.util.RandomUtil;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* 原始类型数组工具类,原始类型数据包括:
@@ -1737,6 +1738,7 @@ public class PrimitiveArrayUtil {
return result;
}
// endregion
// region ----- removeEle
@@ -1861,6 +1863,568 @@ public class PrimitiveArrayUtil {
}
// endregion
// region ----- removeElements
/**
* 从字符数组中移除指定的多个字符
*
* @param array 原始字符数组
* @param elements 要移除的字符数组
* @return 移除指定字符后的新数组
* @since 7.0.0
*/
public static char[] removeElements(final char[] array, final char... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !ArrayUtil.contains(elements, t));
}
// ==================== byte 类型 ====================
/**
* 从byte数组中移除指定的多个byte元素
*
* @param array 原始byte数组
* @param elements 要移除的byte数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static byte[] removeElements(final byte[] array, final byte... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
// ==================== short 类型 ====================
/**
* 从short数组中移除指定的多个short元素
*
* @param array 原始short数组
* @param elements 要移除的short数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static short[] removeElements(final short[] array, final short... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
/**
* 从int数组中移除指定的多个int元素
*
* @param array 原始int数组
* @param elements 要移除的int数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static int[] removeElements(final int[] array, final int... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
/**
* 从long数组中移除指定的多个long元素
*
* @param array 原始long数组
* @param elements 要移除的long数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static long[] removeElements(final long[] array, final long... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
/**
* 从float数组中移除指定的多个float元素
*
* @param array 原始float数组
* @param elements 要移除的float数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static float[] removeElements(final float[] array, final float... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
/**
* 从double数组中移除指定的多个double元素
*
* @param array 原始double数组
* @param elements 要移除的double数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static double[] removeElements(final double[] array, final double... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
/**
* 从boolean数组中移除指定的多个boolean元素
*
* @param array 原始boolean数组
* @param elements 要移除的boolean数组
* @return 移除指定元素后的新数组
* @since 7.0.0
*/
public static boolean[] removeElements(final boolean[] array, final boolean... elements) {
if (isEmpty(array) || isEmpty(elements)) {
return array;
}
return filter(array, t -> !contains(elements, t));
}
// endregion
// region ----- edit and filter
/**
* 对每个数组元素执行指定操作,返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static char[] edit(final char[] array, final UnaryOperator<Character> editor) {
if (null == array || null == editor) {
return array;
}
final List<Character> resultList = new ArrayList<>(array.length);
Character modified;
for (final Character t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为char[]数组
final int size = resultList.size();
final char[] resultArray = new char[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个byte数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static byte[] edit(final byte[] array, final UnaryOperator<Byte> editor) {
if (null == array || null == editor) {
return array;
}
final List<Byte> resultList = new ArrayList<>(array.length);
Byte modified;
for (final Byte t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为byte[]数组
final int size = resultList.size();
final byte[] resultArray = new byte[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个short数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static short[] edit(final short[] array, final UnaryOperator<Short> editor) {
if (null == array || null == editor) {
return array;
}
final List<Short> resultList = new ArrayList<>(array.length);
Short modified;
for (final Short t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为short[]数组
final int size = resultList.size();
final short[] resultArray = new short[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个int数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static int[] edit(final int[] array, final UnaryOperator<Integer> editor) {
if (null == array || null == editor) {
return array;
}
final List<Integer> resultList = new ArrayList<>(array.length);
Integer modified;
for (final Integer t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为int[]数组
final int size = resultList.size();
final int[] resultArray = new int[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个long数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static long[] edit(final long[] array, final UnaryOperator<Long> editor) {
if (null == array || null == editor) {
return array;
}
final List<Long> resultList = new ArrayList<>(array.length);
Long modified;
for (final Long t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为long[]数组
final int size = resultList.size();
final long[] resultArray = new long[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个float数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static float[] edit(final float[] array, final UnaryOperator<Float> editor) {
if (null == array || null == editor) {
return array;
}
final List<Float> resultList = new ArrayList<>(array.length);
Float modified;
for (final Float t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为float[]数组
final int size = resultList.size();
final float[] resultArray = new float[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个double数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static double[] edit(final double[] array, final UnaryOperator<Double> editor) {
if (null == array || null == editor) {
return array;
}
final List<Double> resultList = new ArrayList<>(array.length);
Double modified;
for (final Double t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为double[]数组
final int size = resultList.size();
final double[] resultArray = new double[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 对每个boolean数组元素执行指定操作返回操作后的元素<br>
* 这个Editor实现可以实现以下功能
* <ol>
* <li>过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象</li>
* <li>修改元素对象,返回修改后的对象</li>
* </ol>
*
* @param array 数组
* @param editor 编辑器接口,为 {@code null}则返回原数组
* @return 编辑后的数组
* @since 7.0.0
*/
public static boolean[] edit(final boolean[] array, final UnaryOperator<Boolean> editor) {
if (null == array || null == editor) {
return array;
}
final List<Boolean> resultList = new ArrayList<>(array.length);
Boolean modified;
for (final Boolean t : array) {
modified = editor.apply(t);
if (null != modified) {
resultList.add(modified);
}
}
// 将List转换为boolean[]数组
final int size = resultList.size();
final boolean[] resultArray = new boolean[size];
for (int i = 0; i < size; i++) {
resultArray[i] = resultList.get(i);
}
return resultArray;
}
/**
* 过滤数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static char[] filter(final char[] array, final Predicate<Character> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤byte数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static byte[] filter(final byte[] array, final Predicate<Byte> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤short数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static short[] filter(final short[] array, final Predicate<Short> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤int数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static int[] filter(final int[] array, final Predicate<Integer> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤long数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static long[] filter(final long[] array, final Predicate<Long> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤float数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static float[] filter(final float[] array, final Predicate<Float> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤double数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static double[] filter(final double[] array, final Predicate<Double> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
/**
* 过滤boolean数组元素<br>
* 保留 {@link Predicate#test(Object)}为{@code true}的元素
*
* @param array 数组
* @param predicate 过滤器接口,用于定义过滤规则,为{@code null}则返回原数组
* @return 过滤后的数组
* @since 7.0.0
*/
public static boolean[] filter(final boolean[] array, final Predicate<Boolean> predicate) {
if (null == array || null == predicate) {
return array;
}
return edit(array, t -> predicate.test(t) ? t : null);
}
// endregion
// region ----- reverse
/**
@@ -2136,7 +2700,7 @@ public class PrimitiveArrayUtil {
}
// endregion
// region ------------------------------------------- min and max
// region ----- min and max
/**
* 取最小值

View File

@@ -512,6 +512,19 @@ public class Validator {
return NumberUtil.isNumber(value);
}
/**
* 辅助方法:检查字符串是否只包含数字
*
* @param str 字符串
* @return 是否只包含数字
*/
public static boolean isNumeric(final String str) {
if (StrUtil.isBlank( str)) {
return false;
}
return isMatchRegex("\\d+", str);
}
/**
* 是否包含数字
*
@@ -621,14 +634,15 @@ public class Validator {
/**
* 验证是否为可用邮箱地址<br>
* 邮箱地址限制长度为254个字符参考https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address/44317754
* 邮箱地址限制长度为254个字符参考
* <a href="https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address/44317754">what-is-the-maximum-length-of-a-valid-email-address</a>
*
* @param value 值
* @return true为可用邮箱地址
*/
public static boolean isEmail(final CharSequence value) {
final int codeLength = StrUtil.codeLength(value);
if(codeLength < 1 || codeLength > 254){
if (codeLength < 1 || codeLength > 254) {
return false;
}
@@ -1056,9 +1070,9 @@ public class Validator {
// final double doubleValue = value.doubleValue();
// return (doubleValue >= min.doubleValue()) && (doubleValue <= max.doubleValue());
// 通过 NumberUtil 转换为 BigDecimal使用 BigDecimal 进行比较以保留精度
BigDecimal valBd = NumberUtil.toBigDecimal(value);
BigDecimal minBd = NumberUtil.toBigDecimal(min);
BigDecimal maxBd = NumberUtil.toBigDecimal(max);
final BigDecimal valBd = NumberUtil.toBigDecimal(value);
final BigDecimal minBd = NumberUtil.toBigDecimal(min);
final BigDecimal maxBd = NumberUtil.toBigDecimal(max);
return valBd.compareTo(minBd) >= 0 && valBd.compareTo(maxBd) <= 0;
}

View File

@@ -128,6 +128,19 @@ public class StrUtil extends CharSequenceUtil implements StrPool {
// region ----- str
/**
* 将char[]转为String
*
* @param chars 字符数组
* @return 字符串, 如果给定值为null返回null
*/
public static String str(final char[] chars) {
if (null == chars) {
return null;
}
return new String(chars);
}
/**
* 将对象转为字符串<br>
*
@@ -295,20 +308,21 @@ public class StrUtil extends CharSequenceUtil implements StrPool {
* 该方法按Unicode code point进行反转支持Unicode字符的正确反转
* 确保复杂字符不会被拆分,如表情符号等多字节字符
* </p>
*
* @param str 被反转的字符串
* @return 反转后的字符串如果输入为null则返回null
* @since 5.8.43
*/
public static String reverseByCodePoint(String str) {
public static String reverseByCodePoint(final String str) {
if (null == str) {
return null;
}
//按Unicode code point方式进行反转处理
StringBuilder result = new StringBuilder();
final StringBuilder result = new StringBuilder();
for (int i = str.length(); i > 0; ) {
//获取指定位置前的code point
int codePoint = str.codePointBefore(i);
final int codePoint = str.codePointBefore(i);
//根据code point的字符数量调整索引位置
i -= Character.charCount(codePoint);
//将code point追加到结果中

View File

@@ -27,10 +27,12 @@ import java.util.concurrent.ThreadLocalRandom;
*/
public class RandomUtil {
// region ----- static Strings
/**
* 用于随机选的数字
*/
public static final String NUMBERS = "0123456789";
/**
* 用于随机选的大写字符
*/
@@ -51,6 +53,35 @@ public class RandomUtil {
* 用于随机选的字符和数字(包括大写和小写字母)
*/
public static final String LETTERS_NUMBERS = LETTERS + NUMBERS;
// endregion
// region ----- static chars
/**
* 用于随机选的数字
*/
private static final char[] NUMBERS_CHARS = NUMBERS.toCharArray();
/**
* 用于随机选的大写字符
*/
private static final char[] LETTERS_UPPER_CHARS = LETTERS_UPPER.toCharArray();
/**
* 用于随机选的小写字符
*/
private static final char[] LETTERS_LOWER_CHARS = LETTERS_LOWER.toCharArray();
/**
* 用于随机选的字符(包含大写和小写)
*/
private static final char[] LETTERS_CHARS = LETTERS.toCharArray();
/**
* 用于随机选的字符和数字(小写)
*/
private static final char[] LETTERS_NUMBERS_LOWER_CHARS = LETTERS_NUMBERS_LOWER.toCharArray();
/**
* 用于随机选的字符和数字(包括大写和小写字母)
*/
private static final char[] LETTERS_NUMBERS_CHARS = LETTERS_NUMBERS.toCharArray();
// endregion
// region ----- get or create Random
@@ -274,7 +305,7 @@ public class RandomUtil {
* @since 5.2.1
*/
public static int[] randomInts(final int length) {
final int[] range = NumberUtil.range(length);
final int[] range = NumberUtil.range(length - 1);
for (int i = 0; i < length; i++) {
final int random = randomInt(i, length);
ArrayUtil.swap(range, i, random);
@@ -648,7 +679,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomLettersAndNumbers(final int length) {
return randomString(LETTERS_NUMBERS, length);
return randomString(LETTERS_NUMBERS_CHARS, length);
}
/**
@@ -658,7 +689,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomLettersAndNumbersLower(final int length) {
return randomString(LETTERS_NUMBERS_LOWER, length);
return randomString(LETTERS_NUMBERS_LOWER_CHARS, length);
}
/**
@@ -669,34 +700,38 @@ public class RandomUtil {
* @since 4.0.13
*/
public static String randomLettersAndNumbersUpper(final int length) {
return randomString(LETTERS_NUMBERS_LOWER, length).toUpperCase();
return randomString(LETTERS_NUMBERS_LOWER_CHARS, length).toUpperCase();
}
/**
* 获得一个随机的字符串(只包含数字和字母) 并排除指定字符串
*
* @param length 字符串的长度
* @param elemData 要排除的字符串,如去重容易混淆的字符串oO0、lL1、q9Q、pP区分大小写
* @param elemData 要排除的字符串,如去重容易混淆的字符串oO0、lL1、q9Q、pP<b>区分</b>大小写
* @return 随机字符串
*/
public static String randomLettersAndNumbersWithoutStr(final int length, final String elemData) {
String baseStr = LETTERS_NUMBERS;
baseStr = StrUtil.removeAll(baseStr, elemData.toCharArray());
return randomString(baseStr, length);
char[] baseChars = LETTERS_NUMBERS_CHARS;
if(StrUtil.isNotBlank(elemData)){
baseChars = ArrayUtil.removeElements(baseChars, elemData.toCharArray());
}
return randomString(baseChars, length);
}
/**
* 获得一个随机的字符串(只包含数字和小写字母) 并排除指定字符串
*
* @param length 字符串的长度
* @param elemData 要排除的字符串,如去重容易混淆的字符串oO0、lL1、q9Q、pP不区分大小写
* @param elemData 要排除的字符串,如去重容易混淆的字符串oO0、lL1、q9Q、pP<b>不区分</b>大小写
* @return 随机字符串
* @since 5.8.28
*/
public static String randomLettersAndNumbersLowerWithoutStr(final int length, final String elemData) {
String baseStr = LETTERS_NUMBERS_LOWER;
baseStr = StrUtil.removeAll(baseStr, elemData.toLowerCase().toCharArray());
return randomString(baseStr, length);
char[] baseChars = LETTERS_NUMBERS_LOWER_CHARS;
if(StrUtil.isNotBlank(elemData)){
baseChars = ArrayUtil.removeElements(baseChars, elemData.toLowerCase().toCharArray());
}
return randomString(baseChars, length);
}
/**
@@ -706,7 +741,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomNumbers(final int length) {
return randomString(NUMBERS, length);
return randomString(NUMBERS_CHARS, length);
}
/**
@@ -716,7 +751,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomLetters(final int length) {
return randomString(LETTERS, length);
return randomString(LETTERS_CHARS, length);
}
/**
@@ -726,7 +761,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomLettersLower(final int length) {
return randomString(LETTERS_LOWER, length);
return randomString(LETTERS_LOWER_CHARS, length);
}
/**
@@ -736,7 +771,7 @@ public class RandomUtil {
* @return 随机字符串
*/
public static String randomLettersUpper(final int length) {
return randomString(LETTERS_UPPER, length);
return randomString(LETTERS_UPPER_CHARS, length);
}
/**
@@ -746,21 +781,20 @@ public class RandomUtil {
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomString(final String baseString, int length) {
if (StrUtil.isEmpty(baseString)) {
return StrUtil.EMPTY;
}
if (length < 1) {
length = 1;
}
public static String randomString(final String baseString, final int length) {
Assert.notEmpty(baseString, "Base string can not be empty !");
return randomString(baseString.toCharArray(), length);
}
final StringBuilder sb = new StringBuilder(length);
final int baseLength = baseString.length();
for (int i = 0; i < length; i++) {
final int number = randomInt(baseLength);
sb.append(baseString.charAt(number));
}
return sb.toString();
/**
* 获得一个随机的字符串
*
* @param baseString 随机字符选取的样本
* @param length 字符串的长度
* @return 随机字符串
*/
public static String randomString(final char[] baseString, final int length) {
return new String(randomChars(baseString, length));
}
// endregion
@@ -773,7 +807,7 @@ public class RandomUtil {
* @since 3.1.2
*/
public static char randomNumber() {
return randomChar(NUMBERS);
return randomChar(NUMBERS_CHARS);
}
/**
@@ -783,7 +817,7 @@ public class RandomUtil {
* @since 3.1.2
*/
public static char randomChar() {
return randomChar(LETTERS_NUMBERS_LOWER);
return randomChar(LETTERS_NUMBERS_LOWER_CHARS);
}
/**
@@ -796,6 +830,41 @@ public class RandomUtil {
public static char randomChar(final String baseString) {
return baseString.charAt(randomInt(baseString.length()));
}
/**
* 随机字符
*
* @param baseChars 随机字符选取的样本chars
* @return 随机字符
* @since 7.0.0
*/
public static char randomChar(final char[] baseChars) {
return baseChars[randomInt(baseChars.length)];
}
/**
* 获得一个随机的字符串
*
* @param baseChars 随机字符选取的样本
* @param length 字符串的长度
* @return 随机字符串
*/
public static char[] randomChars(final char[] baseChars, final int length) {
if(ArrayUtil.isEmpty(baseChars)){
throw new IllegalArgumentException("baseChars can not be empty !");
}
Assert.isTrue(length >= 1, "Length can not less than 1 !");
// 预先将基础字符串转为字符数组避免每次charAt调用
final int baseLength = baseChars.length;
final char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i] = baseChars[randomInt(baseLength)];
}
return result;
}
// endregion
// region ----- weightRandom
@@ -857,4 +926,4 @@ public class RandomUtil {
return DateUtil.offset(baseDate, dateField, randomInt(min, max));
}
// endregion
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) 2026 Hutool Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.core.array;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class PrimitiveArrayUtilTest {
/**
* 测试当原始数组为null时的情况
*/
@Test
public void testRemoveElementsWithNullArray() {
final char[] result = PrimitiveArrayUtil.removeElements((char[]) null, 'a', 'b');
assertNull(result, "当原始数组为null时应返回null");
}
/**
* 测试当原始数组为空数组时的情况
*/
@Test
public void testRemoveElementsWithEmptyArray() {
final char[] original = {};
final char[] elementsToRemove = {'a', 'b'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(original, result, "当原始数组为空数组时,应返回原数组");
}
/**
* 测试当要移除的元素数组为null时的情况
*/
@Test
public void testRemoveElementsWithNullElements() {
final char[] original = {'a', 'b', 'c'};
final char[] result = PrimitiveArrayUtil.removeElements(original, (char[]) null);
assertArrayEquals(original, result, "当要移除的元素数组为null时应返回原数组");
}
/**
* 测试当要移除的元素数组为空时的情况
*/
@Test
public void testRemoveElementsWithEmptyElements() {
final char[] original = {'a', 'b', 'c'};
final char[] elementsToRemove = {};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(original, result, "当要移除的元素数组为空时,应返回原数组");
}
/**
* 测试移除存在的字符元素
*/
@Test
public void testRemoveElementsExistingChars() {
final char[] original = {'a', 'b', 'c', 'd', 'e'};
final char[] elementsToRemove = {'b', 'd'};
final char[] expected = {'a', 'c', 'e'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "应正确移除指定的字符元素");
}
/**
* 测试移除不存在的字符元素
*/
@Test
public void testRemoveElementsNonExistingChars() {
final char[] original = {'a', 'b', 'c'};
final char[] elementsToRemove = {'x', 'y'};
final char[] expected = {'a', 'b', 'c'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "当要移除的字符不存在时,应返回原数组");
}
/**
* 测试移除所有元素的情况
*/
@Test
public void testRemoveElementsAllChars() {
final char[] original = {'a', 'b', 'c'};
final char[] elementsToRemove = {'a', 'b', 'c'};
final char[] expected = {};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "当移除所有元素时,应返回空数组");
}
/**
* 测试包含重复字符的情况
*/
@Test
public void testRemoveElementsWithDuplicates() {
final char[] original = {'a', 'b', 'a', 'c', 'b', 'd'};
final char[] elementsToRemove = {'a', 'b'};
final char[] expected = {'c', 'd'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "应正确处理重复字符的移除");
}
/**
* 测试包含特殊字符的情况
*/
@Test
public void testRemoveElementsWithSpecialChars() {
final char[] original = {' ', 'a', '\n', 'b', '\t', 'c'};
final char[] elementsToRemove = {' ', '\n'};
final char[] expected = {'a', 'b', '\t', 'c'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "应正确处理特殊字符的移除");
}
/**
* 测试移除单个字符的情况
*/
@Test
public void testRemoveElementsSingleChar() {
final char[] original = {'a', 'b', 'c', 'd'};
final char[] elementsToRemove = {'c'};
final char[] expected = {'a', 'b', 'd'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "应正确移除单个字符");
}
/**
* 测试移除一个不存在的单个字符
*/
@Test
public void testRemoveElementsSingleNonExistingChar() {
final char[] original = {'a', 'b', 'c'};
final char[] elementsToRemove = {'x'};
final char[] expected = {'a', 'b', 'c'};
final char[] result = PrimitiveArrayUtil.removeElements(original, elementsToRemove);
assertArrayEquals(expected, result, "当移除不存在的单个字符时,应返回原数组");
}
}

View File

@@ -18,17 +18,15 @@ package cn.hutool.v7.core.util;
import cn.hutool.v7.core.collection.ListUtil;
import cn.hutool.v7.core.convert.ConvertUtil;
import cn.hutool.v7.core.lang.Console;
import cn.hutool.v7.core.lang.Validator;
import cn.hutool.v7.core.math.NumberUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.math.RoundingMode;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.*;
@@ -52,12 +50,6 @@ public class RandomUtilTest {
assertTrue(randomDouble <= 1);
}
@Test
@Disabled
public void randomBooleanTest() {
Console.log(RandomUtil.randomBoolean());
}
@Test
public void randomNumberTest() {
final char c = RandomUtil.randomNumber();
@@ -94,8 +86,8 @@ public class RandomUtilTest {
@Test
public void randomStringOfLengthTest(){
final String s = RandomUtil.randomString("123", -1);
assertNotNull(s);
assertThrows(IllegalArgumentException.class, () -> RandomUtil.randomString("123", 0));
assertThrows(IllegalArgumentException.class, () -> RandomUtil.randomString("123", -1));
}
@Test
@@ -114,27 +106,17 @@ public class RandomUtilTest {
// 测试生成长度为5的数字字符串
final String result1 = RandomUtil.randomNumbers(5);
assertEquals(5, result1.length(), "生成的字符串长度应该等于指定长度");
assertTrue(isNumeric(result1), "生成的字符串应该只包含数字");
assertTrue(Validator.isNumeric(result1), "生成的字符串应该只包含数字");
// 测试生成长度为10的数字字符串
final String result2 = RandomUtil.randomNumbers(10);
assertEquals(10, result2.length(), "生成的字符串长度应该等于指定长度");
assertTrue(isNumeric(result2), "生成的字符串应该只包含数字");
assertTrue(Validator.isNumeric(result2), "生成的字符串应该只包含数字");
// 测试生成长度为1的数字字符串
final String result3 = RandomUtil.randomNumbers(1);
assertEquals(1, result3.length(), "生成的字符串长度应该等于指定长度");
assertTrue(isNumeric(result3), "生成的字符串应该只包含数字");
// 测试生成长度为0的数字字符串会被调整为1
final String result4 = RandomUtil.randomNumbers(0);
assertEquals(1, result4.length(), "当长度为0时应生成长度为1的字符串");
assertTrue(isNumeric(result4), "生成的字符串应该只包含数字");
// 测试生成长度为负数的数字字符串会被调整为1
final String result5 = RandomUtil.randomNumbers(-5);
assertEquals(1, result5.length(), "当长度为负数时应生成长度为1的字符串");
assertTrue(isNumeric(result5), "生成的字符串应该只包含数字");
assertTrue(Validator.isNumeric(result3), "生成的字符串应该只包含数字");
// 验证多次生成的结果都不相同(概率性验证)
final String result6 = RandomUtil.randomNumbers(8);
@@ -142,19 +124,121 @@ public class RandomUtilTest {
// 由于是随机生成,不能保证一定不同,但大部分情况下应该不同
// 所以我们主要验证它们都符合预期格式
assertEquals(8, result6.length(), "生成的字符串长度应该等于指定长度");
assertTrue(isNumeric(result6), "生成的字符串应该只包含数字");
assertTrue(Validator.isNumeric(result6), "生成的字符串应该只包含数字");
assertEquals(8, result7.length(), "生成的字符串长度应该等于指定长度");
assertTrue(isNumeric(result7), "生成的字符串应该只包含数字");
assertTrue(Validator.isNumeric(result7), "生成的字符串应该只包含数字");
}
/**
* 辅助方法:检查字符串是否只包含数字
* 测试 randomDouble(double limit, int scale, RoundingMode roundingMode) 方法正常功能
* 验证生成的随机数在正确范围内,并且精度符合要求
*/
private boolean isNumeric(final String str) {
if (str == null || str.isEmpty()) {
return false;
@Test
public void testRandomDoubleWithScaleAndRoundingMode() {
// 测试基本功能生成0到10之间的随机数保留2位小数四舍五入
final double result = RandomUtil.randomDouble(10.0, 2, RoundingMode.HALF_UP);
// 验证结果在[0, 10)范围内
assertTrue(result >= 0.0 && result < 10.0,
"随机数应该在[0, 10)范围内,实际值:" + result);
// 验证小数点后最多2位数字
final String resultStr = String.valueOf(result);
if (resultStr.contains(".")) {
final int decimalPlaces = resultStr.length() - resultStr.indexOf('.') - 1;
assertTrue(decimalPlaces <= 2,
"小数位数应该不超过2位实际" + decimalPlaces);
}
}
/**
* 测试不同的舍入模式
*/
@Test
public void testRandomDoubleWithDifferentRoundingModes() {
final double limit = 5.0;
// 测试向上舍入
final double upResult = RandomUtil.randomDouble(limit, 2, RoundingMode.UP);
assertTrue(upResult >= 0.0 && upResult < limit,
"UP模式下随机数应该在[0, 5)范围内");
// 测试向下舍入
final double downResult = RandomUtil.randomDouble(limit, 2, RoundingMode.DOWN);
assertTrue(downResult >= 0.0 && downResult < limit,
"DOWN模式下随机数应该在[0, 5)范围内");
// 测试四舍五入
final double halfUpResult = RandomUtil.randomDouble(limit, 2, RoundingMode.HALF_UP);
assertTrue(halfUpResult >= 0.0 && halfUpResult < limit,
"HALF_UP模式下随机数应该在[0, 5)范围内");
}
/**
* 测试精度为0的情况
*/
@Test
public void testRandomDoubleWithZeroScale() {
final double result = RandomUtil.randomDouble(10.0, 0, RoundingMode.HALF_UP);
// 验证结果在[0, 10)范围内
assertTrue(result >= 0.0 && result < 10.0,
"随机数应该在[0, 10)范围内");
// 验证没有小数部分(整数)
assertEquals(Math.floor(result), result, 0.001,
"精度为0时应该返回整数");
}
/**
* 测试舍入模式为null的情况应使用默认的HALF_UP模式
*/
@Test
public void testRandomDoubleWithNullRoundingMode() {
final double result = RandomUtil.randomDouble(10.0, 2, null);
// 验证结果在[0, 10)范围内
assertTrue(result >= 0.0 && result < 10.0,
"随机数应该在[0, 10)范围内");
// 验证小数位数不超过2位
final String resultStr = String.valueOf(result);
if (resultStr.contains(".")) {
final int decimalPlaces = resultStr.length() - resultStr.indexOf('.') - 1;
assertTrue(decimalPlaces <= 2,
"小数位数应该不超过2位实际" + decimalPlaces);
}
}
/**
* 测试边界情况limit为较小值
*/
@Test
public void testRandomDoubleWithSmallLimit() {
final double result = RandomUtil.randomDouble(0.001, 5, RoundingMode.HALF_UP);
// 验证结果在[0, 0.001)范围内
assertTrue(result >= 0.0 && result < 0.001,
"随机数应该在[0, 0.001)范围内,实际值:" + result);
}
/**
* 测试较大精度的情况
*/
@Test
public void testRandomDoubleWithHighPrecision() {
final double result = RandomUtil.randomDouble(100.0, 10, RoundingMode.HALF_UP);
// 验证结果在[0, 100)范围内
assertTrue(result >= 0.0 && result < 100.0,
"随机数应该在[0, 100)范围内");
// 验证结果经过了精度处理
final String resultStr = String.valueOf(result);
if (resultStr.contains(".")) {
final int decimalPlaces = resultStr.length() - resultStr.indexOf('.') - 1;
assertTrue(decimalPlaces <= 10,
"小数位数应该不超过10位实际" + decimalPlaces);
}
final Pattern pattern = Pattern.compile("\\d+");
return pattern.matcher(str).matches();
}
}

View File

@@ -0,0 +1,467 @@
/*
* Copyright (c) 2026 Hutool Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.v7.core.util;
import cn.hutool.v7.core.date.DateField;
import cn.hutool.v7.core.date.DateTime;
import cn.hutool.v7.core.date.DateUnit;
import cn.hutool.v7.core.date.DateUtil;
import cn.hutool.v7.core.lang.selector.WeightObj;
import cn.hutool.v7.core.lang.selector.WeightRandomSelector;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.*;
/**
* RandomUtil 单元测试类
* 基于 JUnit 5 实现,覆盖所有核心方法的功能验证
*/
public class TestRandomUtilByDoubao {
// ===================== 基础随机数生成测试 =====================
@Test
void testGetRandom() {
// 测试普通随机数生成器
final ThreadLocalRandom random = RandomUtil.getRandom();
assertNotNull(random);
// 测试安全随机数生成器
final Random secureRandom = RandomUtil.getRandom(true);
assertInstanceOf(SecureRandom.class, secureRandom);
// 测试非安全随机数生成器
final Random normalRandom = RandomUtil.getRandom(false);
assertInstanceOf(ThreadLocalRandom.class, normalRandom);
}
@Test
void testRandomBoolean() {
// 验证生成的布尔值在多次调用中会出现 true 和 false
boolean hasTrue = false;
boolean hasFalse = false;
for (int i = 0; i < 100; i++) {
final boolean result = RandomUtil.randomBoolean();
if (result) {
hasTrue = true;
} else {
hasFalse = true;
}
if (hasTrue && hasFalse) {
break;
}
}
assertTrue(hasTrue && hasFalse);
}
@Test
void testRandomBytes() {
// 测试随机字节数组长度
final int length = 10;
final byte[] bytes = RandomUtil.randomBytes(length);
assertEquals(length, bytes.length);
// 测试指定随机数生成器的字节数组
final byte[] bytesWithRandom = RandomUtil.randomBytes(length, new Random());
assertEquals(length, bytesWithRandom.length);
}
@SuppressWarnings("UnnecessaryUnicodeEscape")
@Test
void testRandomChinese() {
// 验证生成的字符是汉字Unicode 范围:\u4E00 - \u9FFF
final char chineseChar = RandomUtil.randomChinese();
assertTrue(chineseChar >= '\u4E00' && chineseChar <= '\u9FFF');
}
// ===================== 整数随机数测试 =====================
@SuppressWarnings("ConstantValue")
@Test
void testRandomInt() {
// 测试无参随机整数
final int randomInt = RandomUtil.randomInt();
assertTrue(randomInt >= Integer.MIN_VALUE && randomInt <= Integer.MAX_VALUE);
// 测试 [0, limit) 范围
final int limit = 100;
final int intWithLimit = RandomUtil.randomInt(limit);
assertTrue(intWithLimit >= 0 && intWithLimit < limit);
// 测试 [min, max) 范围
final int min = 10;
final int max = 20;
final int intInRange = RandomUtil.randomInt(min, max);
assertTrue(intInRange >= min && intInRange < max);
// 测试包含最大值的情况
final int intWithMax = RandomUtil.randomInt(min, max, true, true);
assertTrue(intWithMax >= min && intWithMax <= max);
// 测试不包含最小值的情况
final int intWithoutMin = RandomUtil.randomInt(min, max, false, false);
assertTrue(intWithoutMin > min && intWithoutMin < max);
// 测试随机索引数组
final int[] randomInts = RandomUtil.randomInts(5);
assertEquals(5, randomInts.length);
// 验证索引是 0-4 的乱序排列
final Set<Integer> indexSet = new HashSet<>();
for (final int num : randomInts) {
indexSet.add(num);
assertTrue(num >= 0 && num < 5);
}
assertEquals(5, indexSet.size());
}
// ===================== 长整数随机数测试 =====================
@SuppressWarnings("ConstantValue")
@Test
void testRandomLong() {
// 测试无参随机长整数
final long randomLong = RandomUtil.randomLong();
assertTrue(randomLong >= Long.MIN_VALUE && randomLong <= Long.MAX_VALUE);
// 测试 [0, limit) 范围
final long limit = 1000L;
final long longWithLimit = RandomUtil.randomLong(limit);
assertTrue(longWithLimit >= 0 && longWithLimit < limit);
// 测试 [min, max) 范围
final long min = 100L;
final long max = 200L;
final long longInRange = RandomUtil.randomLong(min, max);
assertTrue(longInRange >= min && longInRange < max);
// 测试包含最大值的情况
final long longWithMax = RandomUtil.randomLong(min, max, true, true);
assertTrue(longWithMax >= min && longWithMax <= max);
}
// ===================== 浮点数随机数测试 =====================
@Test
void testRandomFloat() {
// 测试 [0, 1) 范围
final float float01 = RandomUtil.randomFloat();
assertTrue(float01 >= 0 && float01 < 1);
// 测试 [0, limit) 范围
final float limit = 100.0f;
final float floatWithLimit = RandomUtil.randomFloat(limit);
assertTrue(floatWithLimit >= 0 && floatWithLimit < limit);
// 测试 [min, max) 范围
final float min = 10.0f;
final float max = 20.0f;
final float floatInRange = RandomUtil.randomFloat(min, max);
assertTrue(floatInRange >= min && floatInRange < max);
// 测试最小值等于最大值的情况
final float floatEqual = RandomUtil.randomFloat(min, min);
assertEquals(min, floatEqual);
}
// ===================== 双精度浮点数随机数测试 =====================
@Test
void testRandomDouble() {
// 测试 [0, 1) 范围
final double double01 = RandomUtil.randomDouble();
assertTrue(double01 >= 0 && double01 < 1);
// 测试 [0, limit) 范围
final double limit = 1000.0;
final double doubleWithLimit = RandomUtil.randomDouble(limit);
assertTrue(doubleWithLimit >= 0 && doubleWithLimit < limit);
// 测试 [min, max) 范围
final double min = 100.0;
final double max = 200.0;
final double doubleInRange = RandomUtil.randomDouble(min, max);
assertTrue(doubleInRange >= min && doubleInRange < max);
// 测试保留小数位数
final double doubleWithScale = RandomUtil.randomDouble(min, max, 2, RoundingMode.HALF_UP);
// 验证小数位数不超过 2 位
final String doubleStr = String.valueOf(doubleWithScale);
int dotIndex = doubleStr.indexOf('.');
if (dotIndex != -1) {
assertTrue(doubleStr.length() - dotIndex - 1 <= 2);
}
// 测试无范围保留小数
final double doubleScaleOnly = RandomUtil.randomDouble(3, RoundingMode.HALF_UP);
final String scaleStr = String.valueOf(doubleScaleOnly);
dotIndex = scaleStr.indexOf('.');
if (dotIndex != -1) {
assertTrue(scaleStr.length() - dotIndex - 1 <= 3);
}
}
// ===================== 高精度小数随机数测试 =====================
@Test
void testRandomBigDecimal() {
// 测试 [0, 1) 范围
final BigDecimal bd01 = RandomUtil.randomBigDecimal();
assertTrue(bd01.compareTo(BigDecimal.ZERO) >= 0 && bd01.compareTo(BigDecimal.ONE) < 0);
// 测试 [0, limit) 范围
final BigDecimal limit = new BigDecimal("100.0");
final BigDecimal bdWithLimit = RandomUtil.randomBigDecimal(limit);
assertTrue(bdWithLimit.compareTo(BigDecimal.ZERO) >= 0 && bdWithLimit.compareTo(limit) < 0);
// 测试 [min, max) 范围
final BigDecimal min = new BigDecimal("10.0");
final BigDecimal max = new BigDecimal("20.0");
final BigDecimal bdInRange = RandomUtil.randomBigDecimal(min, max);
assertTrue(bdInRange.compareTo(min) >= 0 && bdInRange.compareTo(max) < 0);
}
// ===================== 随机元素选取测试 =====================
@Test
void testRandomEle() {
// 测试列表随机元素
final List<String> list = Arrays.asList("A", "B", "C", "D");
final String randomEle = RandomUtil.randomEle(list);
assertTrue(list.contains(randomEle));
// 测试列表前 N 项随机元素
final String randomEleLimit = RandomUtil.randomEle(list, 2);
assertTrue(Arrays.asList("A", "B").contains(randomEleLimit));
// 测试数组随机元素
final String[] array = {"X", "Y", "Z"};
final String randomArrayEle = RandomUtil.randomEle(array);
assertTrue(Arrays.asList(array).contains(randomArrayEle));
// 测试数组前 N 项随机元素
final String randomArrayEleLimit = RandomUtil.randomEle(array, 2);
assertTrue(Arrays.asList("X", "Y").contains(randomArrayEleLimit));
}
@Test
void testRandomEles() {
// 测试随机获取多个元素(允许重复)
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
final List<Integer> randomEles = RandomUtil.randomEles(list, 3);
assertEquals(3, randomEles.size());
for (final Integer num : randomEles) {
assertTrue(list.contains(num));
}
}
@Test
void testRandomPick() {
// 测试随机选取不重复位置的元素
final List<String> list = Arrays.asList("a", "b", "c", "d", "e");
final List<String> picked = RandomUtil.randomPick(list, 3);
assertEquals(3, picked.size());
// 验证选取的元素都是原列表中的
for (final String s : picked) {
assertTrue(list.contains(s));
}
// 测试选取数量大于等于列表长度的情况
final List<String> pickedAll = RandomUtil.randomPick(list, 10);
assertEquals(list.size(), pickedAll.size());
assertEquals(new HashSet<>(list), new HashSet<>(pickedAll));
}
@Test
void testRandomPickInts() {
// 测试随机选取整数数组元素
final int[] seed = {1, 2, 3, 4, 5};
final int[] pickedInts = RandomUtil.randomPickInts(3, seed.clone());
assertEquals(3, pickedInts.length);
// 验证选取的元素都是原数组中的
final Set<Integer> seedSet = new HashSet<>();
for (final int num : seed) {
seedSet.add(num);
}
for (final int num : pickedInts) {
assertTrue(seedSet.contains(num));
}
// 测试选取数量超过种子长度的异常
Assertions.assertThrows(IllegalArgumentException.class, () -> {
RandomUtil.randomPickInts(10, seed);
});
}
@Test
void testRandomEleSet() {
// 测试随机获取不重复元素集合
final List<String> list = Arrays.asList("1", "2", "3", "4", "5", "1", "2");
final Set<String> randomSet = RandomUtil.randomEleSet(list, 3);
assertEquals(3, randomSet.size());
// 验证元素都是原列表中的去重元素
final Set<String> distinctSet = new HashSet<>(list);
for (final String s : randomSet) {
assertTrue(distinctSet.contains(s));
}
// 测试选取数量超过去重后长度的异常
Assertions.assertThrows(IllegalArgumentException.class, () -> {
RandomUtil.randomEleSet(list, 10);
});
}
// ===================== 随机字符串生成测试 =====================
@Test
void testRandomString() {
final int length = 8;
// 测试数字+大小写字母
final String lettersAndNumbers = RandomUtil.randomLettersAndNumbers(length);
assertEquals(length, lettersAndNumbers.length());
assertTrue(lettersAndNumbers.matches("[0-9A-Za-z]+"));
// 测试数字+小写字母
final String lower = RandomUtil.randomLettersAndNumbersLower(length);
assertEquals(length, lower.length());
assertTrue(lower.matches("[0-9a-z]+"));
// 测试数字+大写字母
final String upper = RandomUtil.randomLettersAndNumbersUpper(length);
assertEquals(length, upper.length());
assertTrue(upper.matches("[0-9A-Z]+"));
// 测试排除指定字符
final String withoutStr = RandomUtil.randomLettersAndNumbersWithoutStr(length, "0OlL1");
assertEquals(length, withoutStr.length());
Assertions.assertFalse(withoutStr.matches(".*[0OlL1].*"));
// 测试小写字母+数字排除指定字符
final String lowerWithout = RandomUtil.randomLettersAndNumbersLowerWithoutStr(length, "0OlL1");
assertEquals(length, lowerWithout.length());
Assertions.assertFalse(lowerWithout.matches(".*[0ol1].*"));
// 测试纯数字
final String numbers = RandomUtil.randomNumbers(length);
assertEquals(length, numbers.length());
assertTrue(numbers.matches("[0-9]+"));
// 测试纯字母
final String letters = RandomUtil.randomLetters(length);
assertEquals(length, letters.length());
assertTrue(letters.matches("[A-Za-z]+"));
// 测试纯小写字母
final String lettersLower = RandomUtil.randomLettersLower(length);
assertEquals(length, lettersLower.length());
assertTrue(lettersLower.matches("[a-z]+"));
// 测试纯大写字母
final String lettersUpper = RandomUtil.randomLettersUpper(length);
assertEquals(length, lettersUpper.length());
assertTrue(lettersUpper.matches("[A-Z]+"));
// 测试自定义字符集
final String custom = RandomUtil.randomString("abc123", length);
assertEquals(length, custom.length());
assertTrue(custom.matches("[abc123]+"));
// 测试空字符集异常
Assertions.assertThrows(IllegalArgumentException.class, () -> {
RandomUtil.randomString("", length);
});
}
// ===================== 随机字符生成测试 =====================
@Test
void testRandomChar() {
// 测试随机数字字符
final char numberChar = RandomUtil.randomNumber();
assertTrue(Character.isDigit(numberChar));
// 测试随机小写字母+数字字符
final char charLower = RandomUtil.randomChar();
assertTrue(Character.isDigit(charLower) || (charLower >= 'a' && charLower <= 'z'));
// 测试自定义字符集字符
final char customChar = RandomUtil.randomChar("ABC123");
assertTrue("ABC123".indexOf(customChar) != -1);
// 测试字符数组随机字符
final char[] chars = {'X', 'Y', 'Z'};
final char arrayChar = RandomUtil.randomChar(chars);
assertTrue(Arrays.asList('X', 'Y', 'Z').contains(arrayChar));
// 测试随机字符数组生成
final char[] randomChars = RandomUtil.randomChars(chars, 5);
assertEquals(5, randomChars.length);
for (final char c : randomChars) {
assertTrue(Arrays.asList('X', 'Y', 'Z').contains(c));
}
}
// ===================== 权重随机测试 =====================
@SuppressWarnings("unchecked")
@Test
void testWeightRandom() {
// 构建权重对象列表
final WeightObj<String> obj1 = new WeightObj<>("A", 1);
final WeightObj<String> obj2 = new WeightObj<>("B", 9);
final List<WeightObj<String>> weightObjs = Arrays.asList(obj1, obj2);
// 测试数组形式的权重随机
final WeightRandomSelector<String> selector1 = RandomUtil.weightRandom(weightObjs.toArray(new WeightObj[0]));
// 测试迭代器形式的权重随机
final WeightRandomSelector<String> selector2 = RandomUtil.weightRandom(weightObjs);
// 验证多次随机后,权重高的元素出现概率更高(概率性验证)
int countA = 0;
int countB = 0;
final int total = 10000;
for (int i = 0; i < total; i++) {
final String result = selector1.select();
if ("A".equals(result)) {
countA++;
} else if ("B".equals(result)) {
countB++;
}
}
// 验证 A 的出现概率约 10%B 约 90%(允许 ±2% 误差)
assertTrue(countA >= 800 && countA <= 1200);
assertTrue(countB >= 8800 && countB <= 9200);
}
// ===================== 随机日期测试 =====================
@Test
void testRandomDate() {
// 测试基于当天的随机天数
final DateTime randomDay = RandomUtil.randomDay(-5, 5);
// 验证日期在 [-5, 4] 天范围内(因为 max 不包含)
final long diff = DateUtil.betweenDay(DateUtil.now(), randomDay, true);
assertTrue(diff >= -5 && diff <= 4);
// 测试基于指定日期的随机时间
final DateTime baseDate = DateUtil.parse("2024-01-01 12:00:00");
final DateTime randomDate = RandomUtil.randomDate(baseDate, DateField.HOUR_OF_DAY, -2, 3);
// 验证小时偏移在 [-2, 2] 范围内
final long hourDiff = DateUtil.between(baseDate, randomDate, DateUnit.HOUR);
assertTrue(hourDiff >= -2 && hourDiff <= 2);
// 测试空基准日期
final DateTime randomDateWithNull = RandomUtil.randomDate(null, DateField.MINUTE, -10, 10);
assertNotNull(randomDateWithNull);
}
}