当前位置: 首页 > article >正文

每日 Java 面试题分享【第 11 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:什么是 Java 的 BigDecimal?
  • 问题二:BigDecimal 为什么能保证精度不丢失?
  • 问题三:使用 new String(字符串) 语句在 Java 中会创建多少个对象?

问题一:什么是 Java 的 BigDecimal?

BigDecimal 是 Java 提供的一个用于高精度计算的类,属于 java.math 包。它主要用于需要精确计算的场景,如金融、货币计算或科学运算,能够避免浮点数在计算中出现的精度问题。


1. BigDecimal 的特点

  1. 高精度计算

    • BigDecimal 可以实现任意精度的计算,而不像浮点数(floatdouble)因二进制表示可能出现精度丢失。

    • 例如:

      System.out.println(0.1 + 0.2); // 输出 0.30000000000000004
      

      但使用 BigDecimal 可以避免这个问题。

  2. 不可变性

    • BigDecimal 是不可变的,每次对 BigDecimal 对象的操作都会返回一个新的对象。
  3. 多种舍入模式

    • 提供了多种舍入方式(如四舍五入、向上舍入、向下舍入),适合不同的业务需求。
  4. 数值大小不限

    • 可以表示任意大小的数字,只受内存限制。

2. BigDecimal 的常用构造方法

BigDecimal 提供了多种方式来创建实例,以下为常用方法:

(1) 使用字符串创建
BigDecimal bd = new BigDecimal("0.1");
  • 推荐使用字符串,因为它能精确表示值。

  • 避免使用 floatdouble 构造器,会导致精度丢失:

    BigDecimal bd = new BigDecimal(0.1); // 不推荐,可能出现精度问题
    
(2) 使用静态方法 valueOf
BigDecimal bd = BigDecimal.valueOf(0.1);
  • 推荐使用此方法,它会将浮点数转换为字符串,避免精度问题。
(3) 使用整数或长整型构造
BigDecimal bd = new BigDecimal(10); // 精确表示

3. 常用方法

(1) 基本运算

BigDecimal 提供了加、减、乘、除等运算方法,以下为常用方法示例:

BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");

// 加法
BigDecimal sum = a.add(b); // 10.5 + 2.3 = 12.8

// 减法
BigDecimal diff = a.subtract(b); // 10.5 - 2.3 = 8.2

// 乘法
BigDecimal product = a.multiply(b); // 10.5 * 2.3 = 24.15

// 除法
BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP); // 保留两位小数,结果为 4.57
(2) 舍入模式

当除法结果不能整除时,需要指定舍入模式(RoundingMode):

  • 常用舍入模式:
    • RoundingMode.HALF_UP:四舍五入。
    • RoundingMode.HALF_DOWN:五舍六入。
    • RoundingMode.UP:向上舍入。
    • RoundingMode.DOWN:向下舍入。

例如:

BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 结果保留两位小数
(3) 比较大小

BigDecimal 提供以下方法来比较值:

  • compareTo 方法:返回整数,分别表示小于、等于、大于。

    BigDecimal a = new BigDecimal("10.0");
    BigDecimal b = new BigDecimal("10.00");
    System.out.println(a.compareTo(b)); // 输出 0,值相等
    
  • equals 方法:判断精度和值是否完全相同。

    System.out.println(a.equals(b)); // 输出 false,精度不同
    
(4) 其他常用方法
  • setScale:设置小数位数并指定舍入模式。

    BigDecimal scaled = a.setScale(2, RoundingMode.HALF_UP); // 保留两位小数
    
  • stripTrailingZeros:去掉尾部的多余零。

    BigDecimal trimmed = new BigDecimal("10.0000").stripTrailingZeros(); // 结果为 10
    

4. 示例代码

import java.math.BigDecimal;
import java.math.RoundingMode;

public class BigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");

        // 加法
        BigDecimal sum = a.add(b);
        System.out.println("加法结果: " + sum);

        // 减法
        BigDecimal diff = a.subtract(b);
        System.out.println("减法结果: " + diff);

        // 乘法
        BigDecimal product = a.multiply(b);
        System.out.println("乘法结果: " + product);

        // 除法(四舍五入,保留两位小数)
        BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP);
        System.out.println("除法结果: " + quotient);

        // 比较大小
        System.out.println("比较大小: " + a.compareTo(b)); // 输出 -1(a < b)

        // 设置小数位数
        BigDecimal scaled = a.setScale(3, RoundingMode.HALF_UP);
        System.out.println("保留三位小数: " + scaled);
    }
}
输出
加法结果: 0.3
减法结果: -0.1
乘法结果: 0.02
除法结果: 0.50
比较大小: -1
保留三位小数: 0.100

5. BigDecimal 的实际应用场景

  1. 货币计算

    • 避免浮点数因精度丢失导致的金额计算错误。
    • 比如银行系统、财务系统需要高精度的货币计算。
  2. 科学计算

    • 需要非常高精度的小数计算时使用。
  3. 精准计量

    • 例如商品重量、距离等对精度要求高的场景。

6. 面试中的注意点

  1. 避免使用浮点数直接构造 BigDecimal

    • 推荐使用字符串或 valueOf 方法。

    • 错误:

      BigDecimal bd = new BigDecimal(0.1); // 精度丢失
      
    • 正确:

      BigDecimal bd = new BigDecimal("0.1");
      
  2. 掌握舍入模式的应用

    • 面试官常考除法运算时的舍入模式,如如何避免 ArithmeticException
  3. 性能问题

    • 了解 BigDecimal 的高精度带来的性能开销,建议只在需要高精度的地方使用。

总结

BigDecimal 是 Java 中处理高精度计算的重要工具,适合用在对精度要求高的场景。它以不可变性、多种运算方法和灵活的舍入模式,成为解决浮点数精度问题的首选。理解其构造方式、运算方法和应用场景是面试和开发中的重点。


问题二:BigDecimal 为什么能保证精度不丢失?

BigDecimal 能够保证精度不丢失的关键在于其 底层实现原理数值表示方式。相比于 floatdouble 使用二进制浮点数表示,BigDecimal 使用的是 字符串形式存储数值,并对数字进行严格的数学计算,而不是依赖硬件的浮点运算。这使得 BigDecimal 避免了二进制浮点表示的精度问题。


1. BigDecimal 的数值存储方式

BigDecimal 的底层实现由以下两部分组成:

  1. 任意精度的整数值BigInteger 表示):

    • 数值部分以十进制形式存储,不受二进制表示的限制。
    • 例如,BigDecimal("0.1") 的底层存储为 1(整数部分)。
  2. 精度(小数点位置)

    • 使用一个整数变量存储小数点的位置,精确地记录小数的位数。
    • 例如,BigDecimal("0.1") 会存储 1 和精度 -1,表示 1 × 10^(-1)

这种存储方式完全基于十进制,避免了浮点数的二进制转换误差。


2. 与浮点数表示的区别

浮点数的二进制表示
  • floatdouble 使用 IEEE 754 标准的二进制浮点表示。
  • 某些十进制小数无法用二进制精确表示。例如:
    • 十进制的 0.1 在二进制中是一个 无限循环小数

      0.1 (十进制) = 0.0001100110011…(二进制)
      

      因此,浮点数表示 0.1 时会出现近似值,导致精度丢失。

BigDecimal 的十进制表示
  • BigDecimal 直接以十进制存储数值,能够精确表示任意大小和精度的十进制小数。
  • 例如,BigDecimal("0.1") 就直接存储为十进制 0.1,没有任何精度误差。

3. BigDecimal 的高精度计算方式

(1) 严格的数学运算
  • BigDecimal 的所有操作(如加减乘除)都基于十进制数学运算实现,而不是硬件层面的浮点数运算。
  • 每次运算都严格控制精度,不会因为二进制表示导致误差累积。
(2) 可控的舍入模式
  • 在进行除法或设置小数位时,BigDecimal 提供多种舍入模式(如四舍五入、向上舍入等),保证结果符合预期。

  • 例如:

    BigDecimal a = new BigDecimal("1");
    BigDecimal b = new BigDecimal("3");
    BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 精确到两位小数
    System.out.println(result); // 输出 0.33
    

4. 为什么浮点数会丢失精度,而 BigDecimal 不会?

浮点数精度丢失的原因
  1. 二进制表示限制

    • 浮点数以二进制存储,某些十进制小数无法精确转换为二进制(如 0.1)。
    • 浮点数的有限位数(如 float 是 32 位,double 是 64 位)导致表示范围和精度有限。
  2. 运算误差累积

    • 浮点数运算会因为舍入而产生误差,且误差会随着运算次数增加逐步累积。
BigDecimal 保证精度的原因
  1. 基于十进制存储
    • 数值存储为十进制形式,避免了二进制转换误差。
  2. 任意精度
    • 可以存储任意大小和精度的数值,只受内存限制。
  3. 严格数学计算
    • 所有计算都在软件层实现,遵循十进制数学规则,精确控制每一步的结果。

5. 示例对比

以下是浮点数和 BigDecimal 的对比示例:

浮点数的精度问题
public class FloatPrecision {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2); // 输出 0.30000000000000004
    }
}
BigDecimal 的精确计算
import java.math.BigDecimal;

public class BigDecimalPrecision {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        BigDecimal sum = a.add(b);
        System.out.println(sum); // 输出 0.3
    }
}

6. 使用 BigDecimal 的最佳实践

  1. 避免使用浮点数直接构造

    • 例如:

      BigDecimal bd = new BigDecimal(0.1); // 错误
      BigDecimal bd = new BigDecimal("0.1"); // 正确
      
  2. 使用 valueOf 创建

    • 如果必须使用浮点数,推荐使用 BigDecimal.valueOf()

      BigDecimal bd = BigDecimal.valueOf(0.1); // 安全
      
  3. 合理选择舍入模式

    • 除法等可能产生无限小数的操作时,必须指定舍入模式,避免抛出异常。

7. 总结

  • 为什么 BigDecimal 能保证精度不丢失?

    • 因为它基于十进制存储数值,采用严格的数学运算规则,而不是依赖硬件的二进制浮点运算。
    • 它记录了每个数字的精度(小数位数),能完全避免二进制表示的精度问题。
  • 适用场景

    • 高精度场景,如货币计算、科学计算、精准计量等。

BigDecimal 的优势使其成为对精度要求高的场景中不可替代的工具,但它也有性能上的开销,需要根据实际情况权衡使用。


问题三:使用 new String(字符串) 语句在 Java 中会创建多少个对象?

当使用 new String("字符串") 创建字符串时,具体会创建 1 个或 2 个对象,这取决于字符串常量是否已经存在于 字符串常量池 中。以下是详细分析:


1. 情况分析

情况 1:常量池中不存在该字符串
  • 如果 "字符串" 是一个新的字符串常量,不在常量池中:

    1. 常量池中会创建一个新的字符串对象来存储 "字符串"
    2. new String("字符串") 会创建一个新的堆内存对象。
    • 总计:2 个对象(1 个在常量池,1 个在堆中)
情况 2:常量池中已存在该字符串
  • 如果 "字符串" 已存在于常量池中:

    1. 常量池中不会重复创建对象,直接使用现有对象。
    2. new String("字符串") 会在堆中创建一个新的对象。
    • 总计:1 个对象(仅堆中创建新对象)

2. 代码示例与解读

代码示例 1:常量池中没有字符串
public class StringExample {
    public static void main(String[] args) {
        String s1 = new String("hello");
    }
}
  • 假设 "hello" 不在常量池中:

    1. 常量池中会存储一个 "hello"
    2. 堆中会创建一个新的 String 对象。
    • 总计创建 2 个对象:
      • 常量池中的 "hello"
      • 堆中的 new String("hello")
代码示例 2:常量池中已有字符串
public class StringExample {
    public static void main(String[] args) {
        String s1 = "hello";  // 这行将 "hello" 放入常量池
        String s2 = new String("hello");
    }
}
  • "hello" 已存在于常量池中:

    1. 常量池不会再创建新的 "hello" 对象。
    2. new String("hello") 会在堆中创建一个新对象。
    • 总计创建 1 个对象:
      • 堆中的 new String("hello")

3. 总结

创建对象数量的规律
  • 新字符串常量:如果常量池中没有该字符串,创建 2 个对象(常量池 + 堆)。
  • 已有字符串常量:如果常量池中已有该字符串,创建 1 个对象(堆)。
为什么会有堆中的新对象?
  • 使用 new String(字符串) 强制创建了一个堆内存中的字符串对象,并且它不与常量池中的对象共享。

4. 扩展讲解

字符串常量池机制
  • 字符串常量池是 JVM 中的一块特殊内存区域,用于存储字符串字面量,目的是为了节省内存并提高性能。
  • 当使用双引号创建字符串时(例如 "hello"),JVM 会检查常量池:
    • 如果字符串已存在,则复用。
    • 如果不存在,则将该字符串放入常量池。
如何避免多余对象的创建?
  • 使用 String 时尽量避免 new String(),直接使用字符串字面量或者调用 intern() 方法。

  • 例如:

    String s1 = "hello";  // 常量池复用
    String s2 = new String("hello").intern();  // 返回常量池中的字符串
    
性能建议
  • 如果对内存占用敏感,避免使用 new String(),因为它会创建堆对象,增加内存开销。

关键点总结

  • 对象创建数量:取决于字符串常量池中是否已有该字符串。
  • 常量池 vs 堆"字符串" 会尝试复用常量池对象,而 new String() 强制创建堆对象。
  • 面试重点:考察对 Java 字符串常量池机制的理解,以及如何避免不必要的对象创建。

总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!🎉


http://www.kler.cn/a/521436.html

相关文章:

  • SSM开发(四) spring+SpringMVC+mybatis整合(含完整运行demo源码)
  • PHP htmlspecialchars()函数详解
  • javascript-es6 (二)
  • 深度学习,python编程运行环境问题等记录(更新)
  • DistilBERT 是 BERT 的精简版本,具有更小、更快、更经济、更轻便的特点。
  • SD-WAN站点和客户端的区别
  • VS Code i18n国际化组件代码code显示中文配置 i18n ally
  • toRow和markRow的用法以及使用场景
  • K8s运维管理平台 - KubeSphere 3.x 和4.x 使用分析:功能较强,UI美观
  • 【Java-数据结构】Java 链表面试题上 “最后一公里”:解决复杂链表问题的致胜法宝
  • 深圳大学-智能网络与计算-实验三:网络容量优化分析实验
  • 【2024年华为OD机试】 (B卷,100分)- 阿里巴巴找黄金宝箱(III)(JavaScriptJava PythonC/C++)
  • 超硬核,基于mmap和零拷贝实现高效的内存共享
  • 计算机图形学知识点整理(期末复习/知识点学习/笔试/面试)
  • DDD-事件风暴
  • 机器学习 - 初学者需要弄懂的一些线性代数的概念
  • Alfresco Content Services dockerCompose自动化部署详尽操作
  • 使用scikit-learn中的线性回归包对自定义数据集进行拟合
  • PPT对齐排版的实用方法
  • 【漫话机器学习系列】063.梯度下降(Gradient Descent)