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

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

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

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

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

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


person Aaron K    schedule 26.02.2009    source источник
comment
Во всех наборах в значительной степени должен быть реализован какой-то способ быстрого поиска дубликатов либо с помощью Comparable, либо по хэш-кодам, иначе сканирование дубликатов было бы слишком болезненным.   -  person Paul Tomblin    schedule 26.02.2009
comment
Вы можете дать TreeSet Comparator, что означает, что элементы не должны быть Comparable (должен был быть отдельный метод создания для режима Comparable, IMO).   -  person Tom Hawtin - tackline    schedule 26.02.2009
comment
Вы также можете проверить, является ли что-то нулевым, когда вы извлекаете это из набора.   -  person cdmckay    schedule 26.02.2009
comment
Альтернативная идея: если единственная причина этого - предотвращение ошибок, выполните сканирование на наличие нулей в вашем наборе тестов JUnit, чтобы выявить любые утечки нулевых значений в набор.   -  person mikera    schedule 27.06.2010


Ответы (15)


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

person Tom Hawtin - tackline    schedule 26.02.2009
comment
Солнце? Обновления структуры сбора, вероятно, будут инициированы Google. Кстати, надеюсь, должна быть коллекция BOF a JavaOne. - person Tom Hawtin - tackline; 27.02.2009
comment
классический пример предпочтения композиции перед наследованием .. +1 - person Inquisitive; 12.07.2012
comment
Guava предоставляет ForwardingSet класс, который по умолчанию перенаправляет все вызовы делегату. Затем вы можете просто переопределить add и addAll. - person takteek; 29.01.2013

Я бы сказал, используйте композицию вместо наследования ... это может потребовать больше работы, но она будет более стабильной перед лицом любых изменений, которые 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).

person cdmckay    schedule 26.02.2009
comment
Я бы предложил не выделять HashSet явно, а вместо этого использовать аргумент Set ‹E› в конструкторе. Затем это делает NoNullSet классом-декоратором, который может работать с HashSet, TreeSet или EnumSet или чем-то еще. - person Jason S; 28.06.2010
comment
Я не согласен с тем, чтобы бросать IllegalArgumentException. Это должно быть NullPointerException. Хотя это бесконечное обсуждение, Set.add() уже вызывает NPE если указанный элемент равен нулю и этот набор не допускает пустых элементов. Вы бы создали другое поведение для одного и того же, бросив сейчас IllegalArgumentException. - person Forage; 06.05.2013
comment
Он должен выдать NullPointerException, как определено в документации Collection и Set. @throws NullPointerException if the specified element is null and this set does not permit null elements. - person José Roberto Araújo Júnior; 28.07.2016
comment
Вместо того, чтобы называть это композицией, я бы сказал, что было бы более подходящим называть это агрегацией, потому что набор класс МОЖЕТ СУЩЕСТВОВАТЬ < / b> без его класса-оболочки, т.е. NoNullSet - person Andy; 12.01.2017

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

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

1. Решение Guava:

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

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

Set noNulls = new HashSet();
CollectionUtils.addIgnoreNull(noNulls, object);
person Fritz Duchardt    schedule 21.09.2015
comment
Решение Guava не будет работать для Guava 16+, поскольку Constraints был удален (см. stackoverflow.com/a/33806747/2294031 ). CollectionUtils.addIgnoreNull не вызовет исключения, если добавляемый объект имеет значение NULL, поэтому он не потерпит неудачу, как предполагал OP. - person Snozzlebert; 16.12.2019

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

person Uri    schedule 27.02.2009

Это неудачный способ сделать это общего назначения - вы предоставляете реализацию фильтра, которая может ограничивать то, что добавляется любым способом, которым вы хотите. Взгляните на источник 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);
    }
}
person TofuBeer    schedule 27.02.2009

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

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

person Matt Fenwick    schedule 19.10.2012
comment
Недостатком этой реализации также является то, что она неизменяема. Из документов: For this reason, and to avoid general confusion, it is strongly recommended to place only immutable objects into this collection.. Не думайте, что OP просил. - person Xtreme Biker; 05.09.2014

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

person mipadi    schedule 26.02.2009
comment
Не забывайте allAll и конструкторы! - person Paul Tomblin; 26.02.2009
comment
Фактически, addAll и конструкторы не нужно переопределять, поскольку они определены в AbstractSet и AbstractCollection для простого вызова метода add. Так что действительно нужно переопределить только add. - person Eric Petroelje; 26.02.2009
comment
Возможно, вам будет лучше использовать композицию вместо создания подклассов, поскольку вы не контролируете класс, который подклассифицируете (что, если Sun добавит новый метод в наборы, которые позволят пользователям добавлять null?) - person cdmckay; 26.02.2009
comment
Вам лучше обернуть реализацию Set. - person Steve Kuo; 27.02.2009
comment
Это явно не рекомендуется в Effective Java, вы можете указать на это, почему. Расширение AbstractSet и перенос существующего набора - лучший способ. - person daniu; 27.02.2020

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

person Julien Chastang    schedule 26.02.2009

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

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

person Michael Borgwardt    schedule 29.06.2009

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

person MichaelStoner    schedule 12.02.2015

[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
person JavaTechnical    schedule 27.02.2020

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

person REA_ANDREW    schedule 26.02.2009

Почему вы не хотите разрешать 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);
    }
};

addAll() HashSet вызывает add() несколько раз, так что это единственный метод, который вам придется переопределить.

person Michael Myers    schedule 26.02.2009
comment
Вы не должны рассчитывать на вызов add () с помощью addAll (), поскольку это деталь реализации и не всегда может быть правдой. - person cdmckay; 26.02.2009
comment
@cdmckay: Если вы еще не проголосовали за ответ Тома Хотина, сделайте это сейчас! :) - person Michael Myers; 26.02.2009
comment
О, я вижу, вы вместо этого добавили свой ответ. - person Michael Myers; 26.02.2009
comment
@matt b: верно ли это для HashSet в JVM IBM? JRocket? Путь к классам GNU? Новый оптимизированный в Sun Java 8? Если это не в JLS, вы не можете на это рассчитывать. - person Darron; 27.02.2009

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

person Pavithra    schedule 23.08.2012