一,迭代器的基本知识:
1,为什么要用迭代器?(迭代:即对每一个元素进行一次“问候”)
比如说,我们定义了一个ADT(抽象数据类型),作为ADT的一种实现,如单链表。而单链表的基本操作中,大部分需要用到依次遍历单链表中的每一个元素。一般而言,我们就是用for循环来实现遍历,这样,当你新增一个对单链表的操作并需要使用遍历时,你就得重新写一个for循环而实现遍历。那么,为什么不将迭代(遍历)作为一种基本的ADT操作(基本的ADT操作如:新增一个元素、删除一个元素)呢?于是,迭代器就出场了。
2,鉴于上述迭代器的用途,我们可以知道:迭代器是用来遍历元素的,那么说明这个数据结构应该有一组“相同”类型的元素(如,单链表、数组……)。因此,在JAVA中,为什么在学习集合(Collection)时,经常会有迭代器(Iterator)概念。
3,JAVA中实现迭代器的方式有几种?
主要有两种:a,定义一个公共类并 implements Iterator<T>接口;同时,该类与实现ADT的类(如本例中的 LinkListWithIterator<T>)分开。当然,这两个类还需要以某种方式交互。因为,迭代器需要知道我迭代的东东到底是谁嘛。下面代码粗糙描述了公共的实现迭代器的类:SeparateIterator<T>如何与实现ADT的类交互。
1 public Interface ListInterface<T>{ 2 //此处定义了链表的基本操作 3 } 4 5 public class LinkList<T> implements ListInterface<T>{ 6 //LinkList类实现了ADT 7 //此处实现链表的基本操作 8 } 9 public class SeparateIterator<T> implements Iterator<T> 10 { 11 //此处实现了Iterator接口中的方法 12 } 13 14 //将实现了ADT类(即,LinkList)的对象作为构造函数的参数传入到实现迭代器类的构造方法中 15 //从而实现它们之间的交互 16 Iterator<T> it = new SeparateIterator<T>(LinkList实例);
b,在实现ADT的类中定义一个私有内部类,该私有内部类实现迭代器的功能,使用私有内部类的好处是,在该私有内部类中可以直接访问待迭代元素的数据域(具体看下面代码中的注释),效率较高。以下例子就是这种方式。
4,如何为自定义的数据结构实现一个迭代器?
在JAVA类库中java.util.ArrayList<T> 类代表存储一组“相同”类型的数据元素的数组,ArrayList类从其父类AbstractList中继承了 iterator() 方法来返回一个可以对存放在ArrayList中的元素进行迭代的迭代器。那万一根据需要,需要自定义了一个数据结构,又如何给该数据结构实现迭代器呢?下面以单链表为例进行说明:
二,具体的实例的代码
1,定义一个ListInterface<T>接口(相当于一个ADT),指定了单链表中的各种基本操作:
1 public interface ListInterface<T> { 2 //返回当前链表中元素个数 3 public int length(); 4 5 ////获得索引为index处的元素值,注意:索引从0开始 6 public T get(int index); 7 8 //返回链表中指定元素的索引,若指定元素不存在,返回-1 9 public int locate(T element); 10 11 //在指定索引index处添加指定的element 12 public void insert(T element, int index); 13 14 //采用尾插法为链表增加新节点 15 public void add(T element); 16 17 //采用头插法为链表增加新节点 18 public void addAtHeader(T element); 19 20 //删除指定索引处的节点 21 public T delete(int index); 22 23 //删除线性表中的最后一个元素 24 public T remove(); 25 26 //清空线性表 27 public void clear(); 28 }
2,定义ListWithIteratorInterface<T>接口并 extends ListInterface<T>,这样就可以将迭代器与ADT类联系起来。因为,实现ADT的类LinkListWithIterator<T> implements ListWithIteratorInterface<T>,从而以ListWithIteratorInterface为中界,将LinkListWithIterator(带有迭代器的ADT)与ListInterface<T>(线性表)联系起来了。进而,使得LinkListWithIterator<T>是一个:带有迭代器的单链表了。
这样,就可以通过①LinkListWithIterator<T>的对象调用 getIterator() 获得一个迭代器,②而该迭代器类又是LinkListWithIterator<T>的内部类,即可以直接迭代LinkListWithIterator的数据域。由①②,LinkListWithIterator<T>就表示一个带有迭代器的单链表了。
1 import java.util.Iterator; 2 3 public interface ListWithIteratorInterface<T> extends ListInterface<T>{ 4 public Iterator<T> getIterator(); 5 }
3,定义了一个类LinkListWithIterator<T>实现了ListInterface<T>接口(相当于ADT的实现类),LinkListWithIterator<T>中定义了一个内部类:IteratorForLinkedList,该内部类 implements Iterator<T>,
1 import java.util.Iterator; 2 import java.util.NoSuchElementException; 3 4 public class LinkListWithIterator<T> implements ListWithIteratorInterface<T>{ 5 private class Node{ 6 private T data; 7 private Node next; 8 private Node(){ 9 10 } 11 private Node(T data, Node next){ 12 this.data = data; 13 this.next = next; 14 } 15 } 16 /* 17 * 该内部类用来实现迭代器,从而能够对单链表进行迭代 18 * 使用内部类好处:能够直接访问ADT(抽象数据类型,如此例中的链表)的数据域,迭代效率高 19 */ 20 private class IteratorForLinkedList implements Iterator<T>{ 21 22 private Node nextNode;//Node类型是通过私有内部类定义的表示链表结点的一种数据类型 23 private IteratorForLinkedList() { 24 nextNode = header;//nextNode 用来跟踪迭代,将之初始化为头结点 25 } 26 27 public boolean hasNext() { 28 return nextNode != null; 29 } 30 31 public T next() { 32 if(hasNext()){ 33 Node returnNode = nextNode; 34 nextNode = nextNode.next;//其实迭代器的实现方法就是按照遍历链表的方式 来实现的 35 return returnNode.data; 36 } 37 else 38 throw new NoSuchElementException("Illegal call to next();" + 39 "iteration is after end of list."); 40 } 41 42 //由于Iterator<T>接口中定义了remove()方法,由于 implements ,这里必须实现remove() 43 //但这里并没有真正支持在迭代器操作下对单链表进行删除,而是抛异常 44 public void remove() { 45 throw new UnsupportedOperationException("remove() is not "+ 46 "supported by this iterator"); 47 } 48 } 49 50 public Iterator<T> getIterator(){ 51 return new IteratorForLinkedList(); 52 } 53 54 private Node header;//保存链表的头结点 55 private Node tail;//保存链表的尾结点,采用尾插法添加结点时,不需要遍历整个链表 56 private int size;//保存链表中已包含的节点数 57 58 public LinkListWithIterator(){ 59 header = tail = null;//从构造器可以看出,些链表是一个不带表头结点的单链表 60 } 61 public LinkListWithIterator(T element){ 62 header = new Node(element, null); 63 tail = header; 64 size++; 65 } 66 67 public int length(){ 68 return size; 69 } 70 71 //获得索引为index处的元素值,注意:索引从0开始 72 public T get(int index){ 73 return getNodeByIndex(index).data; 74 } 75 private Node getNodeByIndex(int index){ 76 if(index < 0 || index > size - 1) 77 throw new IndexOutOfBoundsException("单链表越界"); 78 Node current = header; 79 for(int i = 0; (i < size) && (current != null); i++, current = current.next) 80 if(i == index) 81 return current; 82 return null; 83 } 84 85 //返回链表中指定元素的索引,若指定元素不存在,返回-1 86 public int locate(T element){ 87 Node current = header; 88 for(int i = 0; i < size && current != null; i++, current = current.next) 89 if(current.data.equals(element)) 90 return i; 91 return -1; 92 } 93 94 public void insert(T element, int index){ 95 if(index < 0 || index > size) 96 throw new IndexOutOfBoundsException("单链表索引越界"); 97 if(header == null)//链表是空链表时 98 add(element); 99 else{ 100 if(index == 0)//在表头插入 101 addAtHeader(element); 102 else{ 103 Node prev = getNodeByIndex(index - 1);//获取插入结点的前驱 104 prev.next = new Node(element, prev.next); 105 size++; 106 } 107 } 108 } 109 110 //采用尾插法为链表增加新节点 111 public void add(T element){ 112 if(header == null){ 113 header = new Node(element, null); 114 tail = header; 115 } 116 else{ 117 Node newNode = new Node(element, null); 118 tail.next = newNode; 119 tail = newNode; 120 } 121 size++; 122 } 123 124 //采用头插法为链表增加新节点 125 public void addAtHeader(T element){ 126 header = new Node(element, header);//新建的结点的next 需要指向 header结点 127 if(tail == header)//如果插入之前是空链表 128 tail = header; 129 size++; 130 } 131 132 public T delete(int index){ 133 if(index < 0 || index > size - 1) 134 throw new IndexOutOfBoundsException("链表索引越界"); 135 Node del; 136 //待删除的是header节点 137 if(index == 0){ 138 del = header; 139 header = header.next; 140 } 141 else{ 142 Node prev = getNodeByIndex(index - 1);//获取待删节点的前驱 143 del = prev.next;//del 指向待删除的节点 144 prev.next = del.next; 145 } 146 del.next = null;//将待删节点从链表中脱离出去 147 size--; 148 return del.data; 149 } 150 151 //根据指定的元素来删除节点 152 public boolean deleteByElement(T element){ 153 //链表为空 154 if(empty()){ 155 return false; 156 } 157 //待删元素为第一个元素 158 else if(header.data.equals(element)){ 159 Node del = header; 160 header = header.next; 161 if(tail == header)//说明整个链表中只有一个元素,tail也应当置null 162 tail = null; 163 del.next = null; 164 size --; 165 return true; 166 } 167 //待删元素为链表中其他元素 168 else{ 169 Node del = header.next; 170 Node prev = header; 171 while(del != null){ 172 if(del.data.equals(element)){ 173 if(tail == del)//如果待删元素是最后一个元素,需要将tail指针指向其前驱 174 tail = prev; 175 prev.next = del.next; 176 del.next = null; 177 size--; 178 return true; 179 } 180 //若没有找到element,则继续下一轮的循环 181 prev = del; 182 del = del.next; 183 } 184 return false; 185 } 186 } 187 188 //删除链表中的最后一个元素 189 public T remove(){ 190 return delete(size - 1); 191 } 192 193 //判断链表是否为空 194 public boolean empty(){ 195 boolean result; 196 if(size == 0){ 197 assert header == null;//当出现链表为空,但size不为0时,使用断言能够帮助找到逻辑错误 198 result = true; 199 } 200 else{ 201 assert header != null; 202 result = false; 203 } 204 return result; 205 //return size == 0; 206 } 207 208 //清空链表 209 public void clear(){ 210 header = tail = null; 211 size = 0; 212 } 213 214 public String toString(){ 215 if(empty()) 216 return "[]"; 217 else{ 218 StringBuilder sb = new StringBuilder("["); 219 for(Node current = header; current != null; current = current.next) 220 sb.append(current.data.toString() + ", "); 221 int len = sb.length(); 222 //注意删除最后添加的两个多余的字符 223 return sb.delete(len - 2, len).append("]").toString(); 224 } 225 } 226 }
最后,该程序可以作为 不带表头结点的链表 如何进行插入(头插法,尾插法、指定位置进行插入)、删除……一些基本操作的参考。