关于集合操作边界处理的设计

来源:互联网 发布:1比1高仿耐克淘宝店 编辑:程序博客网 时间:2024/05/01 09:49

原创:dlite@163.com

在设计或实现某种集合的时候,经常会遇到集合边界值的情况。例如,从集合中取一个元素,必须得考虑这个集合是否非空;向一个有限集合添加元素,必须考虑集合满的情况。

而对于包含异常处理的编程语言来说,如果遇到集合操作边界,我们常见的编程约定有两种:

一种选择是将边界条件作为异常来处理。如下面处理集合为空的代码。

代码1:

    Collection c;

    //doSomething();

    Item i;
    try {
        i = c.get(); //take a item from the collection
    }
    catch (GetItemException e) {
        log.err("Cannot get item from a null collection.");
        raise e;
    }

    //use the item;

另一种选择是不将边界操作作为异常来处理,而是用某个特定值来表示(如null)。

代码2:

    Item i = c.get();
    if (i == null) {
        i = DefaultValue;
    }
    //use the Item i

对于究竟是否要将这类边界操作作为错误来处理,依程序的业务逻辑而定。有时,我们希望将其作为异常来处理;有时我们希望只是作为普通的逻辑判断条件,用一个特定的值代表最好。

如果在设计集合的接口时,把所有边界操作作为异常处理,那么要实现普通的逻辑判断就要麻烦一点了。

代码3:

    try {
        i = c.get();
    }
    catch (GetItemException e) {
        i = DefaultValue;
    }

当然,如果集合提供了IsEmpty()操作,上述的代码可以更优雅一些:

代码4:

    i = c.isEmpty() ? DefaultValue : c.get();

当代码4与代码3相比,含义稍有不同。代码3适应于所有无法获取集合成员的情况,而不仅仅是集合为空的情况(例如,多线程并发获取集合成员时,某个线程等待锁超时);代码4只能处理集合为空的情况。所以,代码4把集合接口的抽象程度降低了。

当然,接口的抽象程度并非越高越好,而是根据集合的应用环境而定的。这里只是指出了不同设计的差异。

JDK的Collection接口设计时,采用第一种约定。对于add/remove方法,采用抛出异常的方式处理边界操作。

JDK的Queue接口设计时,同时采用两种约定,提供两类接口。一类传统的继承了Collection抛出异常的方式,即add/remove方法;另一类使用返回特定值(false/null)的方式处理边界条件,即offer/poll方法。

然而,事情还没有结束。并发编程时,常常用到类似于阻塞队列的数据结构。当队列为空时,获取元素的的操作结果不是null,也不是抛出一个异常,而是调用线程应该被阻塞。因此,JDK 5以后,在java.util.concurrent包里,又引入了put/take方法,来专门处理这里的第三种情况。

小结,对于集合的增加/删除操作,考虑到集合边界条件(空/满),调用约定通常有三种。以JDK为例:
    add/remove: 达到边界条件,抛出异常;
    offer/poll:达到边界条件,返回特定常量(false/null);
    put/take: 达到边界条件,阻塞调用线程。

原创粉丝点击