一、核心概念

❗ 重要结论前置:

Java 中 Double 类没有缓存机制(DoubleCache

IntegerLongBoolean 等包装类不同,Double 不会缓存任何值,每次通过自动装箱或 Double.valueOf() 创建对象时,都会新建一个 Double 对象(除了 Double.valueOf(double) 在某些 JVM 实现中可能对 0.0 有极有限优化,但这不是规范要求,不可依赖)。

为什么 Double 没有缓存?

  1. 值域连续且巨大

    • double 是 64 位浮点数,可表示的数值范围极广(约 ±1.8×10³⁰⁸),且包括无数个中间值(如 0.1, 0.01, 3.14159...)。
    • 无法像 Integer(-128~127)那样缓存“常用值”。
  2. 精度问题

    • double 存在精度误差(如 0.1 无法精确表示),缓存语义难以定义。
  3. JVM 规范未要求

    • Java 语言规范只要求 BooleanByteCharacter(\u0000-\u007f)、ShortIntegerLong 在特定范围内缓存。
    • FloatDouble 明确不包含在内

二、操作步骤(非常详细)

尽管没有缓存,但理解 Double 的装箱与对象创建过程至关重要。

步骤 1:使用 Double.valueOf(double) 创建对象

Double d1 = Double.valueOf(1.0);
Double d2 = Double.valueOf(1.0);

System.out.println(d1 == d2);     // ❌ 输出: false(不是同一个对象)
System.out.println(d1.equals(d2)); // ✅ 输出: true(值相等)
  • 过程
    1. 调用 Double.valueOf(1.0)
    2. JVM 直接创建一个新的 Double 对象,封装 1.0
    3. 返回该对象引用。
    4. 再次调用时,再次创建新对象,即使值相同。

步骤 2:自动装箱(等价于 valueOf

Double a = 2.5;      // 编译后 → Double.valueOf(2.5)
Double b = 2.5;      // 编译后 → Double.valueOf(2.5)

System.out.println(a == b);   // ❌ false
System.out.println(a.equals(b)); // ✅ true
  • 结论:自动装箱也不会触发缓存。

步骤 3:new Double(...) 创建对象

Double x = new Double(3.14);
Double y = new Double(3.14);

System.out.println(x == y);   // ❌ false
System.out.println(x.equals(y)); // ✅ true
  • valueOf 行为一致:都创建新对象。

步骤 4:验证 0.0-0.0 是否缓存

Double posZero = Double.valueOf(0.0);
Double negZero = Double.valueOf(-0.0);

System.out.println(posZero == negZero); // ❌ false(不同对象)
System.out.println(posZero.equals(negZero)); // ✅ true(值相等)
  • 即使 +0.0-0.0equals 上相等,它们仍是不同的对象实例

步骤 5:NaN 是否缓存?

Double nan1 = Double.valueOf(Double.NaN);
Double nan2 = Double.valueOf(Double.NaN);

System.out.println(nan1 == nan2);     // ❌ false
System.out.println(nan1.equals(nan2)); // ✅ true(所有 NaN 相等)
  • NaN 也不缓存。

三、常见错误

错误 1:认为 Double 有缓存,用 == 比较

Double d1 = 1.0;
Double d2 = 1.0;
if (d1 == d2) { ... } // ❌ 危险!可能为 false,依赖 JVM 实现细节

✅ 正确做法:

if (d1 != null && d1.equals(d2)) { ... } // ✅ 安全

错误 2:误以为 Double.valueOf(0.0) 复用对象

// 错误假设:d1 和 d2 是同一个对象
Double d1 = Double.valueOf(0.0);
Double d2 = Double.valueOf(0.0);
// 实际:d1 != d2

错误 3:在性能敏感场景频繁装箱

// ❌ 高频创建对象,GC 压力大
for (int i = 0; i < 100000; i++) {
    Double wrapper = i * 0.1; // 每次自动装箱 → 创建新对象
    process(wrapper);
}

四、注意事项

  1. 永远不要用 == 比较 Double 对象
    应使用 equals() 比较值,或使用 Double.compare(d1, d2) == 0

  2. equals() 的特殊性

    • Double.NaN.equals(Double.NaN) 返回 true(尽管 NaN != NaN)。
    • +0.0-0.0 被视为相等。
  3. 对象创建开销

    • 每次装箱都涉及对象分配,可能触发 GC。
    • 在循环或高频代码中应避免。
  4. Double.valueOf()new Double() 行为一致

    • 两者都创建新对象,无性能或语义差异(new 已过时,推荐 valueOf 语义更清晰)。
  5. JVM 优化不可依赖

    • 某些 JVM 可能对 0.01.0 做极小范围缓存,但这不是规范,不同 JVM 行为可能不同。

五、使用技巧

  1. 优先使用 double 基本类型

    • 在计算、循环、集合中,尽量使用 double 而非 Double
  2. 避免在集合中存储 Double(若可能)

    • 使用 TroveEclipse Collections 等库的 TDoubleArrayList,避免装箱。
  3. 使用 OptionalDouble 表示可空 double

    OptionalDouble maybeValue = computeValue();
    if (maybeValue.isPresent()) {
        double value = maybeValue.getAsDouble();
    }
    
  4. 解析字符串用 parseDouble

    double d = Double.parseDouble(str); // 返回基本类型,无装箱
    

六、最佳实践

推荐做法

  1. 值比较用 equals()Double.compare()
// 推荐
if (Double.compare(a, b) == 0) { ... }

// 或
if (a != null && a.equals(b)) { ... }
  1. 高频场景避免装箱/拆箱
// ❌ 避免
List<Double> values = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    values.add(i * 0.1); // 10000 次装箱
}

// ✅ 优化:使用基本类型数组或专用集合
double[] values = new double[10000];
for (int i = 0; i < 10000; i++) {
    values[i] = i * 0.1; // 无装箱
}
  1. 使用 Double.valueOf() 而非 new Double()

    • 虽然行为相同,但 valueOf 是标准工厂方法,语义更清晰。
  2. 理解 Double 的局限性

    • 浮点精度、NaNInfinity 等,在设计算法时充分考虑。

七、性能优化

场景 推荐方式 性能优势
获取 double 直接使用 double 变量 零开销
字符串转数值 Double.parseDouble(str) 返回 double,无对象创建
存储大量 double double[]TDoubleArrayList 避免装箱,内存紧凑
比较 double Double.compare(a, b) 高效,处理 NaN
创建 Double 对象 Double.valueOf(d) 语义清晰(尽管仍创建对象)

关键优化减少装箱(Boxing)是提升性能的核心


八、总结

✅ 核心结论:

项目 说明
Double 有缓存吗? 没有!
Double.valueOf(d) 缓存吗? 不缓存,每次都创建新对象
自动装箱缓存吗? 不缓存
== 能比较 Double 吗? 不能! 必须用 equals()Double.compare()
性能关键 避免频繁装箱/拆箱

🚀 实践口诀:

Double 无缓存,装箱总新建;比较用 equals== 是陷阱;性能要高效,基本类型是王道。

记住:Double 是唯一没有缓存的数值包装类。在使用时务必注意对象创建开销和正确的比较方式,避免陷入性能和逻辑陷阱。