Effective.Java第56-66条(规范相关)
56. 为所有已公开的API元素编写文档注释
要正确地记录API,必须在每个导出的类、接口、构造方法、方法和属性声明之前加上文档注释。如果一个类是可序列化的,还需要记录它的序列化形式。
文档注释在源代码和生成的代码中都应该是可读的通用原则。
类或接口中的两个成员方法或构造方法不应具有相同的概要描述。
总结起来就是一句话,文档注释要规范。
阿里规约如下:
【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 // xxx 方式。
【强制】所有的抽象方法(包括接口中的方法) 必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
【强制】所有的类都必须添加创建者和创建日期。
【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途
57. 最小化局部变量的作用域
最重要的技术就是在使用变量之前再首次声明变量。
几乎每个局部变量都应该包含一个初始化器。
58. for-each循环优于普通for循环
for-each官方称为增强for循环。如下:看到冒号(:)读作in。下面的循环读作对于elements中的每个元素e。
for (Element e: elements) { }
有三种情况不能使用for-each循环:
有损过滤:如果需要遍历集合并删除元素不能用,需要使用迭代器。
转换:如果需要转换集合的部分元素或值,需要用到下标的话需要用普通for循环。
并行迭代:如果需要并行遍历多个集合,那么需要显示地控制迭代器或索引遍历。可以使用普通for循环。
for-each循环在清晰度,灵活性和错误预防方面提供了超越传统for循环的优势,而且没有性能损失。
59. 了解并使用库
通过使用标准库,你可以利用编写它的专家的知识和以前使用者的经验。
每个程序员都应该熟悉java.lang、java.util和java.io的基础知识以及其子包。其实java.util.concurrent包也是非常重要的。
第三方jar包的话有commons-lang、commons-io、commons-collections相关jar包。
总之,不要重复造轮子。如果你需要做一些看起来相当常见的事情,那么库中可能已经有一个工具可以做你想做的事情。
60. 若需要精确答案就避免使用float和double
float和double不能再需要精确计算的地方使用,尤其是货币计算。
比如:
float num1 = 1.6F; float num2 = 0.9255F; System.out.println(num1 - num2);
结果:
0.67450005
解决办法:使用BigDecimal,注意使用其String类型的构造方法不要使用float或double,避免在计算中引入不准确的值。
float num1 = 1.6F; float num2 = 0.9255F; BigDecimal bidnumber1 = new BigDecimal(String.valueOf(num1)); BigDecimal bidnumber2 = new BigDecimal(String.valueOf(num2)); BigDecimal subtract = bidnumber1.subtract(bidnumber2); float floatValue = subtract.floatValue(); System.out.println(floatValue);
总之,对于任何需要精确答案的计算,不使用float或double。如果希望系统处理十进制小数点,并且不介意不使用基本类型带来的不便和成本,就使用BigDecimal。使用BigDecimal的另一个好处是,它可以完全控制舍入模式。
61. 基本数据类型优于包装类型
自动装箱和自动拆箱模糊了基本类型和包装类型之间的区别,但不会消除它们。主要区别有下面三条:
第一:基本类型只有它们的值,而包装类型具有与值不同的标识,换句话说,两个相同包装类型具有相同的值和不同的标识。
第二:基本类型只有全功能值,而包装类型除了基本类型的所有功能值外,还有一个非功能值,即null。(将 == 操作符应用于包装类型几乎都是错误的)
第三:基本类型比包装类型更节省时间和空间。
在操作中混合使用基本类型和包装类型时,包装类型就会自动拆箱,如果一个空对象自动拆箱会引发NPE空指针异常。如下:
Long sum = 0L; for (int i = 0; i < 500; i++) { sum += i; }
这个程序比它预期的慢的多,因为它意外地声明了一个局部变量(sum),它是包装类型而不是基本类型。变量会被反复的装箱和拆箱,导致产生明显的性能问题。
什么情况下应该使用包装类型?
第一个是作为集合中的元素、键值;第二个是进行反射方法调用时必须使用包装类型。
62. 当使用其他类型更合适时应避免使用字符串
字符串类型是其他值类型的糟糕替代品。比如int、float就用其类型表示,不能用字符串表示;表示是或否就用布尔类型。
字符串是枚举类型的糟糕替代品。
字符串是聚合类型的糟糕替代品。
总之,当存在或可以编写更好的类型时,应避免使用字符串来表示对象。
63. 当心字符串链接引起的性能问题
字符串链接操作(+)是将几个字符串拼接在一起的简便方法。我们也知道字符串的拼接操作是通过生成StringBuilder并调用其append方法进行拼接。
当拼接项足够多时,考虑使用StringBuilder的append方法代替。
64. 通过接口引用对象
如果存在合适的接口类型,那么应该使用接口类型声明参数、返回值、变量和字段。唯一真正需要引用对象的类的时候是使用构造函数创建它的时候。
如果养成使用接口引用对象的习惯,将会更加灵活。如果决定切换实现,只需在构造函数切换类名(或使用不同的静态工厂)
注意:如果原实现提供了接口约定的其他特殊方法,并且依赖于该功能,那么只能用该类接收对象。比如:如下代码只能用LinkedList接收对象引用。
LinkedList<String> lists = new LinkedList<>(); lists.peekFirst();
如果没有合适的接口存在,那么类引用对象是完全合适的。比如String等。
65. 接口优于反射
核心反射机制 java.lang.reflect 提供对任意类的编程访问。给定一个 Class 对象,你可以获得Constructor、Method 和 Field 实例,分别代表了该 Class 实例所表示的类的构造器、方法和字段。这些对象提供对类的成员名、字段类型、方法签名等的编程访问。
此外,Constructor、Method 和 Field 实例允许你反射性地操作它们的底层对应项:你可以通过调用 Constructor、Method 和 Field 实例上的方法,可以构造底层类的实例、调用底层类的方法,并访问底层类中的字段。例如,Method.invoke 允许你在任何类的任何对象上调用任何方法(受默认的安全约束)。
反射允许一个类使用另一个类,即使在编译前者时后者并不存在。然而,这种能力是有代价的:
(1)失去了编译时类型检查的所有好处,包括异常检查。
(2)执行反射访问所需的代码笨拙又冗长。
(3)性能降低。反射比普通方法调用慢得多。
有一些复杂的程序需要用到反射,比如代码分析工具和依赖注入框架。
通过非常有限的形式使用反射,你可以获得反射的许多好处,同时花费的代价很少。
反射的合法用途(很少)是管理类对运行时可能不存在的其他类、方法或字段的依赖关系。如果编写的程序必须在编译时处理未知 类,则应该尽可能使用反射实例化对象,并在编译时已知的接口或超类访问对象。(比如静态工厂的反射创建对象)
66. 明智审慎地本地方法
Java本地接口(JNI)允许Java调用本地方法,这些方法是c或c++编写而成。本地方法一般有三种用途:提供对特定于平台的设施的访问、提供对现有本地代码库的访问、通过本地语言辨析应用程序中注重性能的部分以提高性能。
为了提高性能,很少建议使用本地方法。