Effective Java 笔记三

读书
文章目录
  1. 1. 检查参数的有效性
  2. 2. 必要时进行保护性拷贝
  3. 3. 谨慎设计方法签名
  4. 4. 慎用重载
  5. 5. 慎用可变参数
  6. 6. 返回零长度的数组或者集合,而不是null
  7. 7. 为所有导出的API元素编写文档注释
  8. 8. 将局部变量的作用域最小化
  9. 9. for-each循环优先于传统的for循环
  10. 10. 了解和使用类库
  11. 11. 如果需要精确的答案,请避免使用 float和 double
  12. 12. 基本类型优先于装箱基本类型
  13. 13. 如果其他类型更适合,则尽量避免使用字符串
  14. 14. 当心字符串连接的性能
  15. 15. 通过接口引用对象
  16. 16. 接口优先于反射机制
  17. 17. 谨慎地使用本地方法
  18. 18. 谨慎地进行优化
  19. 19. 遵守普遍接受的命名惯例
  20. 20. 只针对异常的情况才使用异常
  21. 21. 对可恢复的情况使用受检异常,对编程错误使用运行时异常
  22. 22. 避免不必要地使用受检的异常
  23. 23. 优先使用标准的异常
  24. 24. 抛出与抽象相对应的异常
  25. 25. 每个方法抛出的异常都要有文档
  26. 26. 在细节消息中包含能捕获失败的信息
  27. 27. 努力使失败保持原子性
  28. 28. 不要忽略异常
  29. 29. 同步访问共享的可变数据
  30. 30. 避免过度同步
  31. 31. executor和task优先于线程
  32. 32. 并发工具优先于 wait 和 notify
  33. 33. 线程安全性的文档化
  34. 34. 慎用延迟初始化
  35. 35. 不要依赖于线程调度器
  36. 36. 避免使用线程组
  37. 37. 谨慎地实现 Serializable 接口
  38. 38. 考虑使用自定义的序列化形式
  39. 39. 保护性地编写 readobject 方法
  40. 40. 对于实例控制,枚举类型优先于 readResolve
  41. 41. 考虑用序列化代理代替序列化实例

方法

检查参数的有效性

绝大多数方法和构造器对于传递给它们的参数值都会有某些限制。例如,索引值必须是非负数,对象引用不能为null,等等,这些都是很常见的。对于参数的校验,可以使用 @NotNull

必要时进行保护性拷贝

谨慎设计方法签名

慎用重载

在Java中,参数类型或者个数不一样,对返回参数没有要求,叫做重载。

慎用可变参数

可变参数如下代码所示:

1
2
3
4
5
6
7
8
static int sum(int... args) {
int sum = 0;
for (int arg : args) {
sum += arg;
}

return sum;
}

在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是它们不应该被过度滥用。如果使用不当,会产生混乱的结果。

返回零长度的数组或者集合,而不是null

返回类型为数组或集合的方法没理由返回null,而不是返回一个零长度的数组或者集合。

为所有导出的API元素编写文档注释

方便后来者阅读以及使用 API 的用户使用。

通用程序设计

将局部变量的作用域最小化

要使局部变量的作用域最小化,最有利的方法就是在第一次使用它的地方声明。如果变量在使用之前声明,这只会造成混乱——对于试图理解程序功能的读者来说,多了一种分散注意力的因素。等到用到该变量的时候,读者可能已经记不起该变量的类型或者初始值了。

for-each循环优先于传统的for循环

这个就是推荐使用下面这种循环:

1
2
3
for(Element e: elements){
doSomething (e);
}

对于这种循环,它的性能比传统的循环,性能更好。for-each循环在简洁性和预防Bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。应该尽可能地使用for-each循环。不过在Java8中,stream 有一种foreach功能,可以试试使用。

1
2
3
4
List<Integer> list = new ArrayList<>();
list.forEach(input->{
//对数组的操作
});

了解和使用类库

Java提供了很多的标准库,我们在平时使用的时候,不需要再去造轮子了,可以直接使用,但是在使用的时候,需要注意这些类库的一些使用情况。

如果需要精确的答案,请避免使用 float和 double

如果性能非常关键,并且你又不介意自己记录十进制小数点,而且所涉及的数值又不太大,就可以使用int或者long。如果数值范围没有超过9位十进制数字,就可以使用int;如果不超过18位数字,就可以使用long。如果数值可能超过18位数字,就必须使用 BigDecimal。

进行货币计算,正确的办法是使用 BigDecimal、int 或者 long。

基本类型优先于装箱基本类型

基本类型简单,在运行的时候,速度更加快。装箱基本类型在创建的时候,初始是null。

如果其他类型更适合,则尽量避免使用字符串

  • 字符串不适合替代其他的值类型
  • 字符串不适合替代枚举类型
  • 字符串不适合替代聚合类型
  • 字符串不适合替代能力表

当心字符串连接的性能

可以查看String,StringBuilder,StringBuffer源码分析。

通过接口引用对象

接口优先于反射机制

谨慎地使用本地方法

本地方法(native method),指的是用本地程序设计语言(比如 C 或者 C++)来编写的特殊方法,有 native 修饰的。

谨慎地进行优化

遵守普遍接受的命名惯例

可以参考阿里巴巴的Java手册。

异常

只针对异常的情况才使用异常

对于程序,能够通过代码解决的问题,不要丢给异常来处理。

对可恢复的情况使用受检异常,对编程错误使用运行时异常

避免不必要地使用受检的异常

优先使用标准的异常

抛出与抽象相对应的异常

每个方法抛出的异常都要有文档

使用 Javadoc 的 @throws 标签记录下一个方法可能拋出的每个未受检异常,但是不要使用 throws 关键字将未受检的异常包含在方法的声明中。

在细节消息中包含能捕获失败的信息

努力使失败保持原子性

不要忽略异常

对于异常,需要解决。

并发

同步访问共享的可变数据

当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。

避免过度同步

这会导致程序整体性能下降。为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。更为一般地讲,要尽量限制同步区域内部的工作量。当你在设计一个可变类的时候,要考虑一下它们是否应该自己完成同步操作。在现在现在多核的时代,这比永远不要过度同步来得更重要。

executor和task优先于线程

使用线程池。多线程编程 —— 线程池

并发工具优先于 wait 和 notify

在 java.util.concurrent 工具包下,提供了一些并发工具类,比如 CountDownLatch 和 Semaphore。可以参考CyclicBarrier、CountDownLatch 和 Semaphore 的比较

线程安全性的文档化

慎用延迟初始化

不要依赖于线程调度器

避免使用线程组

序列化

谨慎地实现 Serializable 接口

实现 Serializable 接口而付出的最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。如果一个类实现 Serializable 接口,它的字节流编码就变成了它的导出 API 的一部分。如果使用这个类,就必须支持这种序列化。

第二个代价是增加了出现 Bug 和安全漏洞的可能性。

第三个代价是随着类的发行新的版本,相关的测试负担也增加了。

PS:对于这一点,不是很明白。

考虑使用自定义的序列化形式

保护性地编写 readobject 方法

对于实例控制,枚举类型优先于 readResolve

考虑用序列化代理代替序列化实例

评论