diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/array/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/array/ArrayUtil.java index f53207dbc5..b50301258b 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/array/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/array/ArrayUtil.java @@ -1015,7 +1015,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { } // endregion - // region ----- filter + // region ----- edit and filter /** * 对每个数组元素执行指定操作,返回操作后的元素
diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/array/PrimitiveArrayUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/array/PrimitiveArrayUtil.java index c125ebe55a..501597f996 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/array/PrimitiveArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/array/PrimitiveArrayUtil.java @@ -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 + + /** + * 对每个数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static char[] edit(final char[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static byte[] edit(final byte[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static short[] edit(final short[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static int[] edit(final int[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static long[] edit(final long[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static float[] edit(final float[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static double[] edit(final double[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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数组元素执行指定操作,返回操作后的元素
+ * 这个Editor实现可以实现以下功能: + *
    + *
  1. 过滤出需要的对象,如果返回{@code null}则抛弃这个元素对象
  2. + *
  3. 修改元素对象,返回修改后的对象
  4. + *
+ * + * @param array 数组 + * @param editor 编辑器接口,为 {@code null}则返回原数组 + * @return 编辑后的数组 + * @since 7.0.0 + */ + public static boolean[] edit(final boolean[] array, final UnaryOperator editor) { + if (null == array || null == editor) { + return array; + } + + final List 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; + } + + /** + * 过滤数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤byte数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤short数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤int数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤long数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤float数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤double数组元素
+ * 保留 {@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 predicate) { + if (null == array || null == predicate) { + return array; + } + return edit(array, t -> predicate.test(t) ? t : null); + } + + /** + * 过滤boolean数组元素
+ * 保留 {@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 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 /** * 取最小值 diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/v7/core/lang/Validator.java index c4e47a2546..00f15ac4a0 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/lang/Validator.java @@ -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 { /** * 验证是否为可用邮箱地址
- * 邮箱地址限制长度为254个字符,参考:https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address/44317754 + * 邮箱地址限制长度为254个字符,参考: + * what-is-the-maximum-length-of-a-valid-email-address * * @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; } diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/text/StrUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/text/StrUtil.java index e8102292f2..bbdee2b25d 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/text/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/text/StrUtil.java @@ -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); + } + /** * 将对象转为字符串
* @@ -295,20 +308,21 @@ public class StrUtil extends CharSequenceUtil implements StrPool { * 该方法按Unicode code point进行反转,支持Unicode字符的正确反转 * 确保复杂字符不会被拆分,如表情符号等多字节字符 *

+ * * @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追加到结果中 diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/util/RandomUtil.java index 971cf0c677..3aca2dde56 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/util/RandomUtil.java @@ -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,区分大小写 * @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,不区分大小写 * @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 -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/array/PrimitiveArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/array/PrimitiveArrayUtilTest.java new file mode 100644 index 0000000000..601086d7d6 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/v7/core/array/PrimitiveArrayUtilTest.java @@ -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, "当移除不存在的单个字符时,应返回原数组"); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/util/RandomUtilTest.java index b4100e2aed..1683ba9832 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/util/RandomUtilTest.java @@ -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(); } } diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/util/TestRandomUtilByDoubao.java b/hutool-core/src/test/java/cn/hutool/v7/core/util/TestRandomUtilByDoubao.java new file mode 100644 index 0000000000..37d8b8de8f --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/v7/core/util/TestRandomUtilByDoubao.java @@ -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 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 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 list = Arrays.asList(1, 2, 3, 4, 5); + final List randomEles = RandomUtil.randomEles(list, 3); + assertEquals(3, randomEles.size()); + for (final Integer num : randomEles) { + assertTrue(list.contains(num)); + } + } + + @Test + void testRandomPick() { + // 测试随机选取不重复位置的元素 + final List list = Arrays.asList("a", "b", "c", "d", "e"); + final List picked = RandomUtil.randomPick(list, 3); + assertEquals(3, picked.size()); + // 验证选取的元素都是原列表中的 + for (final String s : picked) { + assertTrue(list.contains(s)); + } + + // 测试选取数量大于等于列表长度的情况 + final List 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 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 list = Arrays.asList("1", "2", "3", "4", "5", "1", "2"); + final Set randomSet = RandomUtil.randomEleSet(list, 3); + assertEquals(3, randomSet.size()); + // 验证元素都是原列表中的去重元素 + final Set 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 obj1 = new WeightObj<>("A", 1); + final WeightObj obj2 = new WeightObj<>("B", 9); + final List> weightObjs = Arrays.asList(obj1, obj2); + + // 测试数组形式的权重随机 + final WeightRandomSelector selector1 = RandomUtil.weightRandom(weightObjs.toArray(new WeightObj[0])); + // 测试迭代器形式的权重随机 + final WeightRandomSelector 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); + } +}