2009-02-27 46 views
1

这是针对“Is there a basic Java Set implementation that does not permit nulls?”的后续问题。 (感谢所有帮助答案的人)在添加到集合之前,检查集合为null的不同方法有什么优缺点?

看来,做这件事的方法,因为Sun没有提供像这样简单的东西,所以是使用包装器/代理。这似乎很直接。我想知道的是以下两种添加集合的方式有什么优点/缺点,还是有另一种更好的方法?

方法#1

public boolean addAll(Collection<? extends E> c) { 
    if (null == c) { 
     throw new NullPointerException("c cannot be null"); 
    } 

    /* 
    * This seems like a terrible abuse of exceptions when 
    * all I want to do is check the set for a null. 
    * 
    * However, simply running through each element of the 
    * Collection may provide much worse performance for large 
    * collections. And according to the Collection API, the 
    * contains method may throw NullPointerExceptions if the 
    * collection implementation does not allow null elements. 
    */ 
    boolean collectionContainsNull = false; 

    try { 
     collectionContainsNull = c.contains(null); 
    } catch (NullPointerException safeToIgnore) { 
     /* Safe to ignore since we do not want nulls */ 
    } 

    if (collectionContainsNull) { 
     throw new NullPointerException("c cannot contain null elements"); 
    } 

    this.wrapperSet.addAll(c); 
} 

方法2

public boolean addAll(Collection<? extends E> c) { 
    if (null == c) { 
     throw new NullPointerException("c cannot be null"); 
    } 

    E[] a = (E[])c.toArray(); 

    /* 
    * We have to iterate through the entire collection to check for 
    * a null. This won't take advantage of any optimizations that 
    * c.contains may be using. 
    * 
    * We don't call add(e) here because if we hit a null midway through 
    * we would violate the condition that the set remains unchanged 
    * on error. 
    */ 
    for (E e : a) { 
     if (null == e) { 
       throw new NullPointerException("c cannot contain nulls"); 
     } 
    } 

    this.wrapperSet.addAll(a); 
} 

提前感谢!

回答

4

第二种方法比较好。永远不要隐藏异常 - 你依赖于假设c.contains(null)仅在集合中存在null的情况下抛出NullPointerException。但是,如果由于equals方法的问题抛出NullPointeException,那么在代码中会出现一个错误 - 并且您将隐藏它。

编辑:

从包含的JavaDoc,则抛出NullPointerException - 如果指定的元素为null,并且此collection不允许null元素(可选)。

鉴于它是一种可选方法,您可能会抛出UnsupportedOperationException而不是NullPointerExcepion(除了在equals中隐藏错误)。

1

首先转换为数组,然后遍历数组,而不是迭代集合,这有什么意义?我会做第二个没有多余的转换。

或者,也许做的添加到临时组:

public boolean addAll(Collection<? extends E> c) { 
    if (null == c) { 
     throw new NullPointerException("c cannot be null"); 
    } 

    Set<E> tempSet = new HashSet<E>(); 

    /* 
    * We have to iterate through the entire collection to check for 
    * a null. This won't take advantage of any optimizations that 
    * c.contains may be using. 
    * 

    */ 
    for (E e : c) { 
     if (null == e) { 
       throw new NullPointerException("c cannot contain nulls"); 
     } 
     tempSet.add(e); 
    } 

    this.wrapperSet = tempSet; 
} 
+0

的指定者的预期好处是,一旦创建阵列的本地副本,如果原始集合要么支票或之后在通话过程修改addAll不会有问题。 – 2009-02-27 17:00:01

1

在数组中创建参数集合的副本更安全。收集参数可能会同时发生更改(可能是并发收集)(或可能会被恶意写入)。

捕捉运行时异常并不是一种很好的方式。

您可能想要使用Object[]而不是E[]并将不安全的转换移到稍后。你还需要一个Arrays.asList(a)或类似的。

+0

CopyOnWriteArrayList可以解决这个问题,而不需要复制。 – TofuBeer 2009-02-27 17:14:49

+0

“在数组中创建参数集合的副本更安全。”也许我误解了它? (或者,或者它在java.util.concurrent包上) – TofuBeer 2009-02-27 17:50:33

0

如果要添加的集合子类为NonNullCollection父类,那么也可以添加一个短路以便不测试空值。

0

所以看来这两种方法都不好。我真的只是发布这个答案将信息整合到一个单一的职位。

TofuBeer指出了方法1的逻辑中被忽视的缺陷,其中还有其他可能引发的异常,这些异常不会被捕获。所以一般来说,试图捕捉异常情况的例外总是很糟糕的。

保罗指出,我认为是安全的演员其实并非如此。我期望集合通用在输出强制执行,但它会返回Object []。正如他指出的,我可以使用临时集来存储数据,而我搜索空值。

另外,由于汤姆证实收集参数可能会同时发生变化,所以防御性拷贝成为一个新对象是个好主意)。

,所以我想需要的方法是:

public boolean addAll(Collection<? extends E> c) { 
    if (null == c) { 
     throw new NullPointerException("c cannot be null"); 
    } 

    // Create a defensive copy to prevent concurrent modifications of c 
    Set<E> tmpSet = new HashSet<E>(c); 

    /* 
     * We have to iterate through the entire collection to check for 
     * a null. This won't take advantage of any optimizations that 
     * c.contains may be using. 
     */ 
    for (E e : tmpSet) { 
     if (null == e) { 
       throw new NullPointerException("c cannot contain nulls"); 
     } 
    } 

    return this.wrapperSet.addAll(tmpSet); 
}