34

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.

Hash
  • 4,647
  • 5
  • 21
  • 39
Aaron K
  • 6,901
  • 3
  • 34
  • 29
  • All Sets pretty much have to implement some sort of way to quickly find duplicates, either by Comparable or hash codes, otherwise scanning for dups would be too painful. – Paul Tomblin Feb 26 '09 at 15:55
  • You can give a TreeSet a Comparator which means the elements don't need to be Comparable (should have been a separate creation method for the Comparable mode, IMO). – Tom Hawtin - tackline Feb 26 '09 at 16:12
  • You can also check if something is null as you pull it out of the set. – cdmckay Feb 26 '09 at 16:39
  • Alternative idea: if the only reason for this is preventing bugs do a scan for nulls in your JUnit test suite to pick up any null leaks into the set. – mikera Jun 27 '10 at 11:43

15 Answers15

28

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
  • 145,806
  • 30
  • 211
  • 305
  • 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 Feb 27 '09 at 10:09
  • 1
    classic example of favoring composition over inheritance..+1 – Inquisitive Jul 12 '12 at 09:53
  • 1
    Guava provides a [ForwardingSet](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ForwardingSet.html) class that forwards all calls to a delegate by default. Then you can just override add and addAll. – takteek Jan 28 '13 at 21:16
22

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 ...
}

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).

Jens Bannmann
  • 4,845
  • 5
  • 49
  • 76
cdmckay
  • 31,832
  • 25
  • 83
  • 114
  • 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. – Jason S Jun 28 '10 at 15:58
  • 3
    I disagree with throwing an `IllegalArgumentException`. It should be a `NullPointerException`. Although it's an [endless discussion](http://stackoverflow.com/questions/3881/illegalargumentexception-or-nullpointerexception-for-a-null-parameter), `Set.add()` already throws a NPE _"if the specified element is null and this set does not permit null elements"_. You would be creating different behaviour for the same thing by throwing an `IllegalArgumentException` now. – Forage May 06 '13 at 07:37
  • 1
    It should throw a `NullPointerException` as defined by the `Collection` and `Set` documentations. `@throws NullPointerException if the specified element is null and this set does not permit null elements`. – Polyana Fontes Jul 28 '16 at 19:45
  • Instead of calling this a **Composition**, I would say it would be more appropriate to call it an **Aggregation** because **Set** class **CAN EXIST** without its wrapper class i.e **NoNullSet** – Andy Jan 12 '17 at 18:04
14

There is no basic proprietary Set implementation that ignores or constrains null! There is EnumSet, but that one is tailors for the containment of enum types.

However, creating your own implementation can be avoided, if you use either Guava or Commons Collections:

1. Guava Solution:

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

2. Commons Collections:

Set noNulls = new HashSet();
CollectionUtils.addIgnoreNull(noNulls, object);
Fritz Duchardt
  • 11,026
  • 4
  • 41
  • 60
  • The Guava Solution won't work for Guava 16+ as `Constraints` was removed (see https://stackoverflow.com/a/33806747/2294031). `CollectionUtils.addIgnoreNull` will throw no exception if the object to add is null - so it won't fail as OP intended. – domids Dec 16 '19 at 15:49
2

Yes -- in the docs for com.google.common.collect.ImmutableSet:

A high-performance, immutable Set with reliable, user-specified iteration order. Does not permit null elements.

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
  • This implementation has the drawback of being immutable, too. From the docs: `For this reason, and to avoid general confusion, it is strongly recommended to place only immutable objects into this collection.`. Don't think that's what the OP was asking for. – Aritz Sep 05 '14 at 10:44
2

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
  • 88,451
  • 51
  • 221
  • 321
2

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
  • 60,850
  • 18
  • 118
  • 163
2

[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.

Instead of checking null, every time, we can simply remove the null once before iterating over the set.

You can remove the null values using set.remove(null);

      Set<String> set = new HashSet<>();

      set.add("test");
      set.add(null);
      set.add(null);
      System.out.println(set);

      set.remove(null);
      System.out.println(set);

      Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }

Output

[null, test]
[test]
test
JavaTechnical
  • 8,846
  • 8
  • 61
  • 97
1

for me, I didn't find one, so I 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)
    }
};
Basheer AL-MOMANI
  • 14,473
  • 9
  • 96
  • 92
1

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
  • 398,885
  • 90
  • 523
  • 479
  • Don't forget allAll and the constructors! – Paul Tomblin Feb 26 '09 at 15:57
  • 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 Feb 26 '09 at 16:03
  • 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 Feb 26 '09 at 16:20
  • 1
    You are better off wrapping a Set implementation. – Steve Kuo Feb 26 '09 at 22:35
  • This is explicitly advised against in Effective Java, you can refer to it for the reasons why. Extends `AbstractSet` and wrap an existing set is the way to go. – daniu Feb 27 '20 at 14:15
1

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

Julien Chastang
  • 17,592
  • 12
  • 63
  • 89
0

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

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
0

In this particular question/example surely if you have a HashSet<MyRandomObject> mySet call mySet.remove(null) before starting the iteration over all elements you mentioned?

MichaelStoner
  • 889
  • 10
  • 26
-1

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
  • 10,666
  • 8
  • 48
  • 71
-1

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
  • 188,989
  • 46
  • 291
  • 292
-2

Hashtable does not allow null values......