使用ListIterator的集合,是一个唯一的列表

时间:2016-04-15 11:27:58

标签: java list collections set java-7

最近我在一个项目中一直很烦我,而且我的Google phoo让我无法找到合适的答案。

是否有一个集合,可以访问ListIterator,但也只允许集合中的唯一值?

对此的推理,我有一个项目集合,在这个集合中应该只有每个元素之一。我也希望能够在两个方向上遍历这个集合。我希望它可以被排序或允许我使用Collections.Sort();

对其进行排序

我找不到合适的东西,不得不使用以下代码编写自己的类:

public class UniqueArrayList<E> extends ArrayList<E> {
    @Override
    public boolean add(E element){
        if (this.contains(element))
            return false;
        else
            return super.add(element);
    }

    @Override
    public void add(int index, E element){
        if (this.contains(element))
            return;
        else
            super.add(index, element);
    }

    @Override
    public boolean addAll(Collection<? extends E> c){
        if (new HashSet<E>(c).size() < c.size())
            return false;
        for(E element : c){
            if (this.contains(c))
                return false;
        }
        return super.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        if (new HashSet<E>(c).size() < c.size())
            return false;
        for(E element : c){
            if (this.contains(c))
                return false;
        }
        return super.addAll(index, c);
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > this.size())
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    @Override
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    @Override
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size())
                throw new NoSuchElementException();
            Object[] elementData = UniqueArrayList.this.toArray();
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                UniqueArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }


    }

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = UniqueArrayList.this.toArray();
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                //Need to allow this for the collections sort to work!
                //if (!UniqueArrayList.this.contains(e))
                UniqueArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                UniqueArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
}

然而,这远非完美,因为我无法覆盖ListIterator.set();因为Collections.sort();使用它来移动列表中的项目。如果我尝试阻止非唯一项目添加到此处的列表中,则排序永远不会发生。

那么,有没有人有更好的方法或知道另一个遵守我想要的规则的集合?或者我只需要忍受这个相当恼人的问题?

[编辑]

这是Collections.sort();方法:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

他们这样做的理由是:

  

此实现将指定的列表转储到数组中,对其进行排序   数组,并迭代列表重置每个元素   数组中的相应位置。这避免了n 2   尝试对链接进行排序所导致的log(n)性能   列表到位。

4 个答案:

答案 0 :(得分:7)

当您需要唯一值时,应尝试切换到Set。 您可以将TreeSet与Comparator实例一起使用来对条目进行排序。 TreeSet的descendingSet()方法将为您提供相反的顺序。 如果你真的需要一个ListIterator,你可以从集合中创建一个临时列表。

答案 1 :(得分:5)

正如Thor Stan提到的那样,TreeSet可以让您获得所需的大部分内容。它确保元素是唯一的,它使它们保持排序,您可以使用iterator()descendingIterator()在任一方向上迭代它。

但是,为什么你要求ListIterator并不完全清楚。关于ListIterator的大部分内容都非常有位置:索引的概念,或者在当前位置添加内容,或者设置当前元素。这些对于排序集合没有意义。

您可能正在寻找的ListIterator的一个方面是能够在迭代过程中反转方向。您无法直接使用TreeSet迭代器执行此操作,因为它仅通过普通Iterator而非ListIterator提供访问权限。

但是,TreeSet实现了NavigableSet接口,它允许您按顺序逐步浏览元素。 NavigableSet接口是SortedSet的子接口,它提供了first()last()方法,可以帮助您开始其中一个&#34;结束&#34;集合。在集合中有元素后,您可以使用lower(E)higher(E)方法进入任一方向。或者,如果你想从中间的某个地方开始,你不能通过索引从一个位置开始,但你可以从一个值开始(不需要成为该集合的成员),然后调用{ {1}}或lower(E)

例如:

higher(E)

答案 2 :(得分:1)

Java认为List允许非唯一项,而Set则不允许。集合显然不支持ListIterators,因此使用ListIterator的代码可以假设基础集合不是Set

Java 8不再使用ListIterator进行排序,但是如果你坚持使用Java 7并不能真正帮助你。基本上在很大程度上取决于你的背景和用法,使用像{1}}这样的人可能会很有用,比如Thor Stan在他的回复中说,在需要时根据需要创建Set

另一个选择是提供您自己的List,它通过不检查重复项的方法访问列表。这样做的好处是不会创建无关的对象,但ListIterator选项最有可能导致代码更短,并且极不可能在性能方面表现出色。

也可能有其他选择,但我想不出任何优雅的选择。

答案 3 :(得分:1)

这是一个没有简单答案的问题。

问题是你违反了add(int, E)的合同。此方法必须添加元素或抛出异常 - 不允许在不添加元素的情况下返回。

如果您覆盖set(int, E),以便有时它不会设置该元素,那么它也会破坏该方法的合同,并且会阻止Collections.sort()按您的方式工作。< / p>

我不建议违反这些合同 - 这可能会导致其他作用于列表的方法以不可预测的方式运行。

其他人遇到了这些困难 - 例如,请参阅SetUniqueList的java文档。

您的实施的另一个问题是它会非常慢,因为ArrayList.contains()是线性搜索。

一种解决方案是编写一个同时使用ArrayListHashSet的类,并编写自己的addset版本,而不是打破通常版本的合同。这没有经过测试,但你明白了。

public final class MyList<E extends Comparable<? super E>> extends AbstractList<E> {

    private final List<E> list = new ArrayList<>();
    private final Set<E> set = new HashSet<>();

    public E get(int i) {
        return list.get(i);
    }

    public int size() {
        return list.size();
    } 

    public boolean tryAdd(E e) {
        return set.add(e) && list.add(e);
    }

    public boolean tryAdd(int i, E e) {
        if (set.add(e)) {
            list.add(i, e);
            return true;
        }
        return false;
    }

    public boolean trySet(int i, E e) {
        return set.add(e) && set.remove(list.set(i, e));
    }

    public boolean remove(Object o) {
        return set.remove(o) && list.remove(o);
    }

    public void sort() {
        Collections.sort(list);
    }

    // One bonus of this approach is that contains() is now O(1)
    public boolean contains(Object o) {
        return set.contains(o);
    }

    // rest omitted.
}

这样的事情不会破坏List的合同。请注意,对于此版本,addset会抛出UnsupportedOperationException,因为这是从AbstractList继承的行为。您可以通过致电sort myList.sort()listIterator。此外,set方法可行,但您无法将其用于addremove(尽管add可行)。

如果在迭代此set时需要ListListIterator元素,则需要使用显式索引,而不是ListIterator。就个人而言,我不认为这是一个主要问题。对于像LinkedList这样没有恒定时间get方法的列表,ArrayList是必需的,但对于/polls/templates/,它很好但不是必需的。