Friday, April 8, 2011

Is there a basic Java Set implementation that does not permit nulls?

The API for the Java Set interface states:

For example, some implementations prohibit null elements, and some have restrictions on the types of their elements

I am looking for a basic Set implementation that does not require ordering (as ArrayList provides for the List interface) and that does not permit null. TreeSet, HashSet, and LinkedHashSet all allow null elements. Additionally, TreeSet has the requirement that elements implement Comparable.

It seems that no such basic Set exists currently. Does anyone know why? Or if one does exist where I can find it?

[Edit]: I do not want to allow nulls, because later in the code my class will iterate over all elements in the collection and call a specific method. (I'm actually using HashSet<MyRandomObject>). I would rather fail fast than fail later or accidentally incur some bizarre behavior due to a null being in the set.

From stackoverflow
  • You could easily write your own, by subclassing an appropriate existing class, and overriding all relevant methods so that you can't add null elements.

    Paul Tomblin : Don't you hate it when somebody puts in the answer while you're typing the same answer?
    Paul Tomblin : Don't forget allAll and the constructors!
    Eric Petroelje : Actually, addAll and the constructors don't need to be overridden since they are defined in AbstractSet and AbstractCollection to simply call the add method. So only add really needs to be overridden.
    cdmckay : You might be better off using composition instead of subclassing, since you're not in control of the class you are subclassing (what if Sun adds a new method to sets that would allow users to add null?)
    Steve Kuo : You are better off wrapping a Set implementation.
  • I am not sure of a type which this is true. But could you not inherit from a collection or HashTable of your choice and override the Add method, throwing an exception if the element is null?

  • Why do you not want to allow null?

    Do you want to throw an exception if null is added to your set? If so, just do something like this:

    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() calls add() repeatedly, so this is the only method you'd have to override.

    cdmckay : You should not count on addAll() calling add() as that is an implementation detail and may not always be true.
    Michael Myers : @cdmckay: If you haven't already upvoted Tom Hawtin's answer, do it now! :)
    Michael Myers : Oh, I see you've added your own answer instead.
    matt b : @cdmckay - if you extend HashSet, that is always true...
    Darron : @matt b: is this true of the HashSet in IBM's JVM? JRocket? GNU Classpath? The new optimized one in Sun's Java 8? Unless this is in the JLS you can't count on it.
  • Better than extending a particular implementation, you can easily write a proxy implementation of Set that checks for nulls. This analogous to Collections.checkedSet. Other than being applicable to any implementation, you can also be sure that you have overridden all applicable methods. Many flaws have been found by extending concrete collections which then have additional methods added in later versions.

    Michael Myers : A little more work, but that's a good idea. +1
    Michael Myers : Actually, it might not even be more work.
    Aaron K : Any idea why Sun didn't do this already?
    duffymo : +1 - terrific thought.
    Steve Kuo : A much better idea than inheritance
    Tom Hawtin - tackline : Sun? Updates to the collection framework are likely to be driven by Google. Btw, there should hopefully be a collections BOF a JavaOne.
  • I would say use composition instead of inheritance... it might be more work but it'll be more stable in the face of any changes that Sun might make to the Collections Framework.

    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: Also note that this implementation does not depend on addAll calling add (which is an implementation detail and should not be used because it cannot be guaranteed to remain true in all Java releases).

    Edit: Added more descriptive error message.

    Edit: Made it so clear() doesn't return anything.

    Edit: Made it so it's add(E e).

    Edit: Throws IllegalArgumentException instead of NullPointerException.

    Thorbjørn Ravn Andersen : instead of throwing NullPointerException, consider throwing RuntimeException("e == null") (or an IllegalArgumentException) as this tells the developer reading the stacktrace that this is a deliberately thrown exception and not a logic error.
    matt b : Or an add a descriptive message to the NullPointerException. Also, +1 for compisition over inheritance.
    Steve Kuo : throw IllegalArgumentException as that is what it's for, an illegal argument (null)
    Andrew Coleson : Should fix your void clear() to not return anything, but otherwise +1 for sample code and composition.
    Nikhil Chelliah : Should be add(E e), not add(E o).
    Shervin : Also there is an error in your add method. It should `return wrappedSet.add(e)`.
    cdmckay : @Shervin: Fixed, thanks.
    Jason S : I'd suggest not explicitly allocating a HashSet, but instead take a Set argument in the constructor. This then makes NoNullSet a decorator class that can work w/ HashSet or TreeSet or EnumSet or whatever.
  • You may also wish to check out Google Collections. They are more null phobic, I believe.

  • You could use apache collections and its PredicatedCollection class, and set the predicate to not allow nulls. You will get exceptions if someone sends nulls in.

  • This is a failry general purpose way of doing it - you provide a Filter implementation that can restrict what gets added in whatevber way you want. Take a look at the source for java.util.Collections for ideas on the wrapping (I think my implementaiton of the FilteredCollection class is correct... but it is not extensivly tested). There is a sample program at the end that shows the usage.

    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);
        }
    }
    
  • BTW, if you'd asked for a Map implementation that does not allow nulls, the old java.uitl.Hashtable does not.

0 comments:

Post a Comment