diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/func/LambdaFactory.java b/hutool-core/src/main/java/org/dromara/hutool/core/func/LambdaFactory.java
index 139016b1e..40c94b76d 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/func/LambdaFactory.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/func/LambdaFactory.java
@@ -26,7 +26,7 @@ import java.util.Map;
/**
* 以类似反射的方式动态创建Lambda,在性能上有一定优势,同时避免每次调用Lambda时创建匿名内部类
- * TODO JDK9+存在兼容问题
+ * TODO JDK9+存在兼容问题,当参数为原始类型时报错
*
* @author nasodaengineer
*/
@@ -122,7 +122,6 @@ public class LambdaFactory {
final String invokeName = invokeMethod.getName();
final MethodType invokedType = MethodType.methodType(funcType);
- // 对入参做检查,原始类型转换为包装类型
final Class>[] paramTypes = invokeMethod.getParameterTypes();
final MethodType samMethodType = MethodType.methodType(invokeMethod.getReturnType(), paramTypes);
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java
new file mode 100644
index 000000000..0c5af9d8e
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberParser.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2023 looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package org.dromara.hutool.core.math;
+
+import org.dromara.hutool.core.text.StrUtil;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+/**
+ * 数字解析器
+ * 用于将字符串解析为对应的数字类型,支持包括:
+ *
+ * - 0开头的忽略开头的0
+ * - 空串返回0
+ * - NaN返回0
+ * - 其它情况按照10进制转换
+ * - .123形式返回0.123(按照小于0的小数对待)
+ *
+ *
+ *
+ * 构造时可选是否将NaN转为0,默认为true。
+ * 参考:https://stackoverflow.com/questions/5876369/why-does-casting-double-nan-to-int-not-throw-an-exception-in-java
+ *
+ *
+ * @author looly
+ * @since 6.0.0
+ */
+public class NumberParser {
+
+ /**
+ * 单例
+ */
+ public static final NumberParser INSTANCE = of(null);
+
+ /**
+ * 构建NumberParser
+ *
+ * @param locale 地域,{@code null}表示默认本地
+ * @return NumberParser
+ */
+ public static NumberParser of(final Locale locale) {
+ return of(locale, true);
+ }
+
+ /**
+ * 构建NumberParser
+ *
+ * @param locale 地域,{@code null}表示默认本地
+ * @param zeroIfNaN 如果用户传入NaN,是否转为0,如果为{@code false},则抛出{@link NumberFormatException}
+ * @return NumberParser
+ */
+ public static NumberParser of(final Locale locale, final boolean zeroIfNaN) {
+ return new NumberParser(locale, zeroIfNaN);
+ }
+
+ private static final String NaN = "NaN";
+
+ private final Locale locale;
+ private final boolean zeroIfNaN;
+
+ /**
+ * 构造
+ *
+ * @param locale 地域,{@code null}表示默认本地
+ * @param zeroIfNaN 如果用户传入NaN,是否转为0,如果为{@code false},则抛出{@link NumberFormatException}
+ */
+ public NumberParser(final Locale locale, final boolean zeroIfNaN) {
+ this.locale = locale;
+ this.zeroIfNaN = zeroIfNaN;
+ }
+
+ /**
+ * 解析转换数字字符串为int型数字,规则如下:
+ *
+ *
+ * 1、0x开头的视为16进制数字
+ * 2、0开头的忽略开头的0
+ * 3、其它情况按照10进制转换
+ * 4、空串返回0
+ * 5、.123形式返回0(按照小于0的小数对待)
+ * 6、123.56截取小数点之前的数字,忽略小数部分
+ * 7、科学计数法抛出NumberFormatException异常
+ *
+ *
+ * @param numberStr 数字字符串
+ * @return int
+ * @throws NumberFormatException 数字格式异常
+ */
+ public int parseInt(final String numberStr) throws NumberFormatException {
+ if (isBlankOrNaN(numberStr)) {
+ return 0;
+ }
+
+ if (StrUtil.containsIgnoreCase(numberStr, "E")) {
+ // 科学计数法忽略支持,科学计数法一般用于表示非常小和非常大的数字,这类数字转换为int后精度丢失,没有意义。
+ throw new NumberFormatException(StrUtil.format("Unsupported int format: [{}]", numberStr));
+ }
+
+ if (StrUtil.startWithIgnoreCase(numberStr, "0x")) {
+ // 0x04表示16进制数
+ return Integer.parseInt(numberStr.substring(2), 16);
+ }
+
+ try {
+ return Integer.parseInt(numberStr);
+ } catch (final NumberFormatException e) {
+ return doParse(numberStr).intValue();
+ }
+ }
+
+ /**
+ * 解析转换数字字符串为long型数字,规则如下:
+ *
+ *
+ * 1、0x开头的视为16进制数字
+ * 2、0开头的忽略开头的0
+ * 3、空串返回0
+ * 4、其它情况按照10进制转换
+ * 5、.123形式返回0(按照小于0的小数对待)
+ * 6、123.56截取小数点之前的数字,忽略小数部分
+ *
+ *
+ * @param numberStr 数字字符串
+ * @return long
+ */
+ public long parseLong(final String numberStr) {
+ if (isBlankOrNaN(numberStr)) {
+ return 0;
+ }
+
+ if (StrUtil.startWithIgnoreCase(numberStr, "0x")) {
+ // 0x04表示16进制数
+ return Long.parseLong(numberStr.substring(2), 16);
+ }
+
+ try {
+ return Long.parseLong(numberStr);
+ } catch (final NumberFormatException e) {
+ return doParse(numberStr).longValue();
+ }
+ }
+
+ /**
+ * 解析转换数字字符串为long型数字,规则如下:
+ *
+ *
+ * 1、0开头的忽略开头的0
+ * 2、空串返回0
+ * 3、其它情况按照10进制转换
+ * 4、.123形式返回0.123(按照小于0的小数对待)
+ *
+ *
+ * @param numberStr 数字字符串
+ * @return long
+ * @since 5.5.5
+ */
+ public float parseFloat(final String numberStr) {
+ if (isBlankOrNaN(numberStr)) {
+ return 0;
+ }
+
+ try {
+ return Float.parseFloat(numberStr);
+ } catch (final NumberFormatException e) {
+ return doParse(numberStr).floatValue();
+ }
+ }
+
+ /**
+ * 解析转换数字字符串为long型数字,规则如下:
+ *
+ *
+ * 1、0开头的忽略开头的0
+ * 2、空串返回0
+ * 3、其它情况按照10进制转换
+ * 4、.123形式返回0.123(按照小于0的小数对待)
+ * 5、NaN返回0
+ *
+ *
+ * @param numberStr 数字字符串
+ * @return double
+ */
+ public double parseDouble(final String numberStr) {
+ if (isBlankOrNaN(numberStr)) {
+ return 0;
+ }
+
+ try {
+ return Double.parseDouble(numberStr);
+ } catch (final NumberFormatException e) {
+ return doParse(numberStr).doubleValue();
+ }
+ }
+
+ /**
+ * 将指定字符串转换为{@link Number} 对象
+ * 此方法不支持科学计数法
+ *
+ *
+ * - 空白符和NaN转换为0
+ * - 0x开头使用16进制解析为Long类型
+ *
+ *
+ *
+ * 需要注意的是,在不同Locale下,数字的表示形式也是不同的,例如:
+ * 德国、荷兰、比利时、丹麦、意大利、罗马尼亚和欧洲大多地区使用`,`区分小数
+ * 也就是说,在这些国家地区,1.20表示120,而非1.2。
+ *
+ *
+ * @param numberStr 数字字符串
+ * @return Number对象
+ * @throws NumberFormatException 包装了{@link ParseException},当给定的数字字符串无法解析时抛出
+ */
+ public Number parseNumber(final String numberStr) throws NumberFormatException {
+ if (isBlankOrNaN(numberStr)) {
+ // 在JDK9+中,NaN的处理逻辑是返回0,此处保持一致
+ return 0;
+ }
+
+ // 16进制
+ if (StrUtil.startWithIgnoreCase(numberStr, "0x")) {
+ // 0x04表示16进制数
+ return Long.parseLong(numberStr.substring(2), 16);
+ }
+
+ return doParse(numberStr);
+ }
+
+ /**
+ * 使用{@link NumberFormat} 完成数字解析
+ * 如果为{@link DecimalFormat}
+ *
+ * @return 数字
+ */
+ private Number doParse(final String numberStr) {
+ Locale locale = this.locale;
+ if (null == locale) {
+ locale = Locale.getDefault(Locale.Category.FORMAT);
+ }
+ try {
+ final NumberFormat format = NumberFormat.getInstance(locale);
+ if (format instanceof DecimalFormat) {
+ // issue#1818@Github
+ // 当字符串数字超出double的长度时,会导致截断,此处使用BigDecimal接收
+ ((DecimalFormat) format).setParseBigDecimal(true);
+ }
+ return format.parse(numberStr);
+ } catch (final ParseException e) {
+ final NumberFormatException nfe = new NumberFormatException(e.getMessage());
+ nfe.initCause(e);
+ throw nfe;
+ }
+ }
+
+ /**
+ * 是否空白串或者NaN
+ * 如果{@link #zeroIfNaN}为{@code false},则抛出{@link NumberFormatException}
+ *
+ * @return 是否空白串或者NaN
+ */
+ private boolean isBlankOrNaN(final String numberStr) throws NumberFormatException {
+ if (StrUtil.isBlank(numberStr)) {
+ return true;
+ }
+
+ if (NaN.equals(numberStr)) {
+ if (zeroIfNaN) {
+ return true;
+ } else {
+ throw new NumberFormatException("Can not parse NaN when 'zeroIfNaN' is false!");
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java
index 510f31461..45fdab884 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java
@@ -1425,31 +1425,13 @@ public class NumberUtil {
* 7、科学计数法抛出NumberFormatException异常
*
*
- * @param number 数字,支持0x开头、0开头和普通十进制
+ * @param numberStr 数字,支持0x开头、0开头和普通十进制
* @return int
* @throws NumberFormatException 数字格式异常
* @since 4.1.4
*/
- public static int parseInt(final String number) throws NumberFormatException {
- if (StrUtil.isBlank(number)) {
- return 0;
- }
-
- if (StrUtil.containsIgnoreCase(number, "E")) {
- // 科学计数法忽略支持,科学计数法一般用于表示非常小和非常大的数字,这类数字转换为int后精度丢失,没有意义。
- throw new NumberFormatException(StrUtil.format("Unsupported int format: [{}]", number));
- }
-
- if (StrUtil.startWithIgnoreCase(number, "0x")) {
- // 0x04表示16进制数
- return Integer.parseInt(number.substring(2), 16);
- }
-
- try {
- return Integer.parseInt(number);
- } catch (final NumberFormatException e) {
- return parseNumber(number).intValue();
- }
+ public static int parseInt(final String numberStr) throws NumberFormatException {
+ return NumberParser.INSTANCE.parseInt(numberStr);
}
/**
@@ -1493,25 +1475,12 @@ public class NumberUtil {
* 6、123.56截取小数点之前的数字,忽略小数部分
*
*
- * @param number 数字,支持0x开头、0开头和普通十进制
+ * @param numberStr 数字,支持0x开头、0开头和普通十进制
* @return long
* @since 4.1.4
*/
- public static long parseLong(final String number) {
- if (StrUtil.isBlank(number)) {
- return 0L;
- }
-
- if (StrUtil.startWithIgnoreCase(number, "0x")) {
- // 0x04表示16进制数
- return Long.parseLong(number.substring(2), 16);
- }
-
- try {
- return Long.parseLong(number);
- } catch (final NumberFormatException e) {
- return parseNumber(number).longValue();
- }
+ public static long parseLong(final String numberStr) {
+ return NumberParser.INSTANCE.parseLong(numberStr);
}
/**
@@ -1550,20 +1519,12 @@ public class NumberUtil {
* 4、.123形式返回0.123(按照小于0的小数对待)
*
*
- * @param number 数字,支持0x开头、0开头和普通十进制
+ * @param numberStr 数字,支持0x开头、0开头和普通十进制
* @return long
* @since 5.5.5
*/
- public static float parseFloat(final String number) {
- if (StrUtil.isBlank(number)) {
- return 0f;
- }
-
- try {
- return Float.parseFloat(number);
- } catch (final NumberFormatException e) {
- return parseNumber(number).floatValue();
- }
+ public static float parseFloat(final String numberStr) {
+ return NumberParser.INSTANCE.parseFloat(numberStr);
}
/**
@@ -1599,22 +1560,15 @@ public class NumberUtil {
* 2、空串返回0
* 3、其它情况按照10进制转换
* 4、.123形式返回0.123(按照小于0的小数对待)
+ * 5、NaN返回0
*
*
- * @param number 数字,支持0x开头、0开头和普通十进制
- * @return long
+ * @param numberStr 数字,支持0x开头、0开头和普通十进制
+ * @return double
* @since 5.5.5
*/
- public static double parseDouble(final String number) {
- if (StrUtil.isBlank(number)) {
- return 0D;
- }
-
- try {
- return Double.parseDouble(number);
- } catch (final NumberFormatException e) {
- return parseNumber(number).doubleValue();
- }
+ public static double parseDouble(final String numberStr) {
+ return NumberParser.INSTANCE.parseDouble(numberStr);
}
/**
@@ -1652,7 +1606,7 @@ public class NumberUtil {
* @since 4.1.15
*/
public static Number parseNumber(final String numberStr) throws NumberFormatException {
- return parseNumber(numberStr, Locale.getDefault(Locale.Category.FORMAT));
+ return NumberParser.INSTANCE.parseNumber(numberStr);
}
/**
@@ -1671,24 +1625,7 @@ public class NumberUtil {
* @throws NumberFormatException 包装了{@link ParseException},当给定的数字字符串无法解析时抛出
*/
public static Number parseNumber(final String numberStr, final Locale locale) throws NumberFormatException {
- if (StrUtil.startWithIgnoreCase(numberStr, "0x")) {
- // 0x04表示16进制数
- return Long.parseLong(numberStr.substring(2), 16);
- }
-
- try {
- final NumberFormat format = NumberFormat.getInstance(locale);
- if (format instanceof DecimalFormat) {
- // issue#1818@Github
- // 当字符串数字超出double的长度时,会导致截断,此处使用BigDecimal接收
- ((DecimalFormat) format).setParseBigDecimal(true);
- }
- return format.parse(numberStr);
- } catch (final ParseException e) {
- final NumberFormatException nfe = new NumberFormatException(e.getMessage());
- nfe.initCause(e);
- throw nfe;
- }
+ return NumberParser.of(locale).parseNumber(numberStr);
}
// endregion
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/ObjUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/ObjUtil.java
index df98ed89d..d8cb4030a 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/util/ObjUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/ObjUtil.java
@@ -56,7 +56,7 @@ public class ObjUtil {
public static boolean equals(final Object obj1, final Object obj2) {
if (obj1 instanceof Number && obj2 instanceof Number) {
return NumberUtil.equals((Number) obj1, (Number) obj2);
- } else if(ArrayUtil.isArray(obj1) && ArrayUtil.isArray(obj2)){
+ } else if (ArrayUtil.isArray(obj1) && ArrayUtil.isArray(obj2)) {
return ArrayUtil.equals(obj1, obj2);
}
return Objects.equals(obj1, obj2);
@@ -68,8 +68,8 @@ public class ObjUtil {
* @param obj1 对象1
* @param obj2 对象2
* @return 是否不等
- * @since 3.0.7
* @see #equals(Object, Object)
+ * @since 3.0.7
*/
public static boolean notEquals(final Object obj1, final Object obj2) {
return !equals(obj1, obj2);
@@ -229,12 +229,12 @@ public class ObjUtil {
*
* @param obj 被判断的对象
* @return 是否为空,如果类型不支持,返回false
- * @since 4.5.7
* @see StrUtil#isEmpty(CharSequence)
* @see MapUtil#isEmpty(Map)
* @see IterUtil#isEmpty(Iterable)
* @see IterUtil#isEmpty(Iterator)
* @see ArrayUtil#isEmpty(Object)
+ * @since 4.5.7
*/
@SuppressWarnings("rawtypes")
public static boolean isEmpty(final Object obj) {
@@ -244,9 +244,9 @@ public class ObjUtil {
if (obj instanceof CharSequence) {
return StrUtil.isEmpty((CharSequence) obj);
- } else if(obj instanceof Collection){
- return CollUtil.isEmpty((Collection)obj);
- }else if (obj instanceof Map) {
+ } else if (obj instanceof Collection) {
+ return CollUtil.isEmpty((Collection) obj);
+ } else if (obj instanceof Map) {
return MapUtil.isEmpty((Map) obj);
} else if (obj instanceof Iterable) {
return IterUtil.isEmpty((Iterable) obj);
@@ -264,13 +264,15 @@ public class ObjUtil {
*
* @param obj 被判断的对象
* @return 是否不为空,如果类型不支持,返回true
- * @since 4.5.7
* @see #isEmpty(Object)
+ * @since 4.5.7
*/
public static boolean isNotEmpty(final Object obj) {
return !isEmpty(obj);
}
+ // region ----- defaultIf
+
/**
* 如果给定对象为{@code null}返回默认值
*
{@code
@@ -340,6 +342,7 @@ public class ObjUtil {
final T source, final Function super T, ? extends R> handler, final R defaultValue) {
return isNull(source) ? defaultValue : handler.apply(source);
}
+ // endregion
/**
* 克隆对象
@@ -451,8 +454,8 @@ public class ObjUtil {
* @param obj 被检查的实体对象
* @param index 泛型类型的索引号,即第几个泛型类型
* @return {@link Class}
- * @since 3.0.8
* @see ClassUtil#getTypeArgument(Class, int)
+ * @since 3.0.8
*/
public static Class> getTypeArgument(final Object obj, final int index) {
return ClassUtil.getTypeArgument(obj.getClass(), index);
@@ -468,8 +471,8 @@ public class ObjUtil {
*
* @param obj Bean对象
* @return 转换后的字符串
- * @since 3.2.0
* @see Convert#toStr(Object)
+ * @since 3.2.0
*/
public static String toString(final Object obj) {
if (null == obj) {
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/util/NumberUtilTest.java
index b390970c6..1b6dbb4c5 100644
--- a/hutool-core/src/test/java/org/dromara/hutool/core/util/NumberUtilTest.java
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/util/NumberUtilTest.java
@@ -5,6 +5,8 @@ import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -698,18 +700,26 @@ public class NumberUtilTest {
// -------------------------- Parse failed -----------------------
Assertions.assertNull(NumberUtil.parseDouble("abc", null));
-
Assertions.assertNull(NumberUtil.parseDouble("a123.33", null));
-
Assertions.assertNull(NumberUtil.parseDouble("..123", null));
-
Assertions.assertEquals(1233D, NumberUtil.parseDouble(StrUtil.EMPTY, 1233D));
// -------------------------- Parse success -----------------------
Assertions.assertEquals(123.33D, NumberUtil.parseDouble("123.33a", null));
-
Assertions.assertEquals(0.123D, NumberUtil.parseDouble(".123", null));
+ }
+ @Test
+ void naNToIntTest() {
+ Assertions.assertEquals(0, Double.valueOf(Double.NaN).intValue());
+ }
+
+ @Test
+ @EnabledForJreRange(max = JRE.JAVA_8)
+ void numberFormatTest() {
+ Assertions.assertThrows(ParseException.class, ()->{
+ NumberFormat.getInstance().parse("NaN");
+ });
}
}
diff --git a/pom.xml b/pom.xml
index b0b081811..d3d9e40e4 100755
--- a/pom.xml
+++ b/pom.xml
@@ -139,9 +139,6 @@
org.apache.maven.plugins
maven-surefire-plugin
3.0.0
-
- --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
-
org.apache.maven.plugins