每日5题Java面试系列(2):基础篇(BigDecimal、String三剑客、常见字符编码、泛型)

系列介绍

欢迎来到"Java面试基础篇"系列!本系列旨在帮助Java开发者系统性地准备面试,每天精选5道经典面试题,涵盖Java基础、进阶、框架等各方面知识。坚持学习21天,助你面试通关!

每日5题Java面试系列(1)

今日面试题

1、为什么用BigDecimal的equals方法做等值比较会出问题

  • 使用 BigDecimal 的equals方法做等值比较会出问题,根本原因在于equals方法不仅比较数值本身,还比较scale(小数位数)。当两个BigDecimal的值相同,但是scale不同,equals会返回false
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
System.out.println(a.equals(b));//输出 false
  • 这是因为从源码层面,BigDecimal的equals方法首先会检查两个对象的scale是否相同,只有在scale相同的情况下才会继续比较值。

  • 这种设计是有意为之的,因为BigDecimal 不仅仅表示一个数值,还包含了精度信息。在科学计算等领域,1.0和1.00可能代表不同的精度要求。此外,按照 Java 集合框架的约定,如果两个对象equals相等,它们的 hashCode也必须相同,这也是设计考量之一。

  • 这个设计在实际开发中会导致一些问题

    1. 在 HashSet 或 HashMap 中,1.0和1.00会被视为不同的元素
    2. 在业务逻辑判断中,相同金额但精度不同的 BigDecimal 会被误判为不相等
    3. 在单元测试中,预期值和实际值即使数值相同也可能导致测试失败
    4. 使用 TreeSet 或 TreeMap ,它们基于 compareTo 方法比较元素
    5. 在存入集合前标准化 BigDecimal,如使用 stripTrailingZeros() 方法
    6. 创建自定义包装类,重写equals和hashCode 方法基于compareTo 实现

正确比较 BigDecimal 相等性的方法是使用 compareTo 方法

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
System.out.println(a.compareTo(b));//输出 true

2、String、StringBuilder和StringBuffer的区别是什么

  • String、StringBuilder 和 StringBuffer 是Java中处理字符串的三个核心类,它们之间有三个关键区别:可变性、线程安全和性能。

  • 首先是可变性,String 是不可变的,一旦创建就不能修改其内容。每次对String的修改操作(如连接、截取)都会创建一个新的String对象。而 StringBuilder 和StringBuffer 都是可变的,可以在源对象上直接修改内容,不会创建新对象。

  • 在线程安全上,String因为不可变,本质上是线程安全的。StringBuffer的所有修改方法都使用 synchronized 关键字进行同步,保证了线程安全。而StringBuilder 没有进行同步处理,在多线程环境下不安全,但因此在单线程环境下性能最好。

  • 在性能角度上,在频繁修改字符串的场景下,String 的性能最差,因为每次操作都会创建新对象。单线程环境中 StringBuilder 性能最佳,而多线程环境中需要使用 StringBuffer。对于简单的字符串连接,现代 JVM 通常会自动优化为使用StringBuilder。

  • 从实现上,三者都是基于字符数组(Java 9 前)或字节数组(Java 9 后)存储内容。String 中的数组的final的,而StringBuilder 和StringBuffer 继承自相同的父类AbstractStringBuilder,区别仅在于StringBuffer的方法添加了synchronized修饰。这些差异决定了它们的适用场景。

  • 在实际项目中,我发现一个常见的性能陷阱是在循环中使用 String 连接操作。例如在一个处理大量数据的报表生成系统中,将循环中的result +=item 替换为StringBuilder模式,处理时间从几分钟减少到几秒钟。另外,合理设置StringBuilder的初始容量也很重要。如果大致知道最终字符串的长度,提前设置足够的容量可以表面多次扩容操作,进一步提高性能。例如在生成固定格式报表时候,我们会根据记录数预估最终长度设置初始容量。

  • 随着Java的发展,这些类也在不断优化。Java 9 引入的紧凑字符串(Compact Strings)使得这三个类在处理ASCII 字符时内存占用减半,提高了性能。

3、String为什么设计成不可变的

  • String 在Java中被设计成不可变主要基于以下几个方面的考虑:

  • 首先,安全性是最主要的原因之一。String 常用语存储敏感信息 如密码、网络连接等。不可变性确保这些信息在传递过程中不会被修改。例如,当String作为方法参数传递时,调用者无法通过引用修改其内容,保障了方法的安全性。

  • 其次,字符串常量池是Java的重要优化机制。由于String 不可变,JVM 可以安全地在常量池中重用相同内容的字符串对象,节省内存。

 String s1= "Hello";
 String s2= "Hello"; //s1和s2指向常量池中的同一对象
  • 第三,String经常做为HashMap和HashSet的键使用。不可变性保证了哈希值的稳定,String类内部会缓存计算出的哈希值,提高性能。如果String可变,则内容变化后哈希值也会变化,可能导致已存入哈希表的对象无法被检索到。

  • 第四,线程安全方面,不可变对象天然是线程安全的,可以在多线程间自动共享而无需同步,这在并发编程中非常重要。

  • 实际发现,String类被声明为final,内部字符数组也是private final 的,所有修改操作(substring、concat等)都会创建新对象而非修改源对象。Java 9 之后,String内部实现从char[]改为byte[] 以进一步优化存储空间。

  • 相比之下,StringBuilder 和StringBuffer被设计为可变的,专门用于频繁字符串拼接场景,避免String 不可变带来的性能损耗。

4、常见的字符编码有哪些,有什么区别

  • 首先是 ASCII, 作为最早的编码标准,用 7 位二进制表示 128 个字符,主要包括英文字母、数字和基本符号。它是许多其他编码的基础,但仅支持英文。

  • ISO-8859-1(Latin-1)是ASCII的 8 位扩展,增加了 128 个字符,支持西欧语言,每个字符占 1字节。早期网页多用此编码

  • 针对中文,中国大陆有 GB 系列编码: GB2312 收录基本汉字, GBK 是其扩展版本收录更多字符, GB18030 是最新国标, 完全兼容 Unicode。这些编码对汉字使用 2 字节表示,与ASCII兼容。

  • 繁体中文地区则主要使用 Big5 编码,同样是双字节编码,与 GB 系列不兼容。

  • 现代应用最广泛的是 Unicode 系列编码,特别是 UTF-8。 Unicode 是字符集,为全球所有字符分配唯一码点;UTF-8 是其实现方式,使用 1-4 个字节表示不同字符。ASCII 字符在 UTF-8 中仍占 1字节,欧洲字符占 2字节,中日韩文字主要占 3字节。UTF-8 的优势在于节省空间且兼容ASCII ,因此成为互联网标准。此外还有 UTF-16( 主要用2 字节,Java内部使用) 和 UTF-32 (固定4字节,处理效率高但占空间)

  • 这些编码的区别主要体现在:

    1. 支持的字符范围不同
    2. 存储效率不同(单字节 vs 多字节,固定长度 vs 可变长度)
    3. 兼容性不同 (如 UTF-8 兼容 ASCII ,而GB2312 不兼容 Big 5)
      在实际开发中,现在基本都推荐使用 UTF-8,它几乎支持所有语言,且在网络传输中更高效。当遇到乱码问题时,通常是因为编码的不匹配,需要确保从读取到显示的整个过程使用一致的编码。

5、什么是泛型?有什么好处

  • 泛型是Java中允许我们在定义类、接口和方法时使用类型参数的一种特性,这些类型参数在使用时被具体类型替换。简单说,泛型就是"参数化类型",使得同一段代码可以操作不同类型的数据。

  • 泛型的主要优势有四点

  1. 首先,泛型提供了编译时类型安全检查。比如,对于泛型集合 List ,在编译阶段就能发现类型错误(如尝试添加 Integer),避免了运行时的 ClassCastException。相比之下,非泛型集合需要在运行时进行类型转换,可能会引发异常:
List<String> list = new ArrayList<>();
list.add("hello");
list.add(10)//编译错误,立即发现问题
  1. 泛型消除了显式类型转换。使用泛型后,编译器自动处理类型转换,代码更简洁刻度。
//不使用泛型
List list = new ArrayList();
Stirng s = (String) list.get(0); //需要强制转换

//使用泛型
List<String> list =new ArrayLisT<>();
sTRING S = list.get(0);//无需转换

  1. 泛型显著提高了代码复用性。同一个类或方法可以处理不同类型的数据,避免了重复编写类似代码。比如集合框架中的排序算法可以应用于任何实现了 Comparable接口的类型。

  2. 泛型使 API设计更加清晰。使用者通过泛型签名就能理解方法的类型要求,而不需要查阅文档或依赖注释。

  • 在实际项目中,我们经常使用泛型开发通用组件,如自定义集合类,缓存机制或通用数据处理框架,大大提高了代码的可维护性和扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值