1. 概述

在 Java 中将字符串转换为 double 类型时,我们通常会使用 Double.parseDouble(String value) 方法。这个方法可以把一个表示浮点数的字符串(例如 "2.0")解析成对应的原始 double 值。

和大多数方法调用一样,传入 null 是危险操作,极有可能在运行时抛出 NullPointerException

本文重点探讨在调用 Double.parseDouble 之前如何安全地检查 null 值。我们会先从纯 Java 实现入手,再介绍几个常用第三方库的解决方案,帮你避免踩坑。


2. 为什么要检查 null?

先来看如果不做检查会怎样。我们分别测试空字符串和 null 输入。

❌ 空字符串导致 NumberFormatException

double emptyString = Double.parseDouble("");

执行结果:

Exception in thread "main" java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    ...

❌ null 导致 NullPointerException

double nullString = Double.parseDouble(null);

执行结果:

Exception in thread "main" java.lang.NullPointerException
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1838)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    ...

⚠️ 虽然异常处理是正常流程的一部分,但像 NumberFormatExceptionNullPointerException 这类 unchecked 异常,通常是由于编码疏忽导致的。我们应该在调用前主动防御,而不是依赖异常捕获来兜底


3. 使用核心 Java 实现检查

3.1 原生 Java 判断

最简单直接的方式:手动判断 null 或空字符串。

private static double parseStringToDouble(String value) {
    return value == null || value.isEmpty() ? Double.NaN : Double.parseDouble(value);
}
  • 如果输入为 null 或空字符串,返回 Double.NaN
  • 否则正常解析

进一步优化:支持自定义默认值

private static double parseStringToDouble(String value, double defaultValue) {
    return value == null || value.isEmpty() ? defaultValue : Double.parseDouble(value);
}

测试用例:

assertThat(parseStringToDouble("1", 2.0d)).isEqualTo(1.0d);
assertThat(parseStringToDouble(null, 1.0d)).isEqualTo(1.0d);
assertThat(parseStringToDouble("", 1.0d)).isEqualTo(1.0d);

✅ 简单粗暴,无需依赖,适合轻量级场景。


3.2 使用 Optional

Java 8+ 推荐使用 Optional 来表达可能缺失的值,语义更清晰。

private static Optional<Double> parseStringToOptionalDouble(String value) {
    return value == null || value.isEmpty() 
        ? Optional.empty() 
        : Optional.of(Double.valueOf(value));
}

调用方式更灵活:

// 判断是否存在
parseStringToOptionalDouble("2").isPresent();

// 获取值或提供默认
parseStringToOptionalDouble("1.0").orElse(2.0d); 
parseStringToOptionalDouble(null).orElse(2.0d); 
parseStringToOptionalDouble("").orElse(2.0d);

✅ 优势:

  • 明确表达“可能无值”的语义
  • 链式调用友好
  • 避免 null 在业务逻辑中蔓延

⚠️ 注意:返回的是 Optional<Double> 而不是 double,需根据调用方是否需要原始类型做取舍。


4. 第三方库方案

4.1 Google Guava

Maven 依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

使用 Doubles.tryParse

Doubles.tryParse(MoreObjects.firstNonNull("1.0", "2.0")); // 返回 1.0
Doubles.tryParse(MoreObjects.firstNonNull(null, "2.0"));   // 返回 2.0

⚠️ 注意坑点:Doubles.tryParse("") 返回 null,而不是抛异常。虽然避免了 NumberFormatException,但你仍需处理 null —— 等于把问题往后推了

Doubles.tryParse(""); // 返回 null

✅ 适合配合 MoreObjects.firstNonNull 做 fallback,但对空字符串不敏感。


4.2 Apache Commons Lang - NumberUtils

Maven 依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

NumberUtils.toDouble 提供两种重载:

NumberUtils.toDouble("1.0");           // 失败返回 0.0d
NumberUtils.toDouble("1.0", 1.0d);     // 失败返回指定默认值

处理边界情况:

assertThat(NumberUtils.toDouble("")).isEqualTo(0.0d);
assertThat(NumberUtils.toDouble(null)).isEqualTo(0.0d);

✅ 最大优点:**永远返回 double 原始类型,调用方无需担心 null**,适合需要“静默失败 + 默认值”的场景。


4.3 Vavr - 函数式 Try

Maven 依赖:

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.2</version>
</dependency>

使用 Vavr 的 Try 模拟函数式异常处理:

public static double tryStringToDouble(String value, double defaultValue) {
    return Try.of(() -> Double.parseDouble(value)).getOrElse(defaultValue);
}

测试:

assertThat(tryStringToDouble("1", 2.0d)).isEqualTo(1.0d);
assertThat(tryStringToDouble(null, 2.0d)).isEqualTo(2.0d);
assertThat(tryStringToDouble("", 2.0d)).isEqualTo(2.0d);

✅ 优势:

  • 函数式风格,异常被封装为值
  • Try 支持 map/flatMap 等操作,便于链式处理
  • Optional 类似但更强调“可能失败的计算”

⚠️ 缺点:引入函数式库可能“杀鸡用牛刀”,适合已使用 Vavr 的项目。


5. 总结

方案 返回类型 是否处理 null/empty 推荐场景
原生 Java double ✅ 手动判断 无依赖,简单直接
Optional Optional<Double> Java 8+,强调语义清晰
Guava Doubles.tryParse Double ⚠️ 不处理空字符串 已用 Guava,且输入可控
Commons Lang NumberUtils double 需要默认值,避免 null
Vavr Try double 函数式风格项目

推荐优先级

  1. 项目已引入 commons-lang3 → 直接用 NumberUtils.toDouble
  2. 否则 → 自定义方法 + 默认值 或 使用 Optional
  3. 函数式项目 → Vavr Try

所有示例代码已上传至 GitHub:https://github.com/example/java-number-parsing


原始标题:Check for null Before Calling Parse in Double.parseDouble