Effective Java 阅读笔记
第九章 通用编程
第 57 条 将局部变量的作用域最小化
将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能。
- 将局部变量的作用域最小化,最好的办法就是在第一次使用变量的地方声明它。
- 几乎每一个局部变量都应该进行初始化。
第 58 条 for-each 循环优先于传统 for 循环
for-each(又称增强 for 循环)隐藏了迭代器和索引变量,避免了出错的可能(特别是嵌套循环时)。
但是下面集中情况无法使用 for-each 循环:
- 解构、过滤:如果需要删除集合种特定元素,就需要使用迭代器,并调用remove方法,或者使用 Java 8 的
removeIf
方法。 - 转换:如果需要下标对特定元素进行处理,就只能使用普通循环。
- 并行迭代:如果需要同时迭代两个循环(特别是嵌套时),就需要操作下标,并进行手动控制迭代。
第 59 条 了解并使用类库
使用类库比自己造轮子更靠谱。比如 Java 7 之后生成随机数就不要使用 Random 类,使用 ThreadLocalRamdom 类,新类更快并且线程安全。
第 60 条 如果需要非常精确的答案,不要使用 float 或者 double
浮点数是不够精确的,不适合用来进行金融计算。
用BigDecimal
来代替 double。如果有最小单位时,可以使用 int 或者 long 进行计算,性能更好,并且也非常精确(但是除法就不行了)。
第 61 条 基本类型优先于装箱类型
基本类型更节省空间,并且计算速度更快(因为不用去堆中创建空间);
自动拆装箱减少了基本类型的烦琐性,但是并没有减少风险。
自动拆箱时,如果包装类型是null,就会抛出空指针异常。
第 62 条 如果其他类型合适,尽量避免使用字符串
字符串更容易出错,并且通常性能也更低。能使用其他类型的时候,尽量使用其他类型。
第 63 条 了解字符串连接的性能
使用+
可以很方便的拼接字符串,但是会有性能问题。虽然 Java 8 之后反编译加号拼接其实还是StringBuilder
,但是有些情况下会大量创建 StringBuilder对象,导致性能下降。
因此在拼接字符串时可以手动创建 StringBuilder 然后使用 append 方法进行拼接。大多数时候,相对加号拼接而言都是更快的。
第 64 条 通过接口引用对象
第 51 条有:使用接口作为参数,而不是使用类。更确切的来说,是使用接口来引用对象,而不是用类来引用。
如果有合适的接口类型存在,那么参数、返回值、变量、字段等都应该使用接口来声明。
如果养成了使用接口作为类型的习惯,程序会更加灵活。
在没有合适的接口时,完全可以使用类来引用对象(这不废话吗)。更准确的说,是如果没有合适的接口,就用类层次中提供了必要功能的最小具体类来引用对象。
第 65 条 接口优先于反射机制
反射允许在运行时访问给定义一个 Class 的构造器、方法、字段等,非常灵活,但是也有以下缺点:
- 损失了编译时的类型检查:如果用反射调用不存在的方法,会发生运行时异常。
- 执行反射访问所需要的代码非常笨拙、冗长:反射代码非常乏味,并且阅读起来也很困难。
- 性能损失:反射比起直接调用要慢了不少。
如果有限制的使用反射,虽然也有部分代价,但是也有非常多的好处。
许多程序必须用到的类在编译时是不可用的,但是有适当的接口或者超类可以引用这个类,如果是这种情况,就可以通过反射创建实例,然后通过接口或者超类进行引用,然后正常调用。
这样性能比Method.invoke
要好。
第 66 条 谨慎地使用本地方法
使用本地方法来提高性能的做法不值得提倡。因为 Java 越来越快了,并且标准库中已经提供了大部分需要的实现。
第 67 条 谨慎地进行优化
- 不要为了性能编牺牲合理的结构,要努力编写好的程序而不是快的程序。
- 要努力避免性质性能的设计。
- 要考虑 API 设计的性能后果。
- 为了获得好的性能而对 API 进行包装是一种非常不好的想法。
- 尝试优化的前后,要做出性能对比。
第 68 条 遵循普遍接受的命名惯例
每种语言一般都有各自推荐的命名方式,Java 也不例外:
标识符或类型 | 示例 |
---|---|
包或者模块 | org.junit.jupiter.api, com.google.common.collect |
类或者接口 | Stream, FutureTask, LinkedHashMap, HttpClient |
方法或者字段 | remove, groupBy, getCrc |
常量 | MIN_VALUE, HEGATIVE_INFINITY |
局部变量 | i, denom, houseNum |
泛型 | T, E, K, V, X, R, U, T1, T2 |