Существует ли базовая реализация Java Set, не допускающая значений NULL?

API для интерфейса Java Set гласит:

Например, некоторые реализации запрещают nullэлементы, а некоторые имеют ограничения на типы их элементов.

Я ищу базовую реализацию Set, которая не требует упорядочивания (поскольку ArrayList предоставляет интерфейс List ) и не позволяет null . TreeSet , HashSet и LinkedHashSet допускают нулевые элементы. Кроме того, TreeSet требует, чтобы элементы реализовывали Comparable .

Похоже, что в Set настоящее время такой основы не существует. Кто-нибудь знает почему? Или, если он существует, где я могу его найти?

[Edit]: я не хочу разрешать null s, потому что позже в коде мой класс будет перебирать все элементы в коллекции и вызывать определенный метод. (На самом деле я использую HashSet<MyRandomObject >). Я предпочитаю быстро потерпеть неудачу, чем потерпеть неудачу позже или случайно столкнусь с каким-то причудливым поведением из-за null существа на съемочной площадке.

Ответов (15)

Решение

Лучше, чем расширять конкретную реализацию, вы можете легко написать прокси-реализацию, Set которая проверяет null s. Это аналогично Collections.checkedSet . Помимо применимости к любой реализации, вы также можете быть уверены, что переопределили все применимые методы. Многие недостатки были обнаружены путем расширения конкретных коллекций, в которые затем были добавлены дополнительные методы в более поздних версиях.

Вы можете легко написать свой собственный, создав подклассы соответствующего существующего класса и переопределив все соответствующие методы, чтобы вы не могли добавлять null элементы.

Я не уверен, что это правда. Но не могли бы вы наследовать от коллекции или HashTable по вашему выбору и переопределить метод Add, выбрасывая исключение, если элемент имеет значение NULL?

Да - в документации для com.google.common.collect.ImmutableSet :

Высокопроизводительный неизменяемый набор с надежным, определяемым пользователем порядком итераций. Не допускает нулевых элементов.

Hashtable не допускает нулевых значений ......

Не существует базовой проприетарной реализации Set, которая игнорирует или ограничивает значение null! Есть EnumSet , но он приспособлен для содержания перечислимых типов.

Однако создания собственной реализации можно избежать, если вы используете Guava или Commons Collections :

1. Решение Guava:

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

2. Коллекции Commons:

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

В этом конкретном вопросе / примере, конечно, если у вас есть HashSet<MyRandomObject> mySet звонок mySet.remove(null) перед началом итерации по всем элементам, которые вы упомянули?

[Edit]: я не хочу разрешать нули, потому что позже в коде мой класс будет перебирать все элементы в коллекции и вызывать определенный метод.

Вместо проверки null каждый раз мы можем просто удалить ноль один раз перед итерацией по набору .

Вы можете удалить нулевые значения, используя 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());
        }

Выход

[null, test]
[test]
test

для меня я не нашел, поэтому я 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)
    }
};

Почему вы не хотите позволять null?

Вы хотите, чтобы исключение было null добавлено в ваш набор? Если да, просто сделайте что-нибудь вроде этого:

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() вызовов add() повторно, так что это единственный метод , вы должны переопределить.

Я бы сказал, используйте композицию вместо наследования ... это может потребовать больше работы, но она будет более стабильной перед лицом любых изменений, которые Sun может внести в структуру коллекций.

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

Обратите внимание, что эта реализация не зависит от addAll вызова add (это деталь реализации, и ее не следует использовать, потому что нельзя гарантировать, что она останется верной во всех выпусках Java).

Вы также можете проверить Коллекции Google . Я считаю, что они более нуль-фобичны.

Вы можете использовать коллекции apache и его класс PredicatedCollection , а также установить предикат, не разрешающий нули. Вы получите исключения, если кто-то отправит нули.

Это неудачный способ сделать это общего назначения - вы предоставляете реализацию фильтра, которая может ограничивать то, что добавляется любым способом, который вы хотите. Взгляните на источник java.util.Collections для идей по упаковке (я думаю, что моя реализация класса FilteredCollection верна ... но она не тестируется всесторонне). В конце есть пример программы, которая показывает использование.

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

Кстати, если бы вы попросили Map реализацию, которая не допускает нулей, старая java.util.Hashtable - нет.