1.为什么禁止使用Apache Beanutils进行属性的copy?
- Apache BeanUtils在性能上相对较差,特别是在处理大量数据时,其反射机制会导致显著的性能下降。相比之下,Spring BeanUtils、Cglib BeanCopier等工具在性能上更优。因此,建议使用性能更好的属性拷贝工具。
2.为什么要求日期格式化时必须有使用 y 表示年,而不能用 Y ?
- 在日期格式化中,
y
代表年份,而Y
在某些情况下可能表示“Week year”(周年份), 这在处理跨年度的日期时可能导致混淆。为了确保日期的准确性,推荐使用y
来表示年份。
3.《Java开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事?
- 三目运算符(如
a ? b : c
)在Java中可能涉及自动拆箱操作,如果b
或c
是包装类型(如Integer)且为null,那么在拆箱为基本类型时可能会抛出NullPointerException。因此,在使用三目运算符时需要特别注意空指针问题。
4.为什么建议初始化HashMap的容量大小?
- HashMap在扩容时重建hash表,这是一个比较耗时的操作。如果能够在创建HashMap时预估其容量并设置合适的初始容量,可以减少扩容的次数,从而提高性能。
5.Java开发手册建议创建HashMap时设置初始化容量,但是多少合适呢?
- 初始化容量的设置应该根据实际应用场景中的元素数量来预估。一般来说,可以将预估的元素数量除以负载因子(默认为0.75)来得到一个合适的初始容量。但需要注意的是,HashMap的容量是2的幂次方,因此实际设置的初始容量会向上取整到最近的2的幂次方。
6.为什么禁止使用Executors创建线程池?
- Executors创建的线程池可能无法合理控制线程数量,如FixedThreadPool和CachedThreadPool在极端情况下可能导致资源耗尽或性能下降。建议使用ThreadPoolExecutor来创建线程池,以便更精确地控制线程数量、核心线程数、最大线程数、存活时间等参数。
7.为什么要求谨慎使用ArrayList中的subList方法?
- ArrayList的subList方法返回的是原列表的一个视图、对子列表的任何非结构修改都会反映到原列表上,然而对子咧白哦的结构性修改(如添加、删除元素)会导致原列表抛出ConcurrentModificationException异常。因此,在使用subList时需要谨慎处理,避免在迭代过程中修改列表结构。
8.为什么不建议在for循环中使用"+"进行字符串拼接?
- 在for循环中使用"+"进行字符串拼接导致大量的字符串对象被创建和销毁,从而增加垃圾回收的负担并降低性能。建议使用StringBuilder或StringBuffer来进行字符串的拼接操作,尤其是在循环中或需要拼接大量字符串时。
9.为什么禁止在foreach循环中进行元素的remove/add操作?
- 解析:在foreach循环中直接对集合进行remove/add操作可能会导致ConcurrModificationException异常。这是因为foreach实际上是通过迭代器来遍历集合的,而在遍历过程中修改集合的结构是不被允许的。如果需要在遍历过程中修改集合,应该使用迭代器本身的remove方法或转化成其他类型的循环结构(如for循环)并手动控制索引。
10.为什么禁止工程师直接使用日志系统(Log4j、Logback)在的API?
- 直接使用日志系统的API可能导致日志配置不灵活、难以维护等问题。建议使用封装好的日志门面如(SLF4J)来统一日志的接口,然后通过配置文件来指定具体的日志实现(如Log4j、Logback)。 这样可以更灵活地控制日志的输出格式、级别等参数,并且方便在项目中更换日志实现而不需要修改代码。
11. 为什么禁止把SimpleDateFormat定义成static变量?
- SimpleDateFormat不是线程安全的,如果将其定义为static变量并在多线程环境下使用,可能会导致日期格式化的结果出现混乱。为了避免这个问题,建议将SimpleDateFormat定义为局部变量或在每次使用时都进行同步处理。另外,也可以考虑使用Joda-Time或Java 8中的java.time包中的类来替代SimpleDateFormat,这些类提供了更好的线程安全性和易用性。
12. 为什么禁止开发人员使用isSuccess作为变量名?
- isSuccess这样的变量名可能过于笼统和模糊,无法清晰地表达变量的具体含义。在命名变量时应该尽量使用具有描述性的名称来反映变量的用途和含义。例如,如果isSuccess用于表示某个操作是否成功完成,那么更具体的命名如operationSuccessful可能会更好。
13.为什么禁止开发人员修改serialVersionUID字段的值?
serialVersionUID
是Java序列化机制中用于验证序列化对象的版本一致性的一个字段。如果类的结构发生了变化(如增加了新的字段、修改了字段的类型等),而serialVersionUID
没有相应地更新,那么在反序列化时可能会因为版本不匹配而抛出InvalidClassException
异常。因此,通常建议在类的定义中显式地指定serialVersionUID
的值,并且在类结构发生变化时更新这个值。然而,如果类的设计是为了向后兼容,即允许反序列化旧版本的对象到新版本的类中,那么可能需要特别处理serialVersionUID
的更新或不变的情况。
14.为什么建议开发者谨慎使用继承?优先使用组合的方式实现?
- 继承是面向对象编程中的一个重要特性,它允许我们基于现有的类来创建新的类,从而实现代码的重用。然而,过度使用继承可能会导致类之间的耦合度过高,使得系统难以维护和扩展。相比之下,组合(Composition)通过将现有的类作为新类的成员变量来使用,可以更加灵活地构建系统,同时降低类之间的耦合度。组合的方式更加符合“开闭原则”(对扩展开放,对修改关闭),即在不修改现有代码的前提下,通过增加新的类来实现新的功能。因此,建议开发者在设计系统时优先考虑使用组合的方式来实现功能,而不是过度依赖继承。
15.为什么禁止使用count(列名)
或count(常量)
来替代count(\*)
?
-
在SQL查询中,
COUNT
函数用于统计行数。count(*)
会统计表中的所有行,包括那些含有NULL值的列。而count(列名)
只会统计该列非NULL值的行数,如果列中存在大量的NULL值,那么count(列名)
的结果可能会小于count(*)
。同样地,count(常量)
(如count(1)
)也会统计所有行,但在某些数据库系统中,其性能可能与count(*)
略有不同。然而,从可读性和一致性的角度来看,count(*)
是统计行数的标准方式,它清晰地表达了“统计所有行”的意图。此外,在某些数据库优化器中,count(*)
和count(常量)
可能会被优化为相同的执行计划,但使用count(列名)
则可能无法享受到这种优化。因此,建议开发者在统计行数时使用count(*)
。本文为个人学习总结,内容仅供参考,如需系统学习,推荐阅读原文
《Java开发手册(嵩山版)》灵魂15问