高效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. 考虑序列化代理替代序列化实例

始终使用Override注解

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

40. 始终使用Override注解

Java类库包含几个注解类型。对于典型的程序员来说,最重要的是@Override。此注解只能在方法声明上使用,它表明带此注解的方法声明重写了父类的声明。如果始终使用这个注解,它将避免产生大量的恶意bug。考虑这个程序,在这个程序中,类Bigram表示双字母组合,或者是有序的一对字母:

// Can you spot the bug?

public class Bigram {

    private final char first;

    private final char second;



    public Bigram(char first, char second) {

        this.first  = first;

        this.second = second;

    }

    public boolean equals(Bigram b) {

        return b.first == first && b.second == second;

    }

    public int hashCode() {

        return 31 * first + second;

    }



    public static void main(String[] args) {

        Set<Bigram> s = new HashSet<>();

        for (int i = 0; i < 10; i++)

            for (char ch = "a"; ch <= "z"; ch++)

                s.add(new Bigram(ch, ch));

        System.out.println(s.size());

    }

}

主程序重复添加二十六个双字母组合到集合中,每个双字母组合由两个相同的小写字母组成。 然后它会打印集合的大小。 你可能希望程序打印26,因为集合不能包含重复项。 如果你尝试运行程序,你会发现它打印的不是26,而是260。它有什么问题?

显然,Bigram类的作者打算重写equals方法(条目 10),甚至记得重写hashCode(条目 11)。 不幸的是,我们倒霉的程序员没有重写equals,而是重载它(条目 52)。 要重写Object.equals,必须定义一个equals方法,其参数的类型为Object,但Bigram的equals方法的参数不是Object类型的,因此Bigram继承Object的equals方法,这个equals方法测试对象的引用是否是同一个,就像==运算符一样。 每个祖母组合的10个副本中的每一个都与其他9个副本不同,所以它们被Object.equals视为不相等,这就解释了程序打印260的原因。

幸运的是,编译器可以帮助你找到这个错误,但只有当你通过告诉它你打算重写Object.equals来帮助你。 要做到这一点,用@Override注解Bigram.equals方法,如下所示:

@Override public boolean equals(Bigram b) {

    return b.first == first && b.second == second;

}

如果插入此注解并尝试重新编译该程序,编译器将生成如下错误消息:

Bigram.java:10: method does not override or implement a method

from a supertype

    @Override public boolean equals(Bigram b) {

    ^

你会立刻意识到你做错了什么,在额头上狠狠地打了一下,用一个正确的(条目 10)来替换出错的equals实现:

@Override public boolean equals(Object o) {

    if (!(o instanceof Bigram))

        return false;

    Bigram b = (Bigram) o;

    return b.first == first && b.second == second;

}

因此,应该在你认为要重写父类声明的每个方法声明上使用Override注解。 这条规则有一个小例外。 如果正在编写一个没有标记为抽象的类,并且确信它重写了其父类中的抽象方法,则无需将Override注解放在该方法上。 在没有声明为抽象的类中,如果无法重写抽象父类方法,编译器将发出错误消息。 但是,你可能希望关注类中所有重写父类方法的方法,在这种情况下,也应该随时注解这些方法。 大多数IDE可以设置为在选择重写方法时自动插入Override注解。

大多数IDE提供了是种使用Override注解的另一个理由。 如果启用适当的检查功能,如果有一个方法没有Override注解但是重写父类方法,则IDE将生成一个警告。 如果始终使用Override注解,这些警告将提醒你无意识的重写。 它们补充了编译器的错误消息,这些消息会提醒你无意识重写失败。 IDE和编译器,可以确保你在任何你想要的地方和其他地方重写方法,万无一失。

Override注解可用于重写来自接口和类的方法声明。 随着default默认方法的出现,在接口方法的具体实现上使用Override以确保签名是正确的是一个好习惯。 如果知道某个接口没有默认方法,可以选择忽略接口方法的具体实现上的Override注解以减少混乱。

然而,在一个抽象类或接口中,值得标记的是你认为重写父类或父接口方法的所有方法,无论是具体的还是抽象的。 例如,Set接口不会向Collection接口添加新方法,因此它应该在其所有方法声明中包含Override注解以确保它不会意外地向Collection接口添加任何新方法。

总之,如果在每个方法声明中使用Override注解,并且认为要重写父类声明,那么编译器可以保护免受很多错误的影响,但有一个例外。 在具体的类中,不需要注解标记你确信可以重写抽象方法声明的方法(尽管这样做也没有坏处)。