【读书笔记】《Effective Java》(6)--方法

这是《Effective Java》的第7章内容总结,讲到了一些方法通用设计的东西。嗯,感觉后面的内容着眼在规范上面。

方法

38. 检查参数的有效性

  • 本条建议:
    1. 除非参数的有效性的检测工作要付出昂贵的性能代价,或者方法中的计算已经隐式地执行了必要的有效性检测,否则应当在方法开头就对参数进行检查,对于构造器也是如此。
    2. 同时,公有方法对参数检查的各种限制和检查到问题时抛出的异常都应当写到JavaDoc文档的@throw标签中(异常一般为IllegalArgumentException、IndexOutofBoundsException、NullPointerException)。
    3. 对于未被导出的方法,作为包的创建者,我们应当控制只将有效的参数传递到方法中。因此对于非公有方法,可以使用断言检查参数。

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

  • Java是安全的语言,对于缓存区溢出、数组越界、非法指针以及其他内存破坏错误都自动免疫,但是为了正确地和其他类隔离开,避免错误,还是需要编写一些更健壮的类的。

  • 为了确保对象内的变量不会在对象外被其他的手段改变,建议在传参、返回结果的时候进行拷贝,而不是直接赋值,因为直接复制只是传递的引用,外部还可以通过其他指向这个对象地址的其他引用改变变量,即便对于final修饰的引用类型也是这样。

  • 本条建议:

    1. 对于构造器的每个可变参数进行保护性拷贝是必要的。
    2. 保护性拷贝在检查参数的有效性之前进行,并且有效性检查是针对拷贝后的对象的。这是为了防止多线程下,先检查完有效性,而后被其他线程改变了参数,之后再拷贝的情况。
    3. 对于参数类型可以被其他不可信任方子类化的参数,不要使用clone方法拷贝,而对于内部域,则可以使用clone方法拷贝一份变量传递出去(用于返回),因为在类的内部,我们知道内部域是遵守约定的,不是不可信任的,clone方法实现失败的类。
    4. 对于不希望改变的域,不要设计getter方法,或者返回一个新实例而不是内部域的引用。
    5. 对于某些保护性拷贝较损耗性能,或者客户代码是值得信任的情况,可以不适用保护性拷贝,但是这应当在API文档中注明,并写清调用者不应修改哪些参数或者返回值。

40. 谨慎设计方法签名

  • 本条件建议:
    1. 谨慎地选择方法签名:易于理解并且和同一包的其他名称风格一致;与大众认可的名称相一致的名称
    2. 不要过于追求提供便利的方法:方法太多会导致类难以学习、使用、文档化、测试和维护。应当首先提供一个功能齐全的方法,如果有一项操作经常被用到,再考虑为它提供快捷方式。
    3. 避免过长的参数列表:如果参数大于4个的话,方法就变得不易使用了。相同类型的长参数序列格外有害,因为分辨到底传入的参数起什么作用是个很耗精神的工作。有三种方法缩短参数列表:
      1. 将一个方法分解成多个方法,为了避免分解的方法过多,可以提升它们的正交性减少方法数目。
      2. 创建辅助类:通过创建参数聚类而成的类,然后传递类的实例简化方法参数列表。
      3. 采用Builder模式:参见第2条。
      4. 对于参数类型,要优先使用接口而不是类:使用类,尤其是具体类限制了方法传入的参数的多样性,不利于方法日后的更新。
      5. 对于boolean类型,要优先使用两个元素的枚举类型:使用枚举类型使得代码更易于阅读和编写,同时便于日后增添新的选项。


41. 慎用重载

  • 重写(override)和重载(overload):重写是子类覆盖父类已有的方法,方法签名完全相同,但是实现不同,通过运行时多态,每当运行是调用重写的方法时,总会调用子类的方法;而重载是一个类自己写下多个具有相同方法名,但是参数列表不同的方法(仅仅返回值不同不认为是重载),通过编译时多态,当运行是调用重载方法时,会调用当前调用这个方法的编译时类型。
    例如:
public class ColectionClassifier{
    public static String classify(Set<?> s){return "Set";}
    public static String classify(List<?> lst){return "List";}
    public static String classify(Collection<?> c){return "Unknow Collection"}
    public static void main(String[] args){
        Colllection<?>[] collections={
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<String,String>().values()
        };
        for(Collection<?> c:collections)
            System.out.println(classify(c));
    }
//结果输出三次Unknow Collection
}
  • 何时使用重载:保守的方法是永远不要导出两个具有相同参数数目的重载方法,如果是参数是可变参数,不要重载它。

  • 如何设计重载的方法:

    1. 使用方法名+Type的方式更改方法名:例如,ObjectOutputStream的Write族方法,有writeBoolean(boolean)、writeInt(int),writeLong(long)等
    2. 对于构造器,可以使用静态工厂代替,因为构造器本身不能更改名字,但是静态工厂可以改名。
    3. 如果一定要重载,需要在每一个重载方法有“根本性的不同”,“根本性的不同”是指参数类表中有至少一个和其他重载方法不同的类型或者参数列表个数不同,注意这个类型在其他重载方法的参数列表中,也不可以充当父类或者子类或者接口。
    4. 另外还有一个曲折一些的方法:如果两个重载方法传入同一个参数,结果是一样的,那么这样也是可以的。客户代码不需要知道到底调用了哪个重载方法,但是它真的起作用了。
  • Java做的不好的地方:

    1. 在引入泛型之后,List的remove方法出现了重载的错误,有remove(int index)remove(E element)两个方法,因为Java对int有自动装箱,传入int后,我们本期望使用remove(E element)方法删除对应索引上的元素,但是却调用remove(int index)删除了传入的元素。解决方法是将传入的int显式转换成Integer或者使用Integer.valeOf(int)转换.

42. 慎用可变参数

  • 可变参数实现:传参时先创建一个数组,数组大小为参数的个数,然后将参数放入数组,数组传入方法。

  • 使用可变参数应对传入1个或多个参数的方法:可变参数允许传入的参数为0以及更多,但是有些方法希望传入的参数最少为1个,这依旧可以通过可变参数解决。像这样:public void method(int firstArg,int...remainingArgs)即可。

  • 可以将以数组当作final参数的现有方法改造成以可变参数代替,但是不必改造具有final数组参数的每个方法,只当确定是在数量不定的值上执行调用时才使用可变参数。

  • 可变参数会降低一部分性能,所以在注重性能的场景下,建议不使用可变参数,而是通过方法重载,重载5个方法,分别0个参数、1个参数…3个参数还有可变参数(因为只有一小部分方法调用需要3个以上的传参),这种方法虽然复杂,但是可缓解性能。

  • 注意点:在以前,Array(引用类型不是基本类型的数组)默认的toString方法只能输出类名+地址,所以可以使用Arrays.asList()转换成List再输出,这会调用List的toString方法,输出每一个元素。这种方法在基本类型数组上使用会报错,但是在Java1.5以后,由于可变参数的使用,以下代码将可以成功,但是输出结果不如人意,因为编译器将整个数组认为是一个元素存入了List(List<int[]>),这种情况下输出依旧是地址而不是元素序列。

//代码如下
public static void main(String[] args){
    int[] digits={3,1,4,1,5,9,2,6,5,4};
    System.out.println(Arrays.asList(digits));
}

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

  • 如果方法返回null,会导致方法的调用中增加检查null的代码,这种检查可能会被遗忘并且不好发现。对于使用null的原因可能是源于对性能的考虑,但这种考虑并不明智。

  • 对于要返回要表示“空”的情况,使用长度为0的数组是合适的,长度为0的数组是不可变的,可以被自由地共享;对于集合来说,当要返回空集时,同样有Collections.emptySetCollections.emptyListCollections.emptyMap可供使用。


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

  • 使用JavaDoc编写API文档:

    1. 在每个被导出的类、接口、构造器、方法和域声明之前增加一个文档注释,如果类是可序列化的,也必须为他的序列化形式编写文档。为了可维护性,应当为没有导出的类、接口、构造器、方法和域编写文档注释。
    2. 方法的文档注释应当简洁地描述出它和客户端之间的约定,除了专门为继承而设计的类中方法外(第17条),注释应当描述这个方法做了什么,而不是怎么做的。
    3. 需要在方法的注释中列举所有的前置条件和后置条件,一般情况下前置条件由@throw标签针对未检测的异常所隐含描述,也可以在@param标记中指定前置条件。
    4. 除了条件,注释还应当注明方法的副作用,比如会启动后台线程等。
  • 编写方法:

    1. 对于每个参数,使用@param标记参数,用@return标记返回值(除非返回值为void),一般而言,这两个标记后面的文字应当是一个名词短语,对参数或者返回值进行描述。
    2. 对于可能出现的异常使用@throw标记,这个标记后应当有短语if(如果)描述异常在什么情况下产生。
    3. JavaDoc最后会生成HTML文件,所以在注释中允许使用HTML标签,它们会被包含在最终文档中,在注释中如果包含HTML中需要转义的字符,可以通过{@literal...}进行转义,此外{@code...}标记允许生成代码字体的最终文档、禁止HTML标记和嵌套的JavaDoc标记在代码片段中进行处理。
    4. 每个文档注释的第一句话应当是该注释的简要描述,应当简短,清晰,对于方法可以一个完整的动词短语,描述方法所执行的动作,对于类、接口和域可以是一个名词短语,描述该类或者接口的实例,或者域代表的事物。
    5. JavaDoc可以继承文档注释,也就是说,当前元素没有注释,JavaDoc会搜索最为合适的文档注释,接口的文档注释优先于超类的文档注释。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值