Java中的Iterator接口有什么好处?
-
01-07-2019 - |
题
我刚刚了解了Java集合框架如何实现链表中的数据结构。据我了解, Iterators
是一种遍历数据结构(例如列表)中的项目的方法。为什么使用这个接口?为什么有这些方法 hasNext()
, next()
和 remove()
不直接编码到数据结构实现本身?
来自Java网站: 链接文本
公共接口迭代器<E>
收藏集的迭代器。迭代器代替Java Collections框架中的枚举。迭代剂与枚举不同的不同方式不同:
该界面是Java Collections框架的成员。
- 迭代器允许呼叫者使用定义明确的语义中的迭代过程中删除基础集合中的元素。
- 方法名称已得到改善。
我尝试谷歌搜索,似乎找不到明确的答案。有人可以解释一下为什么 Sun 选择使用它们吗?是因为更好的设计吗?提高安全性?良好的面向对象实践?
任何帮助将不胜感激。谢谢。
解决方案
为什么使用这个接口?
因为它支持基本操作,允许客户端程序员迭代任何类型的集合(注意:不一定是 Collection
在里面 Object
感觉)。
为什么方法...未直接编码数据结构本身?
它们是的,它们只是被标记为“私有”,因此您无法触及它们并破坏它们。进一步来说:
- 您可以实现或子类化
Iterator
这样它就可以做标准对象不做的事情,而不必改变它迭代的实际对象。 - 可遍历的对象不需要让其接口因遍历方法而混乱,特别是任何高度专业化的方法。
- 你可以交出
Iterators
无论您希望有多少个客户,每个客户都可以按照自己的时间、速度进行遍历。 - 爪哇
Iterators
如果支持它们的存储被修改,而您仍然有一个 java.util 包,特别是会抛出异常Iterator
出去。此异常让您知道Iterator
现在可能会返回无效对象。
对于简单的程序,这些似乎都不值得。不过,您很快就会意识到它们的有用性的复杂性。
其他提示
你问:“为什么 hasNext()、next() 和 remove() 方法没有直接编码到数据结构实现本身?”。
Java Collections 框架选择将 Iterator 接口定义为集合本身的外部化。通常,由于每个 Java 集合都实现了 Iterable
接口,Java程序会调用 iterator
创建自己的迭代器,以便可以在循环中使用它。正如其他人指出的那样,Java 5 允许我们通过 for-each 循环直接使用迭代器。
将迭代器外部化到其集合允许客户端控制如何迭代集合。我能想到的一个有用的用例是,当一个人拥有一个无限的集合(例如互联网上的所有网页)以进行索引时。
在经典的 GoF 书中,内部迭代器和外部迭代器之间的对比阐述得非常清楚。
一个基本问题是决定哪一方控制迭代,迭代器还是使用迭代器的客户端。当客户端控制迭代时,迭代器称为外部迭代器,当迭代器控制迭代时,迭代器称为内部迭代器。使用外部迭代器的客户端必须提前遍历并从迭代器显式请求下一个元素。相反,客户端将要执行的操作交给内部迭代器,迭代器将该操作应用于每个元素......
外部迭代器比内部迭代器更灵活。例如,使用外部迭代器比较两个集合的相等性很容易,但使用内部迭代器实际上是不可能的......但另一方面,内部迭代器更容易使用,因为它们为您定义了迭代逻辑。
有关内部迭代器如何工作的示例,请参阅 Ruby 的 Enumerable
API,具有内部迭代方法,例如 each
. 。在 Ruby 中,想法是传递一段代码(即闭包)到内部迭代器,以便集合可以处理它自己的迭代。
将集合与指针分开很重要。迭代器指向集合中的特定位置,因此不是集合的组成部分。这样,例如,您可以在同一个集合上使用多个迭代器。
这种分离的缺点是迭代器不知道对其迭代的集合所做的更改。所以你不能改变集合的结构并期望迭代器继续它的工作而没有“抱怨”。
使用 Iterator
接口允许任何实现其方法的类充当迭代器。Java 中接口的概念在某种程度上具有在类中提供某些功能的合同义务, implements
接口,以接口要求的方式进行操作。由于必须满足合同义务才能成为有效的班级,因此看到该班级的其他班级 implements
接口,因此可以放心地知道该类将具有这些某些功能。
在此示例中,不是实现方法 (hasNext(), next(), remove()
) 在里面 LinkedList
类本身, LinkedList
类将声明它 implements
这 Iterator
接口,以便其他人知道 LinkedList
可以用作迭代器。反过来, LinkedList
类将实现以下方法 Iterator
接口(如 hasNext()
),所以它可以像迭代器一样工作。
换句话说,实现接口是一种面向对象的编程概念,让其他人知道某个类具有它所声称的功能。
这个概念是通过具有必须由实现该接口的类实现的方法来强制执行的。这确保了其他想要使用实现该类的类 Iterator
接口,它确实具有迭代器应该具有的方法,例如 hasNext()
.
另外,应该注意的是,由于Java没有多重继承,因此可以使用接口来模拟该功能。通过实现多个接口,一个类可以作为子类来继承某些功能,同时也可以通过实现一个接口来“继承”另一个类的功能。一个例子是,如果我想要一个子类 LinkedList
类称为 ReversibleLinkedList
它可以按相反的顺序迭代,我可以创建一个名为的接口 ReverseIterator
并强制要求它提供 previous()
方法。自从 LinkedList
已经实现了 Iterator
, ,新的可逆列表将同时实现 Iterator
和 ReverseIterator
接口。
您可以阅读有关接口的更多信息 什么是接口? 摘自 Sun 的 Java 教程。
可以同时使用迭代器的多个实例。将它们视为基础数据的本地游标。
顺便提一句:偏爱接口而不是具体实现会失去耦合
寻找迭代器设计模式,在这里: http://en.wikipedia.org/wiki/Iterator
因为您可能正在迭代一些不是数据结构的东西。假设我有一个从服务器获取结果的网络应用程序。我可以返回一个围绕这些结果的迭代器包装器 流式传输它们 通过任何接受 Iterator 对象的标准代码。
将其视为良好 MVC 设计的关键部分。数据必须从模型中获取(即数据结构)以某种方式传递给视图。使用迭代器作为中间人可以确保模型的实现永远不会暴露。您可以将 LinkedList 保留在内存中、从解密算法中提取信息或包装 JDBC 调用。这对于视图来说并不重要,因为视图只关心 Iterator 接口。
一篇有趣的论文讨论了使用迭代器的优点和缺点:
我认为这只是很好的面向对象实践。您可以拥有处理各种迭代器的代码,甚至让您有机会创建自己的数据结构或实现迭代器接口的通用类。您不必担心其背后是什么样的实现。
只是 M2C,如果你不知道的话:在以下情况下,您可以避免直接使用迭代器接口 对于每个 循环就足够了。
最终,因为迭代器捕获了适用于大量数据结构的控制抽象。如果您熟悉范畴论,那么这篇论文可能会让您大吃一惊: 迭代器模式的本质.
好吧,第一个要点似乎允许多线程(或单线程,如果你搞砸了)应用程序不需要锁定并发冲突的集合。例如,在 .NET 中,如果不锁定或继承 IEnumerable 和重写方法,则无法同时枚举和修改集合(或列表或任何 IEnumerable)(我们会遇到例外)。
迭代器只是添加了一种遍历项目集合的通用方法。i.remove() 是一个很好的功能,您可以在其中从要迭代的列表中删除元素。如果您只是尝试从列表中删除项目,通常会产生奇怪的效果或抛出异常。
接口就像是所有实现它的事物的契约。你基本上是说..任何实现迭代器的东西都保证具有行为相同的这些方法。如果您只关心在代码中处理迭代器类型,您还可以使用它来传递迭代器类型。(您可能不关心它是什么类型的列表..您只想传递一个迭代器)您可以将所有这些方法独立地放入集合中,但您不能保证它们的行为相同,或者它们甚至具有相同的名称和签名。
迭代器是 java 中可用的众多设计模式之一。设计模式可以被认为是方便的构建块、样式、代码/结构的使用。
要了解有关迭代器设计模式的更多信息,请访问此网站,其中讨论了迭代器以及许多其他设计模式。以下是 Iterator 网站上的一个片段: http://www.patterndepot.com/put/8/Behavioral.html
迭代器是设计模式中最简单,最常使用的迭代器之一。迭代器模式使您可以使用标准接口浏览列表或数据集合,而不必知道该数据的内部表示的详细信息。此外,您还可以定义执行一些特殊处理的特殊迭代器,并仅返回数据收集的指定元素。
迭代器可用于任何类型的集合。它们允许您针对项目集合定义算法,而不管底层实现如何。这意味着您可以处理列表、集合、字符串、文件、数组等。
十年后,您可以将 List 实现更改为更好的实现,并且算法仍然可以无缝运行。
当您处理 Java 中的集合时,迭代器非常有用。
使用 对于每个 Loop(Java1.5) 用于迭代集合、数组或列表。
Java Collections Framework 中使用 java.util.Iterator 接口来允许修改集合,同时仍然迭代它。如果您只想干净地迭代整个集合,请使用 for-each 代替,但迭代器的优点是您可以获得的功能:可选的remove()操作,对于List Iterator接口来说更好,它也提供add()和set()操作。这两个接口都允许您迭代集合并同时更改其结构。在使用 for-each 迭代集合时尝试修改集合会抛出 ConcurrentModificationException,通常是因为集合被意外修改!
看一下 ArrayList 类
它内部有2个私人类(内部类),称为ITR和Listitr
他们分别实现了 Iterator 和 ListIterator 接口
公共类ArrayList......{ //封装类
private class Itr implements Iterator<E> {
public E next() {
return ArrayList.this.get(index++); //rough, not exact
}
//we have to use ArrayList.this.get() so the compiler will
//know that we are referring to the methods in the
//enclosing ArrayList class
public void remove() {
ArrayList.this.remove(prevIndex);
}
//checks for...co mod of the list
final void checkForComodification() { //ListItr gets this method as well
if (ArrayList.this.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
private class ListItr extends Itr implements ListIterator<E> {
//methods inherted....
public void add(E e) {
ArrayList.this.add(cursor, e);
}
public void set(E e) {
ArrayList.this.set(cursor, e);
}
}
}
当您调用方法迭代器()和ListIterator()时,它们会返回私有类或listitr的新实例,并且由于这些内部类“在” enclosing arraylist类中,因此他们可以在不触发concurrentModification exception的情况下自由修改arraylist ,除非您同时(汇总)通过set()add()或remove(删除)类的方法更改列表。