Java:为什么迭代器不可复制

时间:2010-09-30 15:06:35

标签: java iterator copy iterable

我认为Iterator.copy()将是一个非常方便的功能。您可以以更好的方式实现迭代器过滤器。

例如,Googles Java Collection中filter(和类似)函数使用UnmodifiableIterator(仅Iterator没有remove)的唯一原因是因为你无法实现这样的过滤器Iterator,否则无法在某些时候复制它。 (实际上,使用当前界面是不可能的;请试试自己。)

另一个优点是你可以在for-each-loop中使用迭代器:因为可复制的迭代器也可以自动迭代。另请参阅this问题。现在,不允许这样做的主要设计原因是因为实现IteratorIterable的{​​{1}}会使迭代器无效。通过使用Iterator<T> iterator() { return this; }函数,它就像copy一样简单,并且它不会使原始迭代器无效。因此,没有理由不允许这样做。

有什么理由吗?只是为了让它实现起来不那么复杂?

11 个答案:

答案 0 :(得分:10)

虽然它们通常是,但迭代器理论上不必链接到集合。例如,输入流上的复制方法很难实现,并且很容易导致模糊的内存问题。

答案 1 :(得分:5)

Iterator表示来自源( stream )的位置(来自java的Iterable),并且无法保证可以复制甚至访问流的来源。

例如,您可以在从网络服务器流式传输时迭代字节,在这种情况下,无法告诉网络服务器中间流“从此位置开始,我希望您向我发送相同的字节两次,但我要求它们是异步的。“

只有一个流,无法复制。

您通常看到的大多数Iterator超过Collection这一事实是偶然的。

答案 2 :(得分:2)

Google拥有UnmodifiableIterator的唯一原因是其馆藏中基本上保证不变性。他们确保你无法改变集合的内部状态。

不要忘记迭代器的最初想法是它是一个指向当前元素的指针,并且它管理下一个/前一个横向(对于双向链接迭代器的反向)到下一个/前一个元素它

没有实际的理由为什么迭代器不是Cloneable,很简单,克隆迭代器仍然意味着有一个迭代器指向相同的集合元素(除了它现在位于2个不同的地址空间)。除非你希望克隆的迭代器指向另一个集合,否则没有意义。

答案 3 :(得分:1)

您始终可以实施自己的CopyableIterator来实施Iterator。然后就可以了

new CopyableItereator(collection);

班级就像这样

class CopyableIterator implements Iterator{
Iterator iterator;
Collection collection;
int index=0;

public CopyableIterator(Collection collection){
super();
this.collection = collection;
this.iterator = collection.iterator();
}

public CopyableIterator(Collection collection, int index){
super();
this.collection =collection;
this.iterator = collection.iterator();
this.advanceToIndex(iterator,index); //This function just moves the iterator till the index.
this.index=index;
}

//Override the functions of Iterator here returning iterator.function()

@Override
public Object next(){
index++;
return this.iterator.next();
}

public CopyableIterator copy(){
return new CopyableIterator(this.collection,this.index)

}

}

免责声明:这大致是班级。它尚未经过测试。

答案 4 :(得分:1)

我想要这样的东西,这就是我所做的(基于在Lambdaj上完成的一些工作) 主要缺陷是这个Iterator基本上会填充一个List,其中包含Iterator的所有内容,这些内容可能在内存中非常重。

为什么我使用了List,因为有时Iterator会按特定顺序进行迭代,因此“sub - Iterators”必须执行相同操作(而ListIterator确实帮助了我这里)。

public class IterableIterator<T> implements Iterable<T>, Iterator<T> {
    //The content of the given iterator. Will be filled by its iterators.
    private final List<T> iteratorContent = new ArrayList<T>();
    private final Iterator<T> originalIterator;
    private final Iterator<T> innerIterator;

    public IterableIterator(Iterator<T> originalIterator) {
        this(originalIterator, false);
    }

    public IterableIterator(Iterator<T> originalIterator, boolean cache) {
        if (originalIterator == null) {
            throw new IllegalArgumentException("Parameter can't be null");
        }

        this.originalIterator = originalIterator;
        if (cache) {
            while (originalIterator.hasNext()) {
                iteratorContent.add(originalIterator.next());
            }
        }

        innerIterator = iterator();
    }

    @Override
    public Iterator<T> iterator() {
        return new IteratorIterator();
    }

    @Override
    public boolean hasNext() {
        return innerIterator.hasNext();
    }

    @Override
    public T next() {
        return innerIterator.next();
    }

    @Override
    public void remove() {
        innerIterator.remove();
    }

    private class IteratorIterator implements Iterator<T> {
        private ListIterator<T> innerIterator = iteratorContent.listIterator();

        @Override
        public boolean hasNext() {
            return innerIterator.hasNext() || originalIterator.hasNext();
        }

        @Override
        public T next() {
            if (!innerIterator.hasNext() && originalIterator.hasNext()) {
                T item;
                synchronized (originalIterator) {
                    item = originalIterator.next();
                    iteratorContent.add(item);
                }
                innerIterator = iteratorContent.listIterator(innerIterator.nextIndex());
            }
            if (innerIterator.hasNext()) {
                try {
                    return innerIterator.next();
                } catch (ConcurrentModificationException e) {
                    //Quick and dirty solution if you have a concurrent modification.
                    //It can't happen from the outside, so you can easily suppose that another originalIterator
                    //from this class has been called and had added elements to the list.
                    //Best thing to do, reset the originalIterator to the current position.
                    innerIterator = iteratorContent.listIterator(innerIterator.nextIndex());
                    return innerIterator.next();
                }
            }

            throw new NoSuchElementException();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

答案 5 :(得分:1)

作为您想要复制迭代器的简单示例,请考虑以下代码,该代码在单个数组中查找第一对匹配值。

for(int i=0;i<size;i++)
{
  x = array[i];

  for(int j=i+1;j<size;j++)
  {
    y = array[j];
    if(x == y)
    {
      doSomething();
      break;
    }
}

注意“j = i + 1”。这就是你遇到迭代器问题的地方。哦,好像,Java中的解决方法似乎很常见......

答案 6 :(得分:0)

  

有什么理由吗?只是为了让它实现起来不那么复杂?

设计和实现支持Iterator操作的copy包装类会很简单。我不确定它通常是否有用,尤其是因为在一般情况下它将是一项昂贵的操作。仅此一点就足以让Java设计人员不想将copy()添加到Iterator接口。

<强>后续

这是我想到的事情:

public class CopyableIterator<T> implements Iterator<T> {
    private Iterator<T> it;
    private List<T> copy = new ArrayList<T>();
    private int pos;
    public CopyableIterator(Iterator<T> it) {
        while (it.hasNext()) {
            copy.append(it.next());
        }
        this.it = copy.iterator();
    }
    public T next() {
        T res = next();
        pos++;
        return res;
    }
    public boolean hasNext() {
        return it.hasNext();
    }
    public Iterator<T> copy() {
        return copy.sublist(pos, copy.size()).iterator();
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

原因是:

  • 如果我要封装不透明的Iterator,那么我可以复制的 方式是使用next()hasNext()阅读它并从中构建副本Iterator

  • 但在开始使用原始迭代器之前,我必须这样做。

  • 这样做的简单方法是在开始使用它之前制作迭代器内容的副本。 (它可能是通过延迟增量复制完成的,但实现可能会变得非常复杂......尤其是在考虑复制复制的迭代器时。)

另一个答案中提出的方法仅限于普通的集合迭代器。如果你有一个包装的迭代器,或者某个其他来源的迭代器(例如)没有实现Iterable,那么你就被烤了。

即使有了这个前提条件,上面的方法也不会返回迭代器的真实副本。相反,它返回底层集合的新迭代器。这是一个重要的区别。除非您实际复制迭代元素引用,否则无法保证迭代器将返回相同的序列。查看Concurrent...集合类型的迭代器的记录行为。

答案 7 :(得分:0)

复制Iterator究竟是什么意思?你是说它应该能够创建一个新的Iterator,就像它自己一样,除了从头开始?这是Iterable的责任...复制该功能是没有意义的,特别是考虑到迭代器的有状态性......它只会让事情变得混乱。

如果你写下:

,你会发生什么?
Iterator<Foo> iter = someIterable.iterator();
iter.next();
iter.next();
for (Foo foo : iter) {
  ...
}

您是否希望for循环迭代迭代器返回的每个项目,或者除了前两个项目之外的每个项目?在for循环完成后,你期望迭代器是空的吗?

答案 8 :(得分:0)

ILMTitan和ChristofferHammarström的暗示,但没有具体说明复制流可能是不可能的,因为它要求流元素具有复制函数的实现,以便保存可复制迭代器所需的状态。意识到元素可以是可变的(或引用动态值),它们可能引用需要自定义复制功能的其他结构和语义。

因此,可复制迭代器与可复制流元素不正交,因此这就是为什么一般不可复制迭代器的原因。

另一个更加模糊的原因是复制行为对内存分配和释放有副作用。即使是流元素的复制功能也可能有其他副作用。

另一个原因是在编译汇编语言时可能无法进行某些低级优化。

答案 9 :(得分:-1)

创建迭代器是为了使用所述集合支持的数据逐个遍历集合中的所有对象。

Iterator<T>几乎总是使用私有内部类来实现,该内部类可以使用属于外部类的状态。因此,如果不编写自己的Iterator(或其他),就无法真正修改Collection的行为。

复制迭代器可能会导致许多新问题,例如与支持集合失去同步。

答案 10 :(得分:-1)

你不能可能复制迭代器 - 它基本上没有意义。对于某些人来说,这在Iterator界面中显而易见,但让我们用一个具体的例子来演示它。事实上,让我们举一个关于具体的例子。

bar with pieces of concrete

这是混凝土条上的混凝土迭代器的图片。在我们的情况下迭代意味着应用撬棍来打破一块酒吧。现在,请注意:

  • 该栏不是一个片段的集合(虽然其中一些有错误):我们在迭代时创建片段。
  • 通过迭代器(next())进行迭代的结果永远不会是条形图的另一次迭代的结果。结果已从中删除。
  • 迭代可能会产生不同的部分,具体取决于天气,你施加的力量,或者某种热噪声(想想:随机性)。
  • 通过迭代器(next())进行迭代的结果永远不会是条形图的另一次迭代的结果 - 因为精确迭代结果的概率空间是连续的,并且没有特定的结果部分具有非 - 零概率测量。

上述任何一项都应该说服你不要试图复制迭代器&#34;那是愚蠢的......