最近我在一个项目中一直很烦我,而且我的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)性能 列表到位。
答案 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()
是线性搜索。
一种解决方案是编写一个同时使用ArrayList
和HashSet
的类,并编写自己的add
和set
版本,而不是打破通常版本的合同。这没有经过测试,但你明白了。
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
的合同。请注意,对于此版本,add
和set
会抛出UnsupportedOperationException
,因为这是从AbstractList
继承的行为。您可以通过致电sort
myList.sort()
来listIterator
。此外,set
方法可行,但您无法将其用于add
或remove
(尽管add
可行)。
如果在迭代此set
时需要List
或ListIterator
元素,则需要使用显式索引,而不是ListIterator
。就个人而言,我不认为这是一个主要问题。对于像LinkedList
这样没有恒定时间get
方法的列表,ArrayList
是必需的,但对于/polls/templates/
,它很好但不是必需的。