高效Java(第三版) Effective Java
1. 考虑使用静态工厂方法替代构造方法 2. 当构造方法参数过多时使用builder模式 3. 使用私有构造方法或枚类实现Singleton属性 4. 使用私有构造方法执行非实例化 5. 使用依赖注入取代硬连接资源 6. 避免创建不必要的对象 7. 消除过期的对象引用 8. 避免使用Finalizer和Cleaner机制 9. 使用try-with-resources语句替代try-finally语句 10. 重写equals方法时遵守通用约定 11. 重写equals方法时同时也要重写hashcode方法 12. 始终重写 toString 方法 13. 谨慎地重写 clone 方法 14. 考虑实现Comparable接口 15. 使类和成员的可访问性最小化 16. 在公共类中使用访问方法而不是公共属性 17. 最小化可变性 18. 组合优于继承 19. 如果使用继承则设计,并文档说明,否则不该使用 20. 接口优于抽象类 21. 为后代设计接口 22. 接口仅用来定义类型 23. 优先使用类层次而不是标签类 24. 优先考虑静态成员类 25. 将源文件限制为单个顶级类 26. 不要使用原始类型 27. 消除非检查警告 28. 列表优于数组 29. 优先考虑泛型 30. 优先使用泛型方法 31. 使用限定通配符来增加API的灵活性 32. 合理地结合泛型和可变参数 33. 优先考虑类型安全的异构容器 34. 使用枚举类型替代整型常量 35. 使用实例属性替代序数 36. 使用EnumSet替代位属性 37. 使用EnumMap替代序数索引 38. 使用接口模拟可扩展的枚举 39. 注解优于命名模式 40. 始终使用Override注解 41. 使用标记接口定义类型 42. lambda表达式优于匿名类 43. 方法引用优于lambda表达式 44. 优先使用标准的函数式接口 45. 明智审慎地使用Stream 46. 优先考虑流中无副作用的函数 47. 优先使用Collection而不是Stream来作为方法的返回类型 48. 谨慎使用流并行 49. 检查参数有效性 50. 必要时进行防御性拷贝 51. 仔细设计方法签名 52. 明智而审慎地使用重载 53. 明智而审慎地使用可变参数 54. 返回空的数组或集合不要返回null 55. 明智而审慎地返回Optional 56. 为所有已公开的API元素编写文档注释 57. 最小化局部变量的作用域 58. for-each循环优于传统for循环 59. 熟悉并使用Java类库 60. 需要精确的结果时避免使用float和double类型 61. 基本类型优于装箱的基本类型 62. 当有其他更合适的类型时就不用字符串 63. 注意字符串连接的性能 64. 通过对象的接口引用对象 65. 接口优于反射 66. 明智谨慎地使用本地方法 67. 明智谨慎地进行优化 68. 遵守普遍接受的命名约定 69. 仅在发生异常的条件下使用异常 70. 对可恢复条件使用检查异常,对编程错误使用运行时异常 71. 避免不必要地使用检查异常 72. 赞成使用标准异常 73. 抛出合乎于抽象的异常 74. 文档化每个方法抛出的所有异常 75. 在详细信息中包含失败捕获信息 76. 争取保持失败原子性 77. 同步访问共享的可变数据 78. 避免过度同步 79. EXECUTORS, TASKS, STREAMS 优于线程 80. 优先使用并发实用程序替代wait和notify 81. 线程安全文档化 82. 明智谨慎地使用延迟初始化 83. 不要依赖线程调度器 84. 其他替代方式优于Java本身序列化 85. 非常谨慎地实现SERIALIZABLE接口 86. 考虑使用自定义序列化形式 87. 防御性地编写READOBJECT方法 88. 对于实例控制,枚举类型优于READRESOLVE 89. 考虑序列化代理替代序列化实例

熟悉并使用Java类库

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

59. 熟悉并使用Java类库

假设想要生成0到某个上界之间的随机整数。对于这个常见的任务,许多程序员会编写一个类似这样的方法:

// Common but deeply flawed!
static Random rnd = new Random();

static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}

这个方法可能看起来不错,但它有三个缺陷。 首先,如果n是一个比较小的2的乘方,则随机数的序列将在相当短的时间段后开始重复。 第二个缺陷是,如果n不是2的乘方,平均而言,某些数字会比其他数字出现得更加频繁。 如果n很大,这种效果可能非常明显。 以下程序有力地证明了这一点,该程序在精心选择的范围内生成了100万个随机数,然后打印出有多少个数字落在范围的上半部分:

public static void main(String[] args) {
    int n = 2 * (Integer.MAX_VALUE / 3);
    int low = 0;
    for (int i = 0; i < 1000000; i++)
        if (random(n) < n/2)
            low++;
    System.out.println(low);
}

如果random方法正常工作,程序将打印接近50万的数字,但如果运行它,你会发现它打印的数字接近666,666。random方法生成的三分之二数字落在其范围的上半部分!

random方法的第三个缺陷是,在极少数情况下,它可能会灾难性地失败,返回超出指定范围之外的数字。 这是因为该方法尝试通过调用Math.absrnd.nextInt()返回的值映射到非负整数。 如果nextInt()返回Integer.MIN_VALUE,则Math.abs也会返回Integer.MIN_VALUE,假设n不是2的乘方,取模运算符(%)将返回负数。 这几乎肯定会导致程序失败,并且可能难以重现。

要编写一个纠正这些缺陷的random方法的版本,你必须知道关于伪随机数生成器,数论和二进制补码算法的知识。幸运的是,你不必这样做 —— 它已经为你完成了,就是Random.nextInt(int)方法。你不必关心它如何完成其​​工作的细节(,如果您很好奇,可以研究文档或源代码)。一位具有算法背景的高级工程师花费了大量时间来设计,实现和测试这种方法,然后向该领域的几位专家展示,以确保其正确性。然后,这个类库经过了beta测试,发布,并被数百万程序员广泛使用了近二十年。该方法尚未发现任何缺陷,但如果发现了缺陷,将在下一个版本中修复。通过使用标准类库,可以利用编写类库专家的知识以及在前人使用它的经验。

从Java 7开始,就不应再使用Random了。 对于大多数用途,选择的随机数生成器现在是ThreadLocalRandom。 它产生更高质量的随机数,而且速度非常快。 在我的机器上,它比Random快3.6倍。 对于fork-join池和并行流的应用,请使用SplittableRandoms

使用这些类库的第二个好处是,不必浪费时间为那些与你的工作关联不大的问题上,而去编写专门的解决方案。如果像大多数程序员一样,那么宁愿将时间花在应用程序上,而不是底层内容上。

使用标准类库的第三个优点是,它们的性能会随着时间的推移而不断提高,而你无需付出任何努力。 因为许多人使用它们并且因为它们被用于行业标准基准测试,所以提供这些类库的组织有强烈的动力使它们运行得更快。 多年来,许多Java平台类库都经过重写,有时甚至是重复编写,从而显着提升性能。

使用类库的第四个优点是它们倾向于随着时间的推移不断增加功能。 如果某个类库遗失了某些东西,开发人员社区就会知道它,并且可能会在后续版本中添加缺少的功能。

使用标准库的最后一个好处是,可以将代码放在主流中。这样的代码更容易被开发人员阅读、维护和重用。

鉴于所有这些优点,使用类库设施优先于专门实现似乎是合乎逻辑的,但许多程序员并不这样做。为什么不呢? 也许他们不知道类库工具设施的存在。 在每个主要版本中,都会向类库中添加许多特性,了解这些新增特性是非常值得的。每次有Java平台的主要版本发布时,都会发布一个web页面来描述它的新特性。这些页面非常值得一读[Java8-feat, Java9-feat]。为了强调这一点,假设你想编写一个程序来打印命令行中指定的URL的内容(这大致与Linux系统下curl命令相同)。 在Java 9之前,这段代码有点乏味,但在Java 9中,transferTo方法被添加到InputStream中。 以下是使用此新方法执行此任务的完整程序:

// Printing the contents of a URL with transferTo, added in Java 9
public static void main(String[] args) throws IOException {
    try (InputStream in = new URL(args[0]).openStream()) {
        in.transferTo(System.out);
    }
}

这些类库太大了,以至于无法学习所有文档[Java9-api],但每个程序员都应该熟悉java.langjava.utiljava.io及其子包的基础知识。 可以根据需要获取其他类库的知识。 总结类库设施超出了本条目的范围,这些设施多年来已经发展得非常庞大。

几个类库特别值得一提。 集合Collection框架和流Stream类库(条目4——-48)应该是每个程序员的基本工具包的一部分,java.util.concurrent中的并发实用程序的也应如此。 该软件既包含了用于简化多线程编程任务的高级实用程序,还包括偏底层的原语,以允许专家编写自己的高级并发抽象。 条目 80和81会讨论java.util.concurrent的高级部分。

有时,类库设施可能无法满足你的需求。需求越专门化,发生这种情况的可能性就越大。虽然第一个冲动应该是使用这些类库,但是如果已经了解了它们在某些领域提供的功能,而这些功能不能满足你的需求,那么可以使用另一种实现。任何有限的类库集所提供的功能总是存在漏洞。如果你在Java平台库中找不到你需要的东西,你的下一个选择应该是寻找高质量的第三方库,比如谷歌的优秀的开源Guava类库[Guava]。如果无法在任何适当的类库中找到所需的功能,可能别无选择,你只能自己实现了。

总而言之,不要重新发明轮子。 如果需要做一些似乎应该相当常见的事情,那么类库中可能已经有了一个可以满足你需求的工具。 如果有,请使用它; 如果不知道,请检查。 一般来说,类库代码可能比您自己编写的代码更好,并且可能会随着时间的推移而改进。 这并不反映你作为程序员的能力。 规模经济决定了类库代码得到的关注远远超过大多数开发人员可以承担的相同的功能。