Effective Java: 方法的设计

主要内容:如何处理参数和返回值,如何设计方法,使其更合理。

38 检查参数的有效性

方法执行开头,要对有必要的参数进行有效性检验。

Javadoc的@throws 在文档中说明违反参数值限制会出现的异常

对于没有导出的方法,非公有方法通常使用断言来检查参数:

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

断言是在被声称被断言的条件将会为真。不同于一般的有效性检查,断言如果失败,将会抛出AssertionError 。如果断言没有起到作用,本质上不会有成本开销,除非通过-ea(或者 -enableassertions)标记传递给Java解释器,来启用它们。

断言:assert

在防御式编程中经常会用断言(Assertion)对参数和环境做出判断,避免程序因不当的输入或错误的环境而产生逻辑异常,断言在很多语言中都存在,C、C++、Python都有不同的断言表示形式。在Java中的断言使用的是assert关键字,其基本的用法如下:

assert <布尔表达式> 
assert <布尔表达式> : <错误信息> 

在布尔表达式为假时,抛出AssertionError错误,并附带了错误信息。assert的语法较简单,有以下两个特性:

(1)assert默认是不启用的

我们知道断言是为调试程序服务的,目的是为了能够快速、方便地检查到程序异常,但Java在默认条件下是不启用的,要启用就需要在编译、运行时加上相关的关键字,这就不多说,有需要的话可以参考一下Java规范。

(2)assert抛出的异常AssertionError是继承自Error的

断言失败后,JVM会抛出一个AssertionError错误,它继承自Error,注意,这是一个错误,是不可恢复的,也就表示这是一个严重问题,开发者必须予以关注并解决之。

39 必要时进行保护性拷贝

为什么进行保护性拷贝?保护类的状态不被客户端不安全的进行改变,从而导致错误。

以下面的例子进行讲述。

Period中,start的时间要保证一定小于end

public final class Period{
  private final Date start;
  private final Date end;
  public Period(Date start, Date end){
    if(start.compareTo(end) > 0)
       throw exception; //抛出错误
     this.start = start;
     this.end = end;
  }

  public Date start(){
    return start;
  }
  public Date end(){
    return end;
  }
}

考虑下列调用:

Date start = new Date();
Date end = new Date();
Period p = ew Period(start, end);
end.setYear(78);//Period的内部状态被改变了!那么Peroid的start<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 exception; //抛出错误
  }

注意,条件检验放在后面的原因是,在多线程的环境中,防止在经过检验之后,其它线程对参数进行修改。

但是还有可能出现这种情况:

Date start = new Date();
Date end = new Date();
Period p = ew Period(start, end);
p.end().setYear(78);//end还是被改变了!那么Peroid的start<end的安全性不能被保证

修改方法:

public Date end(){
  return new Date(end.getTime());
}

参数的保护性拷贝并不仅仅针对不可变类。每当编写方法后者构造器的时,如果它要允许客提供的对象进入到内容的数据结构中,则有必要考虑,客户提供的对象是否有可能是可变的。

简而言之,如果类具有从客户单得到或者放回到客户端的【可变】组件,类就必须保护性地拷贝这些组件。 如果拷贝的成本收到限制,并且类信任它的客户端不会不恰当的修改这些组件,就可以在文档中指明客户端的职责是不得修改受到影响的组件,一次来代替保护性拷贝。

40 谨慎的设计方法签名

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

方法名称始终遵循标准的命名习惯。易于理解、风格一致。

2.不要干锅鱼追求提供便利的方法

方法太多导致难以学习。只有当一项操作被经常用到的时候,才考虑为它提供快捷方式。如果不确定,还是不提供的好。

3.避免过长的参数列表

目标参数是【4】个以下。

缩短目标参数的方法:、

  • 把方法分解成多个方法,可以组合使用。如果不小心,会导致方法过多。但是通过提升它们的正交性,可以减少方法的数目,也就是可以组合搭配在一起。
  • 创建辅助类。
  • 从对象构建到方法的调用都是用Builder模式。
4.对于boolean参数,优先使用两个元素的枚举类型

代码容易阅读,且以后方便拓展。

41 慎用重载

下列程序为例:

public class CollectionClassifier{
   public static String classify(Set <? > s){  
                   return "Set";  
         }  
         public static String classify(List <? > lst){  
                   return "List";  
         }  
         public static Stringclassify(Collection < ? > c){  
                   return "UnknownCollection";  
         }  
         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));  
         }  
}  

对于上例,它将打印“Unknown Collection”三次。对于for循环中的全部三次迭代,虽然每次迭代的运行时类型都是不同的,但参数的编译时类型都是相同的:Collection

42 慎用可变参数

可变参数的使用:

public void function(int firstArg, Object...args){

}

一般要对可变参数的数量进行检查;

在重视性能的情况下,使用可变参数机制要比较小心.可变参数的方法每次调用都会导致进行一次数组分配和初始化.如果确定无法承受这一成本,但是又需要可变参数的灵活性,有以下解决方法。

对于某个方法96%的调用会有3个或者更少的参数,就声明该方法的5个重载,重载方法代用0->3个参数。当大于3个参数的时候,对剩余参数采用可变参数。

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

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

例子:

public List<Cheese> getCheeseList(){
  if(cChessesInStock.isEmpty())
    return Collections.emptyList();
  else
    return new ArrayList<Chess>(chessesInStock);
}

不把没有的情况做特例返回为null,客户端不用因为没有检查为null的情况而导致出错。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值