Effective Java 读书笔记(六):方法

来源:互联网 发布:js正则表达式 冒号 编辑:程序博客网 时间:2024/05/29 08:00

  • Effective Java 读书笔记六方法
    • 检查参数的有效性
    • 必要时进行保护性拷贝
    • 谨慎设计方法签名
    • 慎用重载
    • 慎用可变参数
    • 返回零长度的数组和集合而不是 null
    • 为所有导出的 API 元素编写文档注释

Effective Java 读书笔记(六):方法

检查参数的有效性

绝大多数方法和构造器对于传递给它们的参数都会有某种限制,你应该在文档中清楚地指明所有这些限制,并且在方法体的开头处检查参数,以强制施加这些限制。这是“应该在发生错误之后,尽快检测出错误”这一普遍原则的一个具体情形。如果不能做到这一点,检测到错误的可能性就比较小,即使检测到错误了,也难以确定错误的根源。不做参数检查有什么危害呢?
1. 该方法可能在处理过程中失败,并且产生令人费解的异常。
2. 更糟糕的是,方法正常返回,但计算出了错误的结果。
3. 最糟糕的是,方法正常返回,但却使某个对象处于被破坏的状态,使得将来在某个不确定的时候,在不相关的点上引发错误。

对于公有的方法,要用 Javadoc 的 @throws 标签在文档中说明,如果违反了参数值限制时,会抛出哪些异常,比如 IllegalArgumentException、IndexOutOfBoundsException 或 NullPointerException。

必要时进行保护性拷贝

如果类具有从客户端得到或返回到客户端的可变组件,类就必须保护性地拷贝这些组件。如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当地修改组件,就可以在文档中指明“客户端的职责是不得修改受到影响的组件”,以此来代替保护性拷贝。

public final class Period {    private final Date start;    private final Date end;    public Period(Date start, Date end) {        if (start.compareTo(end) > 0) {            throw new IllegalArgumentException(start + " after " + end);        }        this.start = start;        this.end = end;    }    public Date start() {        return start;    }    public Date end() {        return end;    }}

对于 Period 类来说,由于 Date 类是可变的,导致虽然 Period 没有 set 之类的修改方法,也很容易篡改其字段的值。故而,适当的保护性拷贝是必要的。

public final class Period {    private final Date start;    private final Date end;    // 构造方法保护性拷贝    public Period(Date start, Date end) {        this.start = new Date(start.getTime());        this.end = new Date(end.getTime());        if (this.start.compareTo(this.end) > 0) {            throw new IllegalArgumentException(this.start + " after " + this.end);        }    }    // 读方法保护性拷贝    public Date start() {        return new Date(start.getTime());    }    public Date end() {        return new Date(end.getTime());    }}

上面构造方法中,参数检查必须在保护性拷贝之后,这是为了避免 Time-Of-Check/Time-Of-Use 攻击。

谨慎设计方法签名

这里是一些设计易学、易用 API 的技巧:

  1. 谨慎选择方法名称:遵循命名习惯、项目风格。
  2. 方法不要太多,太多了难以维护。
  3. 避免过长的参数列表。一般多于 4 个参数,就容易让人记不住。如果参数很多时,怎么办?
    • 分解方法成多个。
    • 使用辅助类,将多个参数封装到一个方法中。
    • 从对象构建到方法调用都采用 Builder 模式。
  4. 对于参数类型,要优先使用接口,而非类。比如一般都用 Map,而不用 HashMap。
  5. 对于 boolean 参数,优先使用枚举类型(如果这个参数后续可能扩展其他值的话)。

慎用重载

对于以下类会输出什么呢?答案是 collection。因为具体要调用哪个重载 overloading 方法是在编译时做出决定的,而调用哪个覆盖 override 方法是在运行时做出决定的。

public class Test {    public static String name(Set<?> set) {        return "set";    }    public static String name(List<?> list) {        return "list";    }    public static String name(Collection<?> collection) {        return "collection";    }    public static void main(String[] args) {        Collection<Integer> collection = Lists.newArrayList();        System.out.println(name(collection));    }}    

如果两个重载方法的参数个数相同,就要小心了,最好避免这种情况,减少误用。

慎用可变参数

可变参数有什么问题呢?
1. 需要创建数组,数组耗费性能,不适合性能要求极高的程序。
2. 可能需要额外检查传 0 个参数的情况。
3. 举例来说,在 Arrays.asList 方法中,传递 int[] 对象时,无法正常工作,该方法会把整个 int[] 当初 List 的第一个元素。

返回零长度的数组和集合,而不是 null

返回 null 就需要让调用方去做额外的判断(这很容易被遗忘),同时也让代码变得臃肿。如果担心返回空数组或集合影响性能的话,可以将空数组或集合做成常量,这正是 Collections 的 emptyList、emptyMap 方法所提供的功能。

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

为了正确地编写 API 文档,必须在每个被导出的类、接口、构造器、方法和域声明之前增加一个文档注释。为了编写可维护的代码,还应该为那些没有被导出的类、接口等等编写注释。

方法的文档注释应该简洁地描述出它和客户端之间的约定。具体描述什么呢?
1. 前提条件:客户端调用方法必须满足的条件。
2. 后置条件:调用成功之后,哪些条件会被满足。
3. 副作用:系统状态中可以观察到的变化,它不是为了获得后置条件而明确要求的变化。
4. 是否线程安全?是否可序列化?
5. @param 标签说明参数。
6. @return 说明返回值。
7. @throws 说明什么情况下,抛出哪些异常?
8. 每个文档注释的第一句话,成为注释所属元素的概要描述。
9. 对于泛型、注解、枚举这些特殊类型,也有用文档描述其中的类型参数、成员变量、常量。
10. 如果多个 API 之间有关联,那应该有一个总的文档来描述这组 API 的总体结构。

关于编写文档的指导,可以查看如何写Java文档注释。

原创粉丝点击