Chapter 7 Methods

Item 38: Check parameters for validity

直接举例吧:

/**
* ...其他的被我省略了
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0)
throw new ArithmeticException("Modulus <= 0: " + m);
... // Do the computation
}

这里记得要写文档。

对于那些unexported method(也就是自己内部用,而不公开到API里的方法),一般是用assertions,比如:

private static void sort(long a[], int offset, int length) {
assert a != null;
assert offset >= 0 && offset <= a.length;
... // Do the computation
}

想让他们有效,需要指定-ea (or -enableassertions) flag to the java interpreter。

Item 39: Make defensive copies when needed

主要是如果你的类有指向mutable对象的field的话,要进行defensive copy,举个例子,假设下面这个类中的date这个field是“内部实现”:

public final class Example{
private final Date date;
public Example(Date date,) {
this.date = date;
}
public Date getDate() {
return date;
}

Java中的Date是mutable的,所以很容易破坏这个类的内部field:

Date date = new Date();
Example example = new Example(date);
date.setYear(78);

所以我们要把constructor改成:

public Example(Date date,) {
this.date= new Date(date.getTime());
//对date这个field需要满足的条件进行验证
if (this.date...) {throw ...}
}

为什么要先defensive copy,再进行验证?因为如果先验证再copy的话,可能中间会被别的线程修改。

注意这里不能用Date的clone方法,因为Date是非final的,所以clone方法返回的可能是一个恶意的子类对象。

接着,getter方法也需要修改,因为很容易通过getter破坏内部field:

Date date = example.getDate();date.setYear(78);

所以需要这么修改,返回一个自己private field的copy:

public Date getDate() {
return new Date(date.getTime());
}

注意这里我们是可以用clone的,因为刚才由于我们对constructor的修改,内部的date field已经可以保证绝对就是一个Date对象,而不是什么Date子类对象。

其实这个例子告诉我们的真正教训是:最好用immutable的对象作为component。

由于defensive copy导致额外的性能损失,有的时候如果能保证调用方是可信的,比如

这个被调用的class和调用方都在同一个package,那么可以不用defensive copy,但是必须在文档中说明“谁都不能改这个对象”。

Item 40: Design method signatures carefully

本条item主要是讲你在设计API时候需要注意的事项:

一.Choose method names carefully。遵守标准的命名约定,以及同一个package中命名的一致性。

二.不要追求提供convenience method。因为这意味着更多的文档,维护,测试。只有真正需要时,再提供。如果不确定,就别提供。

三.不要定义过多参数,最好四个或四个以下。有一些技巧,比如将方法拆分成几个不同的方法,或者创建(inner)helper classes,或者使用item2介绍的Builder pattern。

四.For parameter types, favor interfaces over classes。

五.Prefer two-element enum types to boolean parameters。这是为了可读性和可维护性考虑的。

Item 41: Use overloading judiciously

对于重载方法的选择,是发生在编译时的。比如如果有两个重载方法,唯一不同的是接收参数的类型分别是Set<?>Collection<?>,然后你定义了一个变量:

Collection<?> c = new HashSet<String>()

然后把c传给刚才的重载方法里去的时候,由于编译时c的静态类型是Collection<?>,所以会选Collection<?>的版本,而不会选Set<?>的版本。

所以在API中尽量不要设计让人感到疑惑的方法重载,也就是让调用方无法确定哪个方法会被调用,保守的做法是尽量不要定义参数数量相同的重载方法,因为最多不过就是改个名字的事儿,比如ObjectOutputStream中的writeBoolean(boolean), writeInt(int), 和writeLong(long)。或者说如果两个类“完全没关系”,也就是neither one is an instance of the other one,那么重载也是安全的。如果不遵照这种简单的做法,编译器如何做出选择的规则是十分复杂的(我记得在看C# in depth的时候就是),在Java language specification中占了非常多篇幅,而很少有人真得理解这些规则。

有的时候如果两个重载方法做的事实际上一模一样,那么你可以违背上面说的简单规则,比如String中有两个重载方法分别接受StringBuffer和CharSequence类型的参数(CharSequence是被StringBuffer, StringBuilder, String, CharBuffer等类实现的接口),想让这两个方法的行为完全一致,只要让“范围小的”去调用“范围更大的那个”就行了:

public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence) sb);
}

所以我们完全不在意编译器会选择哪个方法,因为反正最后的行为都是一样的。当然,Java库中也有一些违背上述简单原则的重载方法,可能会造成困惑,所以尽量避免。

下面举个造成困惑的例子:

Set<Integer> set = new TreeSet<Integer>();
List<Integer> list = new ArrayList<Integer>();
for (int i = -3; i < 3; i++) {
set.add(i);
list.add(i);
}
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove(i);
}
System.out.println(set + " " + list);

这里有个很贱的陷阱,其中两个remove的签名分别是Set.remove(Object o)和List.remove(int index),所以Set的remove是会移除某个元素,而List的remove是会移除某个位置的元素。这其中的历史原因是因为在Java引进自动装箱之前,List的remove(int index)和remove(Object o)是不可能导致困惑的,因为int和Object基本就是完全不搭嘎的两个玩意儿,而引入自动装箱后,List.remove方法“遭到破坏”。要得到期望的结果,需要改成list.remove((Integer) i)。

Item 42: Use varargs judiciously

你不应该把一个 以一个数组作为最后一个参数的方法 修改成varargs parameter,虽然这么做不会影响到已经存在的client code。为什么呢?举个例子(我自己想的例子):比如你把public static void test(Object[] objs)改成了public static void test(Object... objs),那么你如果int[] ints = {1,2,3};test(ints),就只能是一个“有一个元素的Object[]数组”,这个唯一的元素是一个数组对象{1,2,3},而你实际期待的行为应是:Integer[] ints =...;test(ints),这样的话就是一个“有三个元素的Object[]数组,三个元素分别是1,2,3的Integer对象”了。

所以慎用varargs,只有真正需要的时候再用。如果为了性能考虑(因为创建数组需要开销)你可以用以下方法:

public void foo() { }
public void foo(int a1) { }
public void foo(int a1, int a2) { }
public void foo(int a1, int a2, int a3) { }
public void foo(int a1, int a2, int a3, int... rest) { }

这让我想到了Console.WriteLine对各种primitive type都写了重载方法,避免不要的装箱。

Item 43: Return empty arrays or collections, not nulls

先举个反例:

private final List<Cheese> cheesesInStock = ...;
public Cheese[] getCheeses() {
if (cheesesInStock.size() == 0) return null;
...
}

这样会造成client每次都要做额外的null检查,其实是不必要的。所以你应该直接返回一个empty的List。另外,如果要对性能考虑的话,一个长度为0的数组对象肯定是immutable的对吧,那么你可以每次都返回这个相同的空数组对象(如果你需要返回一个空的数组或一个空的集合的时候),比如:

// The right way to return an array from a collection
private final List<Cheese> cheesesInStock = ...;
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

List的toArray的文档中说了:你传给它的参数决定了它返回数组的类型,如果你传给它的参数(一个数组)的容量够的话,那就返回你传的这个数组,否则它会自己new一个。所以上面的方法中,如果对应的List是empty的话,每次都会返回同一个数组(EMPTY_CHEESE_ARRAY )。如果你要返回一个Collection的话,可以用比如Collections.emptyList()。

Item 44: Write doc comments for all exposed API elements

你应该用doc comment给每一个要export的class, interface, constructor, method, field declaration都注上文档。你甚至应该为大部分unexported的类写上文档,为了更好的可维护性。

doc方法的时候应该简要地写出这个方法是干嘛的,而不是how it does the job(Item 17中说的专门用来被继承的方法例外),并应该说明参数应该满足的条件(一般是通过@throws这个tag说明),以及造成的各种“side effects”(比如改变了某个对象,或者start了一个background thread)。以及还应该说明thread safety和是否能被序列化。具体来说,对于每一个参数都要写对应的@param tag,除非是void否则就要写@return tag,用@throws说明每一个不管che不check的Exception。根据约定,@param tag@return tag后面都是先跟一个名词,说明这玩意儿代表什么,而且@param, @return, or @throws tag都不以句号结束,请看示例:

/**
* Returns the element at the specified position in this list.
*
* <p>This method is <i>not</i> guaranteed to run in constant time. In some implementations it
* may run in time proportional to the element position.
*
* @param index index of element to return; must be non-negative and less than the size of this list
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= this.size()})
*/
E get(int index);

你可以用HTML标签,但没必要弄得太花俏和复杂。注意{@code} tag的作用是第一让代码高亮,第二别让HTML解释成标签(也就是说你可以随便使用大于小于号,而不需要用HTML的转义字符)。如果有多行代码,请用:<pre>{@ 多行代码}</pre>

By convention,注释中的this这个词儿一般都是指instance method的对象。

如果你懒得用HTML转义字符,那么可以用{@literal} tag,示例如下:

* The triangle inequality is {@literal |x + y| < |x| + |y|}.

大于小于号随便写。这个tag和{@code}类似,只是不提供代码高亮功能。作者说用这个是为了让注释文档在源代码中也有很好的可读性,我非常同意。

注意每一个doc comment的第一句话是一句概括性的说明(summary description),比如上面的“Returns the element at the specified position in this list.”。同一个类中的两个或多个constructors,以及重载的方法,都不应该有相同的summary description。另外,在这个summary description中以“句号后面跟一个空格”作为结束符(我估计就是显示成HTML的时候只显示前面这么多作为总的概括说明),所以你需要把并不是作为结束符的某些字符用{@literal }括起来,如{@literal M.S.}。对于方法来说,这句summary description一般以动词开头,对于class和Interface以一个名词开头。

如果有泛型参数的话,记得也要描述一下,比如:

* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values

对于enum类型,每个constant都要写注释,书上是写在对应的constant的上面。

对于annotation类型,它的每一个成员也都要写注释。

{@inheritDoc}可以复用doc comments,但是不在本书讨论范围内。

IDE有插件可以检查你对这些doc comments规则的的遵守。

《Effective Java》读书笔记 - 7.方法的更多相关文章

  1. Effective Java 读书笔记之六 方法

    一.检查参数的有效性 1.考虑参数有哪些限制,把限制写到文档中,在方法的开头处通过显式地检查来实施这些限制. 二.必要时进行保护性拷贝 1.如果类具有从客户端得到或者返回的可变组件,类就必须考虑保护性 ...

  2. Effective Java 读书笔记(一):使用静态工厂方法代替构造器

    这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...

  3. Effective Java读书笔记——第三章 对于全部对象都通用的方法

    第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...

  4. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

  5. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  6. Effective java 读书笔记(2)

    第四条:通过私有构造器强化不可实例化的能力 有时可能需要编写只包含静态方法和静态域的类,这样的工具类不希望被实例化,因为实例化对它来说没有意义. 然而,在缺少显式构造器的情况下,系统会自动提供一个缺省 ...

  7. [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- 九条

    这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对 ...

  8. Effective Java读书笔记--对所有对象都通用的方法

    1.覆盖equals请遵守通用规定.不需要覆写equals的场景:a.类的每个实例都是唯一的.b.类不需要提供"逻辑相等"的测试功能.c.超类已经覆盖了equals的方法.d.类是 ...

  9. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

  10. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

随机推荐

  1. hadoop-组件

    hadoop1.x 和 hadoop2.x 区别 HDFS 分布式文件存储系统 优点   缺点 MapReduce 分布式计算 详见我的博客 mapreduce YARN 计算资源管理器 主要了解两个 ...

  2. str 小列题

    name = " aleX leNb "#2.有变量 完成如下操作: 移除 name 变量对应的值两边的空格,并输出处理结果 name=name.strip() print(nam ...

  3. C#/.NET/.NET Core定时任务调度的方法或者组件有哪些--Timer,FluentScheduler还是...

    原文:C#/.NET/.NET Core定时任务调度的方法或者组件有哪些--Timer,FluentScheduler还是... 原文由Rector首发于 码友网 之 <C#/.NET/.NET ...

  4. AutoLayout and Sizeclasses讲解

    iOS8和iPhone6发布已经过去蛮久了,广大的果粉终于迎来了大屏iPhone,再也不用纠结为大屏买三星舍苹果了…但是对于iOS开发人员来说,迎来了和Android开发开发一样的问题—>各种屏 ...

  5. iptables-save - 保存 IP Tables

    总览 SYNOPSIS iptables-save [-c] [-t table] 描述 DESCRIPTION iptables-save 用来将 IP Table 转储为可以简单解析的格式,输出到 ...

  6. 机器学习python常用模块

    .Pickle模块 打包,解压训练模型 .pysnooper 调试打印日志

  7. python-文件操作3(读写文件的详细操作)

    f=open('my-heart','r') print(f.encoding)#返回字符编码 print(f.fileno())#返回操作系统的端口编号 print(f.seekable())#是否 ...

  8. kloxo增加了域名,怎么不能访问?如何重启web服务?

    kloxo增加了域名,怎么不能访问?这是因为需要重新启动web服务. 有时候网站打不开,也可以尝试重启web服务. 重启web服务方法: 登录kloxo后台-->左边栏:服务器linux --& ...

  9. WPF 样式Style

    一:样式基础 如果我们的程序有三个这样的按键,一般我们会这样写 <StackPanel> <!--按键的背景色为Azure蔚蓝色背景色为Coral珊瑚色字体为Arial加粗字体大小为 ...

  10. numpy模块、matplotlib模块、pandas模块

    目录 1. numpy模块 2. matplotlib模块 3. pandas模块 1. numpy模块 numpy模块的作用 用来做数据分析,对numpy数组(既有行又有列)--矩阵进行科学计算 实 ...