Java实现单向链表(基于Collection扩展)
1 新增需求
前面我们已经实现了一个简单的单向链表,虽然从功能上来说,它可以满足一个单向链表的基本需求,但如果放在整个Java体系中,这样的链表是无法融入的。在Java体系中,链表属于Collection集合体系下的集合容器(并且Collection的实现类已有实现),我们必须要让自定义链表实现Collection接口。
2 新增功能
新增的功能主要是对Collection接口中定义的方法进行实现,在之前的单向链表中,我们已经实现了部分方法,这样可以减少一定的工作量。Collection的方法如表1所示。
类型 | 方法名称 | 方法描述 |
---|---|---|
boolean | add(E e) √ | 向集合添加元素,返回添加结果。 |
boolean | addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合中。 |
void | clear() | 从该集合中删除所有元素。 |
boolean | contains(Object o)√ | 判断此集合中是否包含指定的元素。 |
boolean | containsAll(Collection<?> c) | 判断此集合包含指定集合中的所有元素。 |
boolean | equals(Object o) | 将指定对象与此集合进行比较以获得相等性。 |
int | hashCode() | 返回此集合的哈希码值。 |
boolean | isEmpty() | 如果此集合不包含任何元素,则返回true。 |
Iterator | iterator() | 返回此集合中元素的迭代器。 |
boolean | remove(Object o) | 从该集合中删除指定元素的单个实例(如果存在)。 |
boolean | removeAll(Collection<?> c) | 删除指定集合中包含的所有此集合的元素。 |
boolean | retainAll(Collection<?> c) | 仅保留指定集合中包含的此集合中的元素。 |
int | size()√ | 返回此集合中元素的数量。 |
Object[] | toArray() | 返回包含此集合中所有元素的数组。 |
T[] | toArray(T[] a) | 返回包含此集合中所有元素的数组; 返回数组的运行时类型是指定数组的运行时类型。 |
对比我们已经实现的方法和Collection接口中的方法,其中三个方法是已经实现了(√号标识的方法),剩余方法是还需要我们进行实现的。
Collection接口中的15个方法,3个方法我们已经实现,hashCode和equals方法我们可以不用实现(借用Object类的该方法),其余的10个方法我们总结如下:
- boolean addAll(Collection<? extends E> c)
- booleancontainsAll(Collection<?> c)
- boolean removeAll(Collection<?> c)
- boolean retainAll(Collection<?> c)
这四个方法的参数都是一个Collection集合,在操作参数对象时,我们势必要将集合中的元素逐一遍历出来,Collection集合能够遍历的基础在于迭代器Iterator的使用。除了containsAll方法外,其它三个方法还需要对链表的长度成员变量(size)进行更新。
- Iterator iterator():iterator方法要返回一个Iterator对象,我们可以在该方法中用内部类的方式实现一个Iterator接口,实现该接口需要重写方法hasNext()和next(),hasNext方法返回一个布尔值,用于表示是否有下一个元素需要遍历。next方法返回下一个元素对象。
iterator()是Collection接口中非常重要的方法,即迭代器接口。我们在迭代集合的时候,通常是通过它来迭代的。在Collection的其它方法中,只要涉及到两个集合的相互操作(合并、删除、查找)都会依赖Iterator迭代器来完成。
- void clear():清空链表,同时注意头、尾节点以及size的重置。
- boolean remove(Object o):该方法和基础链表中的remove方法形成重载,但该方法可以将链表中的所有指定实例o删除。
- boolean equals(Object o):建议直接使用Object的equals方法。如果不熟悉equals和hashCode的重写规则,建议不重写该方法。
- int hashCode():建议直接使用Object的hashCode方法。
- boolean isEmpty():如果size为0,返回true。
- Object[] toArray():将链表转化成数组,根据size大小创建同等大小的Object数组,通过遍历的方式,将链表中的元素放入数组中。最后返回数组。
- < T > T[] toArray(T[] a):将链表元素放到泛型数组a中,注意这里面的泛型标识符T与定义的泛型标识符不是同一个类型,在转化的过程中需要转型。提供的泛型数组长度可能与链表的长度一致,如果a的长度小,则截取一部分链表元素放入a中,如果a的长度过长,则a多余部分用null代替。
2.1 迭代功能实现分析
迭代器Iterator的实现主要实现hasNext和next方法,迭代器的最低实现要求是能够将链表中的所有节点数据遍历出来。
hasNext方法可以判断迭代器是否有下一个要迭代的节点。当首次迭代的时候,下一个被迭代的元素应该是链表的头节点。所以我们可以虚设一个节点node,有node.next=head这一关系。next方法返回下一个迭代的元素,它的隐藏要求是,迭代器的迭代节点要向后移动一个。实现这两个方法就可以最低配的实现一个链表迭代器了。
2.2 删除指定元素remove(Object o)
remove(Object o)方法可以删除链表中指定元素o,以下我们简称为目标元素。该方法我们的要求是要将链表中所有的目标元素,这可能会删除链表中多个元素。
要达到这个效果的算法的方式有多种,我们将采用的算法是通过一次遍历,将链表中的所有目标元素删除,大致算法如下图所示。
在删除元素时,如果被删除的元素是链表头节点,那么我们直接让头节点后移,并将原有的头节点清空。
2.3 保留目标集合所有元素
retainAll(Collection<?> c)链表中保留的元素必须是集合c中存在的元素。这个方法最简单的实现过程是通过遍历一次链表,将链表中的每个元素提取出来与集合c中的元素进行比对,如果集合c中存在该元素(可使用contains方法是进行判断),链表中的该节点保留,否则删除链表中的该元素。为了能够彻底的删除链表中所有的相同元素,我们可以调用remove(Object o)方法进行删除,该方法的实现思路如下图所示。
另一个方法removeAll与retainAll是极为相似的,当迭代的链表节点元素在目标集合c中,将该元素从链表中删除。
2.4 hashCode和equals
这两个方法,我们可以暂时不考虑实现,直接借用Object的方法来实现。如果非要实现这两个方法,我们需要注意集合类在实现这两个方法的时候与普通类有些不同。
1.大多数的集合类中的hashCode并不是唯一的(如List),它们的hash值是由集合元素所决定的,而普通类中的hashCode值应该始终保证唯一。
2.集合类的hashCode值与equals的结果与集合中的元素高度相关,这一点不止体现在集合类中,String和Integer等封装类也是如此,在阅读它们的源码时,可以发现它们的hashCode值是由内部重要元素(变量)计算得到的。不同之处在于String等对象的值发生变化时,引用也随之变化,这一点与集合类还是不同的。
3 详细设计
扩展后的链表命名为LinkCollection,除去原有基础链表(Link)功能外,还对接口Collection进行实现。在LinkCollection的内部,也拥有一个Node内部类,它与基本链表中的结构是一样的,本文我们忽略它的介绍。另一个内部类LinkIterator,它是迭代器的重要实现,我们将会在iterator方法中以局部内部类的方式进行创建。
3.1 LinkIterator内部类
LinkIterator具体声明格式为"class LinkIterator implements Iterator< T >",它是创建在iterator方法中的内部类,仅在iterator方法中使用。
LinkIterator包含一个成员变量"private Node iterNode",该变量是用来链接链表头节点的一个临时结点。初始值为"new Node(null, head)",其中"head"为链表的头节点。Iterator接口的两个覆盖方法实现过程如下:
1. public boolean hasNext():当iterNode.next不为空时,返回true,否则返回false。
2. punblic T next():让iterNode节点后移,返回移动后节点的元素内容。
3.2 Collection接口方法的实现
1.public Iterator iterator():创建LinkIterator对象实例并返回。
2.public boolean addAll(Collection<? extends T> c)
- 方法描述:使用迭代器遍历集合c,将集合c中的元素加入到当前链表中。
- 实现过程:1.先判断目标集合是否为null,为null返回false。2.目标集合和当前链表的长度之和是否大于链表的最大长度,不符合要求返回false。3.通过迭代目标集合c的方式,将集合c中的元素逐一添加到链表中,添加结束后返回true。
3.public void clear():将链表清空,重置size,head,last为初始值。
4.public boolean containsAll(Collection<?> c):
- 方法描述:判断当前链表是否包含指定集合c中的所有元素。
- 实现过程:1.判断目标集合是否null,为null返回false。2.用迭代器遍历目标集合c,将迭代的元素放入链表中查找(contains方法查找),如果没有找到返回false。3.迭代结束后,方法返回true。
5.public boolean isEmpty():当size等于0时返回true,否则返回false。
6.public boolean remove(Object o)
- 方法描述:删除链表中所有与目标元素o相等的元素。
- 实现过程:1.遍历链表中的每一个节点,遍历节点为node,注意在遍历的过程中,需要定义preNode记录node上一个节点信息。2.当node节点元素与o不相同时,node节点后移(preNode也要更新),继续迭代循环;如果node节点与元素o相同,删除该节点(如果该节点是头节点,更新头结点信息,如果该节点是尾节点,更新尾节点信息),node和preNode节点更新,继续循环遍历。3.如果有删除过节点,遍历结束后,返回true否则返回false。
7.public boolean removeAll(Collection<?> c)
- 方法描述:删除链表中所有包含在目标集合c中的元素。
- 实现过程:1.判断c是否为null,为null返回false。2.使用迭代器迭代目标集合c,从链表中删除迭代的元素,如果出现删除失败,返回false。3.迭代完成后,返回true。
8.public boolean retainAll(Collection<?> c)
- 方法描述:链表中只保留在集合c中的元素。
- 实现过程:1.判断c是否为null,为null返回false。2.遍历链表,如果遍历的节点元素不在目标集合c中,则链表删除该元素。3.遍历结束后,返回true。
9. public Object[] toArray()
- 方法描述:将链表各节点元素转化成Object数组对象。
- 实现过程:根据链表长度size,创建等长大小Object数组。遍历链表将,链表中各元素依次放入Object数组中,最后返回该数组。
10. public < E > E[ ] toArray(E[ ] a)
- 方法描述:提供一个泛型数组E,将链表元素转化成类型E,并返回该数组。
- 实现过程:这个方法中的泛型与链表的泛型是不一致的,由于泛型数组无法实例化,我们可以将链表中的元素迭代出来,依次转化后放入目标数组a中,如果数组a的长度小于链表长度,只返回a长度小大的数组,如果数组a的长度大于链表长度,则数组多余部分进行null处理。
3.2代码实现
01. import java.util.Collection;
02. import java.util.Iterator;
03.
04. public class LinkCollection<T> implements Collection<T>{
05.
06. private int size=0; //链表长度
07. private Node<T> head=null;//链表的头节点
08. private Node<T> last=null;//链表的头节点
09.
10. //链表最大长度,链表长度size最大值为MAX_SIZE,是在链表中添加元素时必须判断的条件。
11. private final int MAX_SIZE=2<<10;
12.
13. //链表中的节点类
14. static class Node<T> {
15. //链表中的节点元素
16. T item;
17. //下一个节点的地址
18. Node<T> next;
19.
20. Node(T item,Node<T> next){
21. this.item=item;
22. this.next=next;
23. }
24. }
25.
26. public boolean add(T obj){
27.
28. if(size>=MAX_SIZE) return false;
29. Node<T> newNode=new Node<T>(obj, null);
30. if(size==0) {
31. //如果为头节点,首末节点等于新增节点
32. head=newNode;
33. last=newNode;
34. }else {
35. //将节点添加至尾部
36. last.next=newNode;
37. //尾节点为新节点
38. last=newNode;
39. }
40.
41. size++;
42. return true;
43. }
44.
45. public boolean add(int index,T obj) {
46.
47. if(index>=size || index<0 || size>MAX_SIZE-1) return false;
48. Node<T> newNode=new Node<T>(obj, null);
49. //如果是头结点
50. if(index==0) {
51. newNode.next=head;
52. head=newNode;
53. size++;
54. return true;
55. }
56.
57. Node<T> node=head;//index索引处节点
58. Node<T> preNode=null;//index索引之前的节点
59. //遍历节点,node为指定序列index的链表节点,preNode为前一个节点
60. for(int i=0;i<index;i++) {
61. preNode=node;
62. node=node.next;
63. }
64. //更新节点间的关系
65. preNode.next=newNode;
66. newNode.next=node;
67.
68. size++;
69. return true;
70. }
71.
72.
73. public boolean contains(Object obj) {
74.
75. Node<T> start=head;
76. while(start!=null) {
77. //当节点对象与obj的equals相等时,则找到对象
78. if(start.item.equals(obj)) return true;
79. //节点后移
80. start=start.next;
81. }
82.
83. return false;
84. }
85.
86.
87. public T get(int index) {
88.
89. if(index<0 || index>=size) return null;
90. //从头部节点遍历到指定索引节点
91. Node<T> node=head;
92. for(int i=0;i<index;i++) {
93. node=head.next;
94. }
95.
96. return node.item;
97. }
98.
99. public T getHead() {
100. return head==null?null:head.item;
101. }
102.
103. public T getLast() {
104. return last==null?null:last.item;
105. }
106.
107. public boolean remove(int index) {
108.
109. if(index>=size || index<0) return false;
110.
111. Node<T> removeNode=head;//被删除节点,初始值为头结点
112. Node<T> preNode=null;//被删除节点的上一个节点
113. //如果是头节点,将头节点后移。
114. if(index==0) {
115. this.head=head.next;
116. removeNode=null;
117. size--;
118. return true;
119. }
120. //移动到指定索引节点处。
121. for(int i=0;i<index;i++) {
122. preNode=removeNode;
123. removeNode=removeNode.next;
124. }
125. //如果被删除节点是尾节点
126. if(removeNode.next==null) {
127. last=preNode;
128. preNode.next=null;
129. removeNode=null;
130. }else {
131. preNode.next=removeNode.next;
132. removeNode=null;
133. }
134. size--;
135. return true;
136. }
137.
138. public int size() {
139. return this.size;
140. }
141.
142. public String toString() {
143. StringBuilder strs=new StringBuilder();
144. Node<T> start=head;
145. while(start!=null) {
146. strs.append(start.item).append(",");
147. start=start.next;
148. }
149.
150. return strs.toString();
151. }
152.
153. @Override
154. public boolean isEmpty() {
155. return size==0?true:false;
156. }
157.
158. @Override
159. public Iterator<T> iterator() {
160.
161. class LinkIterator implements Iterator<T>{
162. //相当于在链表的头结点前加一个临时结点
163. private Node<T> iterNode=new Node<T>(null, head);
164. @Override
165. public boolean hasNext() {
166. return iterNode.next==null?false:true;
167. }
168.
169. @Override
170. public T next() {
171. iterNode=iterNode.next;
172. return iterNode.item;
173. }
174. }
175.
176. return new LinkIterator();
177. }
178.
179. @Override
180. public Object[] toArray() {
181. if(size==0) return null;
182. Object[] array=new Object[size];
183. Iterator<T> it=this.iterator();
184. for(int i=0;i<size;i++) {
185. array[i]=it.next();
186. }
187.
188. return array;
189. }
190.
191. @SuppressWarnings("unchecked")
192. @Override
193. public <E> E[] toArray(E[] a) {
194. E[] array=a;
195. Object[] src=this.toArray();
196. for(int i=0;i<array.length;i++) {
197. if(i<src.length) {
198. array[i]=(E) src[i];
199. }else {
200. array[i]=null;
201. }
202. }
203.
204. return array;
205. }
206.
207. @Override
208. public boolean remove(Object o) {
209. Node<T> preNode=null;
210. Node<T> node=head;
211. boolean isRemove=false; //是否删除过元素标识
212. while(node!=null) {
213. T ele=node.item;
214. if(ele.equals(o)) {
215. //如果被移除节点是头结点,头结点下移
216. if(node.equals(head)) {
217. head=head.next;
218. }else {
219. preNode.next=node.next;
220. }
221. node=node.next;
222. //被移除节点是尾结点
223. if(node==null) {
224. last=preNode;
225. }
226. size--;
227. isRemove=true;
228. }else {
229. preNode=node;
230. node=node.next;
231. }
232. }
233. return isRemove;
234. }
235.
236. @Override
237. public boolean containsAll(Collection<?> c) {
238. Iterator<?> it=c.iterator();
239. while(it.hasNext()) {
240. Object collectEle=it.next();
241. if(!contains(collectEle)) {
242. return false;
243. }
244. }
245. return true;
246. }
247.
248. @Override
249. public boolean addAll(Collection<? extends T> c) {
250. if(c==null) return false;
251. if(size>MAX_SIZE-c.size()) return false;
252. Iterator<? extends T> it=c.iterator();
253. while(it.hasNext()) {
254. if(!add(it.next())) {
255. return false;
256. }
257. }
258. return true;
259. }
260.
261. @Override
262. public boolean removeAll(Collection<?> c) {
263. if(c==null) return false;
264. Iterator<?> it=c.iterator();
265. while(it.hasNext()) {
266. Object collectEle=it.next();
267. if(contains(collectEle)) {
268. remove(collectEle);
269. }
270. }
271. return true;
272. }
273.
274. @Override
275. public boolean retainAll(Collection<?> c) {
276. if(c==null) return false;
277. Node<T> node=head;
278. while(node!=null) {
279. if(!c.contains(node.item)) {
280. remove(node.item);
281. }
282. node=node.next;
283. }
284.
285. return true;
286. }
287.
288. @Override
289. public void clear() {
290. size=0;
291. head=last=null;
292. }
293. }
4 链表的测试用例
测试用例中,使用的初始化链表数据方法和链表信息输出(链表的头结点、尾节点、链表长度信息的输出)方法分别如下:
初始化链表方法:
01. public static Link<Integer> createLink(int size){
02. Link<Integer> link=new Link<>();
03. for(int i=0;i<size;i++) {
04. link.add(i);
05. }
06.
07. return link;
08. }
链表信息输出方法:
01. public static void showLinkMessage(Link<?> link) {
02. System.out.println("head="+link.getHead()
03. +",last="+link.getLast()
04. +",size="+link.size());
05. System.out.println("link="+link);
06. }
1.addAll方法测试用例内容:正常添加集合,观察链表中元素前后的变化。
01. public static void addAllTest() {
02. Link<Integer> link=createLink(10);
03. ArrayList<Integer> list=new ArrayList<>();
04. list.add(100);
05. list.add(200);
06. list.add(300);
07. showLinkMessage(link);
08. System.out.println("addAll result="+link.addAll(list));
09. showLinkMessage(link);
10. }
用例运行结果:
head=0,last=9,size=10
link=0,1,2,3,4,5,6,7,8,9
addAll result=true
head=0,last=300,size=13
link=0,1,2,3,4,5,6,7,8,9,100,200,300
2.addAll测试用例内容:添加集合,当元素超量时,观察链表的操作反馈。
01. public static void addAllTest2() {
02. Link<Integer> link=createLink(2<<10);
03. ArrayList<Integer> list=new ArrayList<>();
04. list.add(100);
05. list.add(200);
06. list.add(300);
07. System.out.println(link.size());
08. System.out.println("addAll result="+link.addAll(list));
09. System.out.println(link.size());
10. }
用例运行结果:
2048
addAll result=false
2048
3.addAll测试用例内容:添加空集合时,观察链表的操作反馈。
01. public static void addAllTest3() {
02. Link<Integer> link=createLink(10);
03. ArrayList<Integer> list=null;
04. showLinkMessage(link);
05. System.out.println("addAll result="+link.addAll(list));
06. showLinkMessage(link);
07. }
用例运行结果:
head=0,last=9,size=10
link=0,1,2,3,4,5,6,7,8,9
addAll result=false
head=0,last=9,size=10
link=0,1,2,3,4,5,6,7,8,9
4.clear测试用例内容:有数据链表清空后,观察链表前后的信息。
01. public static void clearTest() {
02. Link<Integer> link=createLink(10);
03. showLinkMessage(link);
04. link.clear();
05. showLinkMessage(link);
06. }
用例运行结果:
head=0,last=9,size=10
link=0,1,2,3,4,5,6,7,8,9,
head=null,last=null,size=0
link=
5. containsAll测试用例内容:集合三种情况下进行测试。1.目标集合全部元素在链表中存在。2.目标集合部分元素在链表中存在。3.目标集合所有元素都不在链表中。分别观察方法的返回结果。
01. public static void containsAllTest() {
02. Link<Integer> link=createLink(10);
03. ArrayList<Integer> list=new ArrayList<>();
04. list.add(1);
05. list.add(2);
06. list.add(9);
07. System.out.println("全部包含在link中:"+link.containsAll(list));
08. list.add(100);
09. System.out.println("部分包含在link中:"+link.containsAll(list));
10. list.clear();
11. list.add(300);
12. list.add(400);
13. System.out.println("不包含在link中:"+link.containsAll(list));
14. }
用例运行结果:
全部包含在link中:true
部分包含在link中:false
不包含在link中:false
6. isEmpty方法测试用例:集合三种情况下进行测试。1.新链表,无数据时进行该方法测试。2.将链表清空后,再使用该方法测试。
01. public static void isEmptyTest() {
02. LinkCollection<Integer> link=new LinkCollection<>();
03. System.out.println(link.isEmpty());
04. link=createLink(10);
05. System.out.println(link.isEmpty());
06. link.clear();
07. System.out.println(link.isEmpty());
08. }
用例运行结果:
true
false
true
7. removeObject方法测试用例:集合三种情况测试。1.删除头节点和重复元素。2.删除尾节点。3删除任意不重复节点。观察链表变化。
01. public static void removeTest() {
02. LinkCollection<Integer> link=new LinkCollection<Integer>();
03. for(int i=0;i<20;i++) {
04. if(i%5==0) {
05. link.add(5);
06. }
07. link.add(i);
08. }
09. showLink(link);
10. System.out.println("删除5:"+link.remove(new Integer(5)));
11. showLink(link);
12. System.out.println("删除19:"+link.remove(new Integer(19)));
13. showLink(link);
14. System.out.println("删除6:"+link.remove(new Integer(6)));
15. showLink(link);
16. }
用例运行结果:
head=5,last=19,size=24
link=5,0,1,2,3,4,5,5,6,7,8,9,5,10,11,12,13,14,5,15,16,17,18,19,
删除5:true
head=0,last=19,size=19
link=0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
删除19:true
head=0,last=18,size=18
link=0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,
删除6:true
head=0,last=18,size=17
link=0,1,2,3,4,7,8,9,10,11,12,13,14,15,16,17,18,
8.removeAll方法测试用例:集合两种情况测试。1.目标集合中元素部分存在与链表中。2目标集合中元素都不在链表中。
01. public static void removeAllTest() {
02. LinkCollection<Integer> link=new LinkCollection<Integer>();
03. for(int i=0;i<20;i++) {
04. if(i%5==0) {
05. link.add(5);
06. }
07. link.add(i);
08. }
09. ArrayList<Integer> list=new ArrayList<>();
10. list.add(5);
11. list.add(10);
12. list.add(19);
13. list.add(200);
14. showLink(link);
15. System.out.println("删除集合"+link.removeAll(list));
16. showLink(link);
17. System.out.println("删除集合"+link.removeAll(list));
18. showLink(link);
19. }
用例运行结果:
head=5,last=19,size=24
link=5,0,1,2,3,4,5,5,6,7,8,9,5,10,11,12,13,14,5,15,16,17,18,19,
删除集合true
head=0,last=18,size=17
link=0,1,2,3,4,6,7,8,9,11,12,13,14,15,16,17,18,
删除集合true
head=0,last=18,size=17
link=0,1,2,3,4,6,7,8,9,11,12,13,14,15,16,17,18,
9.retainAll方法测试用例:目标集合部分元素在链表中,观察该方法执行后,链表的变化。
01. public static void retainAllTest() {
02. LinkCollection<Integer> link=createLink(20);
03. showLink(link);
04. ArrayList<Integer> list=new ArrayList<>();
05. list.add(5);
06. list.add(10);
07. list.add(19);
08. list.add(200);
09. link.retainAll(list);
10. showLink(link);
11. }
用例运行结果:
head=0,last=19,size=20
link=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
head=5,last=19,size=3
link=5,10,19,
10.toArray方法测试用例:测试返回的Object数组中的数据是否正常。
01. public static void toArrayTest1() {
02. LinkCollection<Integer> link=createLink(20);
03. Object[] array=link.toArray();
04. for(Object o:array) {
05. System.out.print(o+",");
06. }
07. }
用例运行结果:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
11.toArray(E[] a) 方法测试用例:集合三种测试场景。1.提供的泛型数组与实际的链表大小一致。2.提供的泛型数组比实际的链表长。3. 提供的泛型数组比实际的链表小。
01. public static void toArrayTest2() {
02. LinkCollection<Integer> link=createLink(20);
03. Object[] os=new Object[link.size()];
04. os=link.toArray(os);
05. Number[] ns=new Number[10];
06. ns=link.toArray(ns);
07. Integer[] is=new Integer[25];
08. is=link.toArray(is);
09. System.out.println("数组长度一致");
10. for(Object o:os) {
11. System.out.print(o+",");
12. }
13. System.out.println("\n数组长度小");
14. for(Number n:ns) {
15. System.out.print(n+",");
16. }
17. System.out.println("\n数组长度大");
18. for(Integer i:is) {
19. System.out.print(i+",");
20. }
21. }
用例运行结果:
数组长度一致
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
数组长度小
0,1,2,3,4,5,6,7,8,9,
数组长度大
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,null,null,null,null,null,
12.iterator() 测试用例:集合两种测试场景进行测试。1.新链表的迭代测试。2.有数据的链表迭代测试。
01. public static void iteratorTest() {
02. LinkCollection<Integer> link=new LinkCollection<>();
03. Iterator<Integer> it1=link.iterator();
04. System.out.print("新链表遍历=");
05. while(it1.hasNext()) {
06. System.out.print(it1.next()+" ");
07. }
08. System.out.print("\n有数据链表遍历=");
09. link=createLink(10);
10. Iterator<Integer> it2=link.iterator();
11. while(it2.hasNext()) {
12. System.out.print(it2.next()+" ");
13. }
14. }
用例运行结果:
新链表遍历=
有数据链表遍历=0 1 2 3 4 5 6 7 8 9
经过上述基本的功能测试,我们的扩展链表功能基本实现,我们可以继续增加测试用例,来检验我们编写的程序是否可靠。
5 总结
链表是数据结构中非常重要的一种数据存储结构,在Java的Collection体系中,链表已有实现,如LinkedList就是一种经典的双向链表实现。链表在使用上是有一定的背景的,我们需要对它的特点进行一些掌握。
5.1 链表特点
由于单向链表的特殊结构,它的遍历是非常慢的,每一次查找元素都要从链表头节点向尾节点逐一进行查找,如果被查找元素在链表的尾部,几乎需要全链表遍历,所以在单向链表中,我们就要避免对链表进行遍历。而使用链表完成先进先出(队列)和先进后出(栈)的操作。
5.2 设计问题
从上面的测试用例来看,我们似乎已经完成了链表的设计操作。实际上它还有可能存在一些严重的问题没有被找到,这是因为我们的测试用例还不足够覆盖所有使用场景,所以导致一部分问题没有被发现。从现在设计的结构来看,它还存在如下问题。
1.在向链表添加数据的时候,我们没有对被添加数据进行null判断。其实在一些数据结构中允许我们添加null元素,但在一些方法中就需要我们消除一些歧义,例如get(int index)方法,如果提供的索引index超出链表范围,该方法也会返回null,这就会引起歧义(是没有找到,还是找到的元素为null)。这就要我们改变一些设计,例如使用一些非法参数时,我们不使用返回null的方式进行反馈,应该使用抛出异常的方式进行反馈。
2.清空问题,这个问题涉及到了垃圾回收问题,Java中垃圾回收器回收没有被引用的对象。当我们清除链表节点的时候,我们只是简单的将链表节点赋值了null。这样是不全面的,还应该将节点的指针域也进行清空,这样更安全一些。
单向链表的内容我们到此结束,在下一篇中,我们会讲述双向链表的设计,并且对单向链表中遗留的一些问题进行改善。