6.方法_EJ

第38条: 检查参数的有效性

对于这一条,最常见的莫过于检查参数是否为null。

有时出现调用方未检查传入的参数是否为空,同时被调用方也没有检查参数是否为空,结果这就导致两边都没检查以至于出现null的值程序出错,通常情况下会规定调用方或者被调用方来检查参数的合法性,或者干脆规定都必须检查。null值的检查相当有必要,很多情况下没有检查值是否为空,结果导致抛出NullPointerException异常。

除了null,常见的还有对于方法参数的限制。一个典型的例子:

public BigInteger mod(BigInteger m) {
        if (m.signum <= 0)
            throw new ArithmeticException("BigInteger: modulus not positive");

        BigInteger result = this.remainder(m);
        return (result.signum >= 0 ? result : result.add(m));
}

总之,每当编写方法或构造器时,应该考虑它的参数有哪些限制,应该把这些限制写到文档中去,并且在这个方法的开头处,通过显示的检查来实施这些限制。

 

第39条: 必要时进行保护性拷贝

java是一门安全的语言,但即使在安全的语言中,如果不采取点措施,还是无法与其它的类隔离开来。假设类的客户端会尽其所能来破坏这个类的约束条件,因此必须保护性地设计程序。考虑下面的类,表示一段不可变的时间周期。

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;
    }
    @Override
    public String toString() {
        return start + " -- " + end;
    }
    public static void main(String[] args){
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        System.out.println(p);
        end.setYear(78);
        System.out.println(p);
    }
}

从输出语句来看,这个“不可变的时间周期”变了。虽然私有域start和end的引用是不可变的,但其date类本身是可变的,因此很容易违反这个约束条件。因此对构造器和方法的保护性拷贝是很有必要的。

public final class Period2 {
    private final Date start;
    private final Date end;
    public Period2(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());
    }
    @Override
    public String toString() {
        return start + " -- " + end;
    }
    public static void main(String[] args){
        Date start = new Date();
        Date end = new Date();
        Period2 p = new Period2(start, end);
        System.out.println(p);
        end.setYear(78);
        p.end().setYear(78);
        System.out.println(p);
    }
}

从代码的输出可以看出,无论我们怎么改,时间周期p都是不可变的。

 

第40条: 谨慎设计方法签名

使api更易于学习和使用的设计技巧:

1.谨慎地选择方法名称。

2.不要过于追求提供便利的方法。

3.避免过长的参数列表。三种方法可以缩短过长的参数列表:

  a.把方法分解成多个方法

  b.创建辅助类

  c.从对象的构建和方法调用都采用Builder模式。

对于参数类型,要优先使用接口而不是类。

对于boolean参数,要优先使用两个元素的枚举类型。

 

第41条: 慎用重载

考虑下面一个程序,它试图根据一个集合是Set还是list,还是其它集合类型,来对它进行分类:

public class CollectionClassifier {
    /*public static String classify(Set<?> s){
        return "Set";
    }
    public static String classify(List<?> s){
        return "List";
    }
    public static String classify(Collection<?> s){
        return "Unknown Collection";
    }   */
    //避免滥用重载修正方案
    public static String classify(Collection<?> s){
        return s instanceof Set ? "Set" : s instanceof List ? "List" : "Unknown Collection";
    } 
    public static void main(String[] args){
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<BigInteger>(),
                new HashMap<String, String>().values()
        };
        for(Collection<?> c : collections){
            System.out.println(classify(c));
        }
    }
}

如果打开注释中的三个classify方法测试,我们会发现这个程序的行为有悖常理,它打印三次“Unknown Collection”。这是因为对于重载方法的选择是静态的,而对于被覆盖的方法的选择是动态的。我们可以用上面的修正方案来实现这个功能。下面是一个覆盖的例子:

public class Overriding {

    public static void main(String[] args) {
        Wine[] wines = {
                new Wine(),
                new SparklingWine(),
                new Champagne()
        };
        for(Wine wine : wines){
            System.out.println(wine.name());
        }
        
    }

}
class Wine {
    String name(){
        return "wine";
    }
}
class SparklingWine extends Wine {
    @Override
    String name() {
        return "sparkling wine";
    }
}
class Champagne extends SparklingWine {
    @Override
    String name() {
        return "champagne";
    }
}

可以看出,代码实现了正确的行为。简而言之,“能够重载方法”并不意味着“应该重载方法”,一般情况下,对于多个具有相同参数数目的方法来说,应该尽量避免重载方法。

 

第42条: 慎用可变参数

具有可变参数的方法可以传入0个或者多个参数,这种方法我相信自己写的可能在少数,用得最多的可能要属反射中的getDeclaredMethod方法:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

为什么要慎用,其中有一个原因就是很有可能在没有传入参数的时候程序没有做任何保护而导致程序错误。另外有一个原因就是它会带来一定的性能问题,EnumSet类在传入少量参数的时候是直接调用具体的方法,只有在传入大量参数时才会调用可变参数的方法,这也是它在性能方面有优势的原因,因为可变参数的每次调用都会导致进行一次数组分配和初始化。

总之,“在定义参数数目不定的方法时,可变参数是一种很方便的方式,但是它们不应该被过度滥用。如果使用不当,会产生混乱的结果”。

 

第43条: 返回零长度的数组或集合,而不是null

使用和避免null:null是模棱两可的,会引起令人困惑的错误,有些时候它让人很不舒服。

例如对于一个Map,调用其get(key)方法,此时若返回null,可能表示这个key所对应的值本身就是null,或者表示这个Map中没有这个key值。这是两种截然不同的语义。

书中仅是说明对于零长度的数组或者集合不应该返回null,实际上对于所有的情况,都不要轻易返回null,特别是在语义不清的情况,更别说返回null时有的客户端程序并没有处理null的这种情况。如果一定要用到null,更好的办法是单独维护它。

 

第44条: 为所有导出的api元素编写文档注释

现在的IDE很方便,只要轻轻敲几个快捷键就能方便的生成文档注释模板,所以不要觉得麻烦,基本的参数、返回值、用途的注释一定要写。

posted @ 2018-08-15 15:12  Ericyshi  阅读(210)  评论(0编辑  收藏  举报