tags:

views:

2441

answers:

9

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.

+2  A: 

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.

mipadi
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!
Paul Tomblin
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.
Eric Petroelje
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?)
cdmckay
You are better off wrapping a Set implementation.
Steve Kuo
A: 

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?

REA_ANDREW
A: 

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.

Michael Myers
You should not count on addAll() calling add() as that is an implementation detail and may not always be true.
cdmckay
@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.
Michael Myers
@cdmckay - if you extend HashSet, that is always true...
matt b
@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.
Darron
+14  A: 

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.

Tom Hawtin - tackline
A little more work, but that's a good idea. +1
Michael Myers
Actually, it might not even be more work.
Michael Myers
Any idea why Sun didn't do this already?
Aaron K
+1 - terrific thought.
duffymo
A much better idea than inheritance
Steve Kuo
Sun? Updates to the collection framework are likely to be driven by Google. Btw, there should hopefully be a collections BOF a JavaOne.
Tom Hawtin - tackline
+13  A: 

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.

cdmckay
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.
Thorbjørn Ravn Andersen
Or an add a descriptive message to the NullPointerException. Also, +1 for compisition over inheritance.
matt b
throw IllegalArgumentException as that is what it's for, an illegal argument (null)
Steve Kuo
Should fix your void clear() to not return anything, but otherwise +1 for sample code and composition.
Andrew Coleson
Should be add(E e), not add(E o).
Nikhil Chelliah
Also there is an error in your add method. It should `return wrappedSet.add(e)`.
Shervin
@Shervin: Fixed, thanks.
cdmckay
I'd suggest not explicitly allocating a HashSet, but instead take a Set<E> argument in the constructor. This then makes NoNullSet a decorator class that can work w/ HashSet or TreeSet or EnumSet or whatever.
Jason S
A: 

You may also wish to check out Google Collections. They are more null phobic, I believe.

Julien Chastang
A: 

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.

Uri
+2  A: 

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);
    }
}
TofuBeer
+1  A: 

BTW, if you'd asked for a Map implementation that does not allow nulls, the old java.uitl.Hashtable does not.

Michael Borgwardt