代码之家  ›  专栏  ›  技术社区  ›  Aaron K

在添加到集合之前检查集合是否为null的不同方法的优缺点是什么?

  •  1
  • Aaron K  · 技术社区  · 16 年前

    Is there a basic Java Set implementation that does not permit nulls? “(感谢所有帮助回答问题的人)

    因为Sun没有提供像OOTB这样简单的东西,所以实现这一点的方法似乎是使用包装器/代理。这似乎是直截了当的。我想知道的是,以下两种添加集合的方法的优缺点是什么,或者还有其他更好的方法吗?

    方法#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);
    }
    

    5 回复  |  直到 8 年前
        1
  •  4
  •   TofuBeer    16 年前

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

    编辑:

    如果指定的元素为null,并且此集合不允许null元素(可选),则从contains的JavaDoc中抛出NullPointerException。

        2
  •  1
  •   Paul Tomblin    16 年前

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

    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;
    }
    
        3
  •  1
  •   Tom Hawtin - tackline    16 年前

    在数组中复制参数集合更安全。集合参数可能会同时更改(可能是一个并发集合)(也可能是恶意编写的)。

    此外,捕获运行时异常也不是一种很好的方法。

    您可能想使用 Object[] 而不是 E[] Arrays.asList(a) 或类似的。

        4
  •  0
  •   Ben S    16 年前

    如果要添加的集合是非NullCollection父类的子类,则还可以添加a短路以不测试Null。

        5
  •  0
  •   Aaron K    16 年前

    看来这两种方法都不好。我发布这个答案只是为了将信息整合到一个帖子中。

    TofuBeer指出了方法1的逻辑中被忽略的缺陷,其中存在其他可能抛出但无法捕获的异常。因此,一般来说,试图捕捉非异常条件的异常总是不好的。

    保罗指出,我认为安全的演员阵容实际上并不安全。我希望在输出强制转换上强制执行集合泛型,但是它将返回对象[]。正如他指出的,我可以使用一个临时设置来存储数据,同时搜索空值。

    因此,我想理想的方法是:

    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 );
    }