2009-02-27 4 views
1

"Is there a basic Java Set implementation that does not permit nulls?"에 대한 후속 질문입니다. (답변에 도움을 준 모든 분들께 감사드립니다)집합에 추가하기 전에 null에 대한 Collection을 검사하는 여러 가지 방법에 대한 장단점은 무엇입니까?

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

감사합니다!

답변

4

두 번째 방법이 더 좋다. 예외를 절대로 숨기지 마십시오. c.contains (null)은 컬렉션에 null이있는 경우에만 NullPointerException을 던진다는 가정에 의존합니다. 그러나 equals 메서드에 문제가있어서 NullPointeException이 throw되면 코드에 버그가 생기며 숨길 수 있습니다.

편집 : 대한 포함 된의 JavaDoc에서

는, NullPointerException을한다 - 지정된 요소가 null이며,이 컬렉션 (선택 사항) null 요소를 허가하지 않는 경우.

선택적인 방법이므로 NullPointerExcepion 대신 Throw되는 UnsupportedOperationException (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

toArray의 의도 장점은, 원래의 콜렉션이 수표 나에 대한 후속 호출 중 하나 중에 수정되는 경우 있음 addAll은 문제가되지 않습니다. –

1

배열에서 인수 컬렉션 복사본을 만드는 것이 더 안전합니다. 콜렉션 인수는 동시에 변경 될 수 있습니다 (아마도 동시 수집 일 수도 있습니다) (또는 악의적으로 작성 될 수도 있습니다).

또한 런타임 예외를 포착하는 것은 좋은 방법이 아닙니다.

E[] 대신 Object[]을 사용하고 안전하지 않은 캐스트를 나중에 옮길 수도 있습니다. Arrays.asList(a) 또는 이와 비슷한 것이 필요합니다.

+0

CopyOnWriteArrayList는 복사하지 않고이를 해결합니다. – TofuBeer

+0

"배열에서 인수 컬렉션을 복사하는 것이 더 안전합니다." 아마 내가 잘못 읽은거야? (그 또는 그것은 java.util.concurrent 패키지에있다) – TofuBeer

+0

[게시물을 편집했다.] c.toArray() 호출은 콜렉션 보장만큼 안전해야한다 (예를 들어, 동기화 된 콜렉션은 동기화를해야한다). 질문자의 첫 번째 예에서 동기화 된 컬렉션조차도 일관성없는 결과를 초래할 수 있습니다. –

0

또한 추가되는 컬렉션이 귀하의 NonNullCollection 상위 클래스를 하위 클래스로 분류한다면 단락 회로를 추가하여 null을 테스트하지 않을 수도 있습니다.

0

두 가지 방법이 모두 좋지 않은 것 같습니다. 나는 정보를 하나의 게시물로 통합하기 위해이 답변 만 게시하고 있습니다.

TofuBeer는 던져 질 수있는 다른 예외가있는 잡을 수없는 방법 1에 대한 간과 된 결함을 지적했습니다. 그래서 일반적으로 예외가없는 조건에 대한 예외를 잡으려고 항상 노력하는 것은 나쁘다.

폴은 안전한 캐스팅이라고 생각했던 것이 실제로는 아니라고 지적했습니다.나는 콜렉션 generic이 출력 캐스트에 적용될 것을 기대했지만 Object []를 리턴 할 것이다. 그가 지적했듯이 null을 검색하는 동안 데이터를 저장하기 위해 임시 세트를 사용할 수 있습니다.

또한 Tom이 확인한 것처럼 콜렉션 인수가 동시에 변경되어 새로운 객체에 대한 방어 복사본이 좋은 아이디어 일 수 있음).

그래서 내가 원하는 방법은 추측 : 배열의 로컬 복사본이 생성되면

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

추가 된 모음에 null이 포함되어 있으면이 집합을 선언되지 않은 상태로 둡니다. 일부 요소가 추가되고 다른 요소는 추가되지 않고 컬렉션으로 중단됩니다. 폴 톰 블린 (Paul Tomblin)의 해결책은 그 점에서 더 깨끗했습니다. – akuhn

+0

컬렉션이 tmpSet에 저장되므로 원래 컬렉션이 수정 된 경우 동시 수정 예외가 발생하지 않습니다. 그런 다음이 tmpSet을 검사하여 예외가 발생하면 null을 검사합니다. 그렇지 않으면 null이없는 세트가 추가됩니다. –

관련 문제