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

遵守普遍接受的命名约定

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

  1. 遵守普遍接受的命名约定

Java平台有一组完善的命名约定(naming conventions),其中许多约定包含在Java语言规范[JLS, 6.1]中。宽泛地说,命名约定分为两类:字面(typographical)的和语法的(grammatical)。

只有少量的字面的命名约定,包括包、类、接口、方法、属性和类型变量。你不应该违反它们,而且没有理由去违反。如果API违反了这些约定,那么它可能很难使用。如果实现违反了这些规则,可能很难维护。在这两种情况下,违反约定都有可能使其他使用代码的程序员感到困惑和恼怒,并可能导致他们做出错误的假设,从而导致错误。本条目概述了各个命名约定。

包和模块名称应该是分层的,每个部分以句点分隔。 每个部分应包含小写字母字符,很少包含数字。任何在你的组织外部使用的包的名称都应该以你的组织的Internet域名开头,但包名正好相反,例如,edu.cmu,com.google,org.eff。 名称以java和javax开头的标准类库和可选包是此规则的例外。 用户不得创建名称以java或javax开头的包或模块。 可以在JLS [JLS, 6.1]中找到将Internet域名转换为包名称前缀的详细规则。

包名的其余部分应该由描述包的一个或多个组件构成。组件应该很短,通常为8个或更少的字符。鼓励使用有意义的缩写,例如util而不是utilities。缩写词是可以接受的,例如awt。组件通常应该由一个单词或缩写组成。

除了Internet域名之外,许多包的名称只包含一个组件。 其他组件适用于大型设施,其大小要求将其分解为非正式层次结构。 例如,javax.util包具有丰富的包层次结构,其名称如java.util.concurrent.atomic。 这样的包被称为子包,尽管几乎没有语言对包层次结构提供支持。

类和接口名称(包括枚举和注解类型名称)应由一个或多个单词组成,每个单词的首字母大写,例如List或FutureTask。 除了首字母缩略词和某些常用缩写(如max和min)之外,应避免使用缩写。 关于首字母缩略词是大写还是仅首字母大写,存在一些分歧。 虽然一些程序员仍然使用大写字母,但是可以做出强有力的论证,只支持大写第一个字母:即使多个首字母缩写连续出现,仍然可以知道一个单词从哪里开始,下一个单词从哪里结束。 你更喜欢看哪个类名,HTTPURL或HttpUrl?

方法和属性名遵循与类和接口名相同的字面约定,除了方法或属性名的第一个字母应该是小写,例如remove或ensureCapacity。 如果首字母缩略词作为方法或属性名称的第一个单词出现,则它应该是小写的。

前面规则的唯一例外是“常量属性”,它的名称应该由一个或多个大写单词组成,由下划线分隔,例如VALUES或NEGATIVE_INFINITY。常量属性是一个静态的final属性,其值是不可变的。如果静态final属性具有基本类型或不可变引用类型(条目 17),那么它就是常量属性。例如,枚举常量是常量属性。如果静态final属性有一个可变的引用类型,那么如果所引用的对象是不可变的,那么它仍然可以是一个常量属性。注意,常量属性是唯一推荐的下划线用法。

局部变量名称与成员名称具有相似的字面命名约定,但允许使用缩写除外,单个字符和短字符序列的含义取决于它们出现的上下文,例如i,denom,houseNum。 输入参数是一种特殊的局部变量。 它们的名称应该比普通的局部变量更加仔细,因为它们的名称是其方法文档中不可或缺的一部分。

类型参数名通常由单个字母组成。最常见的是以下五种类型之一:T表示任意类型,E表示集合的元素类型,K和V表示映射的键和值类型,X表示异常。方法的返回类型通常为R。任意类型的序列可以是T、U、V或T1、T2、T3。

为了快速参考,下表列出了字面约定的示例。

标识符类型 示例
包名或模块 org.junit.jupiter.api, com.google.common.collect
类或接口 Stream, FutureTask, LinkedHashMap, HttpClient
方法或属性 remove, groupingBy, getCrc
常量属性 MIN_VALUE, NEGATIVE_INFINITY
局部变量 i, denom, houseNum
类型参数 T, E, K, V, X, R, U, V, T1, T2

语法命名约定比字面约定更灵活,也更有争议。包没有语法命名约定。可实例化的类,包括枚举类型,通常使用一个或多个名词短语来命名,例如Thread、PriorityQueue或ChessPiece。不可实例化的实用程序类(条目 4)通常使用复数名词来命名,例如Collector或Collections。接口的名称类似于类,例如Collection或Comparator,或者以able或ible结尾的形容词,例如Runnable、Iterable或Accessible。因为注解类型有如此多的用途,所以没有哪部分词性占主导地位。名词、动词、介词和形容词都很常见,例如,BindingAnnotation、Inject、ImplementedBy或Singleton。

执行某些操作的方法通常使用动词或动词短语(包括对象)命名,例如append或drawImage。 返回boolean类型的方法通常具有以单词is,或不太常用的has开头的名称,后跟名词,名词短语或任何用作形容词的单词或短语,例如isDigit,isProbablePrime,isEmpty, isEnabled,或hasSiblings。

方法返回被调用对象的非boolean的方法或属性,通常使用以get开头的名词、名词短语或动词短语来命名,例如size、hashCode或getTime。有一种说法是,只有第三种形式(以get开头)才是可接受的,但这种说法几乎没有根据。前两种形式的代码通常可读性更强,例如:

if (car.speed() > 2 * SPEED_LIMIT)
    generateAudibleAlert("Watch out for cops!");

以get开头的形式起源于基本过时的Java bean规范,该规范构成了早期可重用组件体系结构的基础。有一些现代工具继续依赖于Beans命名约定,你可以随意在任何与这些工具结合使用的代码中使用它。如果类同时包含相同属性的setter和getter,则遵循这种命名约定也有很好的先例。在本例中,这两个方法通常被命名为getAttribute和setAttribute。

一些方法名称值得特别提及。 转换对象类型,返回不同类型的独立对象的实例方法通常称为toType,例如toString或toArray。 返回类型与接收对象类型不同的视图(条目 6)的方法通常称为asType,例如asList。 返回与调用它们的对象具有相同值的基本类型的方法通常称为typeValue,例如intValue。 静态工厂的常用名称包括from,of,valueOf,instance,getInstance,newInstance,getType和newType(条目 1,第9页)。

属性名称的语法约定不太完善,并且不如类,接口和方法名称那么重要,因为设计良好的API包含很少的暴露属性。 boolean类型的属性通常被命名为boolean 访问器方法,省略了初始的is前缀,例如,initialized,composite。 其他类型的属性通常以名词或名词短语命名,例如height,digits或bodyStyle。 局部变量的语法约定类似于属性,但甚至更弱。

总之,将标准命名约定内在化,并将其作为第二天性来使用。字面约定是直接的,而且在很大程度上是明确的;语法约定更加复杂和松散。引用Java语言规范[JLS, 6.1]中的话说,“如果长期以来的传统用法要求不遵循这些约定,就不应该盲目地遵循这些约定”。使用常识。