38.检查参数的有效性

每当编写方法或者构造器时, 应该考虑它的参数有哪些限制, 应该把这些限制写到文档中, 并且在这个方法体的开头处, 通过显式的检查来实施这些限制.
原则是”应该在发生错误之后尽快检测到错误”!

39.必要时进行保护性拷贝

联系不可变类进行思考
代码示例: 因为Date对象是可变的, 因此该类线程不安全, 且最后返回的Date对象还可以被改变

  1. /**
  2. * Period
  3. *
  4. * @author gavin
  5. * @version 2020/7/27
  6. */
  7. public class Period {
  8. private final Date start;
  9. private final Date end;
  10. public Period(Date start, Date end) {
  11. if (start.compareTo(end) > 0) {
  12. throw new IllegalArgumentException(start + " after " + end);
  13. }
  14. this.start = start;
  15. this.end = end;
  16. }
  17. public Date start() {
  18. return start;
  19. }
  20. public Date end() {
  21. return end;
  22. }
  23. }

改进:

  • 对于构造器的每个可变参数进行保护性拷贝, 而不使用原始的对象

此处注意保护性拷贝是在检查参数的有效性之前进行的, 并且有效性检查是针对拷贝之后的对象, 而不是针对原始的对象. 虽然这样做看起来有点不太自然, 却是必要的, 这样做可以避免在”危险阶段”期间从另一个线程改变类的参数, 这里的危险阶段是指从检查参数开始, 直到拷贝参数之间的时间段.

同时注意, 这里没有用Date的clone方法来进行保护性拷贝, 因为Date是非final的, 不能保证clone方法一定返回Date的对象, 对于参数类型可以被不可信任方子类化的参数, 请不要使用clone方法进行保护性拷贝

  • 修改访问方法, 使它返回可变内部域的保护性拷贝
  1. public class Period {
  2. private final Date start;
  3. private final Date end;
  4. public Period(Date start, Date end) {
  5. if (start.compareTo(end) > 0) {
  6. throw new IllegalArgumentException(start + " after " + end);
  7. }
  8. this.start = new Date(start.getTime());
  9. this.end = new Date(end.getTime());
  10. }
  11. public Date start() {
  12. return new Date(start.getTime());
  13. }
  14. public Date end() {
  15. return new Date(end.getTime());
  16. }
  17. }

总结
每当编写方法或者构造器时, 如果它要允许客户提供的对象进入到内部数据结构之中, 则有必要考虑一下, 客户提供的对象是否有可能是可变的. 如果是, 就要考虑你的类是否能够容忍对象进入数据结构之后发生变化, 如果答案是否定的, 就必须对该对象进行保护性拷贝.

40.谨慎设计方法签名

  • 谨慎的选择方法的名称. 遵循标准的命名习惯, 易于理解, 与同一个包中其他名称风格一致, 大众认可的名称
  • 不要过于追求遍历的方法
  • 避免过长的参数列表. 目标是四个参数, 或者更少

有三种方法可以缩短过长的参数列表:
1.将方法分解成多个方法, 每个方法只需要这些参数的一个子集;
2.创建辅助类, 用来保存参数的分组
3.从对象创建到方法调用都采用builder模式

  • 对于参数类型要优先使用接口而不是类
  • 对于boolean参数, 要优先使用两个元素的枚举类型, 它使代码更易阅读和编写

41.慎用重载

安全而保守的策略是, 永远不要导出两个具有相同参数数目的重载方法, 如果方法使用可变参数, 保守的策略是根本不要重载它.
自动装箱和泛型成了java语言的一部分之后, 谨慎重载显得更加重要了, 例如remove(E)和remove(i);

总结
一般情况下, 对于多个具有相同参数数目的方法来说, 应该尽量避免重载方法;
若重载不可避免, 至少应该避免同一组参数只需经过类型转换就可以被传递给不同的重载方法;
如果不能避免, 就应该保证, 当传递同样的参数时, 所有重载方法的行为必须一致.

最后一种情况即是, 程序员可能并不知道哪个重载函数会被调用, 但只要这两个方法返回相同的结果就行, 确保这种行为的标准做法是, 让更具体化的重载方法把调用转发给更一般化的重载方法.

42.慎用可变参数

可变参数方法接收0个或者多个指定类型的参数. 可变参数机制通过先创建一个数组, 数组的大小为在调用位置所传递的参数数量, 然后将参数值传到数组中, 最后将数组传递给方法.
可变参数方法每次调用都会导致进行一次数组分配和初始化, 如果凭经验确定无法承受这一成本, 但又需要可变参数的灵活性, 即可以针对多数调用情况创建多个重载方法.

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

好处:

  • 不必专门对返回值做非null处理, 避免了漏写的隐患

至于性能问题;

  • 在这个级别上担心性能问题是不明智的
  • 每次都返回同一个零长度数组是有可能的, 零长度数组不可变, 可以共享, 例如Collections.emptySet等.

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