2009-02-26 13 views
25

L'API per gli stati di interfaccia Java Set:Esiste un'implementazione di base di Java Set che non consente valori nulli?

Per esempio, alcune implementazioni vietare null elementi e alcuni hanno restrizioni sui tipi di loro elementi

Cerco un'implementazione di base Set che non richiede l'ordinazione (come ArrayList fornisce l'interfaccia List) e ciò non consente null. TreeSet, HashSet e LinkedHashSet consentono tutti elementi null. Inoltre, TreeSet ha il requisito che gli elementi implementino Comparable.

Sembra che al momento non esista uno standard di base Set. Qualcuno sa perché? O se esiste uno dove posso trovarlo?

[Modifica]: Non voglio consentire null s, perché più avanti nel codice la mia classe eseguirà un'iterazione su tutti gli elementi nella raccolta e chiamerà un metodo specifico. (In realtà sto usando HashSet<MyRandomObject>). Preferirei fallire più rapidamente che fallire successivamente o incidentalmente incorrere in qualche comportamento bizzarro a causa di un null nel set.

+0

tutti i set praticamente necessario attuare una sorta di modo per trovare rapidamente i duplicati, sia paragonabile o codici hash, altrimenti la scansione per dups sarebbe troppo dolorosa. –

+0

È possibile assegnare a un set di alberi un comparatore che indica che gli elementi non devono essere comparabili (avrebbe dovuto essere un metodo di creazione separato per la modalità comparabile, IMO). –

+0

Puoi anche controllare se qualcosa è nullo mentre lo estrai dal set. – cdmckay

risposta

27

Meglio di estendere una particolare implementazione, è possibile scrivere facilmente un'implementazione proxy di Set che controlla null s. Questo analogo a Collections.checkedSet. Oltre ad essere applicabile a qualsiasi implementazione, puoi anche essere sicuro di aver ignorato tutti i metodi applicabili. Sono stati scoperti molti difetti estendendo le raccolte di calcestruzzi che poi hanno aggiunto metodi aggiuntivi nelle versioni successive.

+1

Qualche idea sul perché Sun non lo abbia già fatto? –

+0

Sun? Gli aggiornamenti al framework di raccolta saranno probabilmente guidati da Google. A proposito, dovrebbe esserci una collezione BOF a JavaOne. –

+0

classico esempio di favorire la composizione sull'ereditarietà .. + 1 – Inquisitive

2

Si potrebbe facilmente scrivere il proprio, per sottoclasse una classe esistente appropriata e sovrascrivendo tutti i metodi importanti in modo che non possibile aggiungere null elementi.

+0

Non dimenticare tuttiTutti i costruttori! –

+0

In realtà, addAll ei costruttori non devono essere sovrascritti poiché sono definiti in AbstractSet e AbstractCollection per chiamare semplicemente il metodo add. Quindi aggiungere solo davvero deve essere ignorato. –

+0

È preferibile utilizzare la composizione anziché la sottoclasse, dal momento che non si ha il controllo della classe che si sottoclasse (cosa succede se Sun aggiunge un nuovo metodo agli insiemi che consentirebbe agli utenti di aggiungere null?) – cdmckay

0

Non sono sicuro di un tipo che sia vero. Ma potresti non ereditare da una raccolta o HashTable di tua scelta e sovrascrivere il metodo Aggiungi, generando un'eccezione se l'elemento è nullo?

-1

Perché non si desidera consentire null?

Si desidera generare un'eccezione se null viene aggiunto al set? Se è così, basta fare qualcosa di simile:

private Set<Object> mySet = new HashSet<Object>() { 
    @Override 
    public boolean add(Object e) { 
     if (e == null) 
      throw new IllegalArgumentException("null"); // or NPE 
     // or, of course, you could just return false 
     return super.add(e); 
    } 
}; 

HashSet 's addAll() chiamate add() ripetutamente, quindi questo è l'unico metodo che avrebbe dovuto ignorare.

+0

Non dovresti contare su addAll() che chiama add() poiché è un dettaglio di implementazione e potrebbe non essere sempre vero. – cdmckay

+0

@cdmckay: se non hai già sviato la risposta di Tom Hawtin, fallo ora! :) –

+0

Oh, vedo che hai aggiunto la tua risposta. –

21

Direi usare la composizione anziché l'ereditarietà ... potrebbe essere più lavoro ma sarà più stabile di fronte alle eventuali modifiche apportate da Sun al Framework delle raccolte.

public class NoNullSet<E> implements Set<E> 
{ 
    /** The set that is wrapped. */ 
    final private Set<E> wrappedSet = new HashSet<E>(); 

    public boolean add(E e) 
    { 
    if (e == null) 
     throw new IllegalArgumentException("You cannot add null to a NoNullSet"); 
    return wrappedSet.add(e); 
    } 

    public boolean addAll(Collection<? extends E> c) 
    { 
    for (E e : c) add(e); 
    } 

    public void clear() 
    { wrappedSet.clear(); } 

    public boolean contains(Object o) 
    { return wrappedSet.contains(o); } 

    ... wrap the rest of them ... 
} 

Edit: Si noti inoltre che questa implementazione non dipende addAll chiamare add (che è un dettaglio di implementazione e non deve essere utilizzato in quanto non può essere garantito per rimanere fedele in tutto Java release).

Modifica: Aggiunto più messaggio di errore descrittivo.

Modifica: Realizzato in modo chiaro() non restituisce nulla.

Modifica: Fatto questo è aggiungere (E e).

Edit: Lanci IllegalArgumentException invece di NullPointerException.

+2

invece di lanciare NullPointerException, prendere in considerazione la possibilità di lanciare RuntimeException ("e == null") (o un IllegalArgumentException) poiché ciò indica allo sviluppatore di leggere lo stacktrace che si tratta di un'eccezione deliberatamente generata e non di un errore logico. –

+1

Oppure aggiungi un messaggio descrittivo a NullPointerException. Inoltre, +1 per la complicazione sull'ereditarietà. –

+0

throw IllegalArgumentException poichè è a cosa serve, un argomento illegale (null) –

2

È possibile utilizzare le raccolte di apache e il relativo PredicatedCollection class e impostare il predicato in modo che non consenta valori nulli. Otterrete delle eccezioni se qualcuno invierà valori nulli.

2

Questo è un modo generico di failry per farlo: si fornisce un'implementazione del filtro che può limitare ciò che viene aggiunto nel modo desiderato. Dai un'occhiata al codice sorgente di java.util.Collections per le idee sul wrapping (penso che la mia implementazione della classe FilteredCollection sia corretta ... ma non è stata testata estensivamente). C'è un programma di esempio alla fine che mostra l'utilizzo.

public interface Filter<T> 
{ 
    boolean accept(T item); 
} 

import java.io.Serializable; 
import java.util.Collection; 
import java.util.Iterator; 


public class FilteredCollections 
{ 
    private FilteredCollections() 
    { 
    } 

    public static <T> Collection<T> filteredCollection(final Collection<T> c, 
                 final Filter<T>  filter) 
    { 
     return (new FilteredCollection<T>(c, filter)); 
    } 

    private static class FilteredCollection<E> 
     implements Collection<E>, 
        Serializable 
    { 
     private final Collection<E> wrapped; 
     private final Filter<E> filter; 

     FilteredCollection(final Collection<E> collection, final Filter<E> f) 
     { 
      if(collection == null) 
      { 
       throw new IllegalArgumentException("collection cannot be null"); 
      } 

      if(f == null) 
      { 
       throw new IllegalArgumentException("f cannot be null"); 
      } 

      wrapped = collection; 
      filter = f; 
     } 

     public int size() 
     { 
      return (wrapped.size()); 
     } 

     public boolean isEmpty() 
     { 
      return (wrapped.isEmpty()); 
     } 

     public boolean contains(final Object o) 
     { 
      return (wrapped.contains(o)); 
     } 

     public Iterator<E> iterator() 
     { 
      return new Iterator<E>() 
      { 
       final Iterator<? extends E> i = wrapped.iterator(); 

       public boolean hasNext() 
       { 
        return (i.hasNext()); 
       } 

       public E next() 
       { 
        return (i.next()); 
       } 

       public void remove() 
       { 
        i.remove(); 
       } 
      }; 
     } 

     public Object[] toArray() 
     { 
      return (wrapped.toArray()); 
     } 

     public <T> T[] toArray(final T[] a) 
     { 
      return (wrapped.toArray(a)); 
     } 

     public boolean add(final E e) 
     { 
      final boolean ret; 

      if(filter.accept(e)) 
      { 
       ret = wrapped.add(e); 
      } 
      else 
      { 
       // you could throw an exception instead if you want - 
       // IllegalArgumentException is what I would suggest 
       ret = false; 
      } 

      return (ret); 
     } 

     public boolean remove(final Object o) 
     { 
      return (wrapped.remove(o)); 
     } 

     public boolean containsAll(final Collection<?> c) 
     { 
      return (wrapped.containsAll(c)); 
     } 

     public boolean addAll(final Collection<? extends E> c) 
     { 
      final E[] a; 
      boolean result; 

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

      result = false; 

      for(final E e : a) 
      { 
       result |= wrapped.add(e); 
      } 

      return result; 
     } 

     public boolean removeAll(final Collection<?> c) 
     { 
      return (wrapped.removeAll(c)); 
     } 

     public boolean retainAll(final Collection<?> c) 
     { 
      return (wrapped.retainAll(c)); 
     } 

     public void clear() 
     { 
      wrapped.clear(); 
     } 

     public String toString() 
     { 
      return (wrapped.toString()); 
     } 
    } 
} 


import java.util.ArrayList; 
import java.util.Collection; 


public class Main 
{ 
    private static class NullFilter<T> 
     implements Filter<T> 
    { 
     public boolean accept(final T item) 
     { 
      return (item != null); 
     } 
    } 

    public static void main(final String[] argv) 
    { 
     final Collection<String> strings; 

     strings = FilteredCollections.filteredCollection(new ArrayList<String>(), 
                 new NullFilter<String>()); 
     strings.add("hello"); 
     strings.add(null); 
     strings.add("world"); 

     if(strings.size() != 2) 
     { 
      System.err.println("ERROR: strings.size() == " + strings.size()); 
     } 

     System.out.println(strings); 
    } 
} 
0

BTW, se avessi chiesto un implementazione Map che non consente i null, il vecchio java.util.Hashtable non lo fa.

-2

Hashtable non consente valori nulli ......

+1

'Hashtable' non è un' Set' –

2

Yes - nella documentazione per com.google.common.collect.ImmutableSet:

A ad alte prestazioni, Set immutabile affidabili, ordine di iterazione specificato dall'utente . Non consente elementi null.

+0

Questa implementazione ha anche lo svantaggio di essere immutabile. Dai documenti: "Per questo motivo, e per evitare confusione generale, si consiglia vivamente di inserire solo oggetti immutabili in questa collezione. Non pensare che sia quello che l'OP stava chiedendo. –

0

In questo particolare domanda/esempio sicuramente se si dispone di un HashSet<MyRandomObject> mySet chiamata mySet.remove(null) prima di iniziare l'iterazione su tutti gli elementi che hai citato?

4

Non esiste un'implementazione di set proprietario di base che ignori o limiti null! C'è EnumSet, ma quello è il sarto per il contenimento dei tipi di enum.

Tuttavia, la creazione di una propria implementazione può essere evitato, se si utilizza Guava o Commons Collections:

1. Guava Soluzione:

Set noNulls = Constraints.constrainedSet(new HashSet(), Constraints.notNull()); 

2.Commons Collezioni:

Set noNulls = new HashSet(); 
CollectionUtils.addIgnoreNull(noNulls, object); 
0

per me, non ho trovato uno, così ho overrode the add function

Collection<String> errors = new HashSet<String>() { 
    @Override 
    public boolean add(String s) { 
     return StringUtil.hasContent(s) && super.add(s);//we don't want add null and we allow HashSet.add(null) 
    } 
}; 
Problemi correlati