1、常用方法
代码示例:
package test5_LinkedList;
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
import java.util.Iterator;
import java.util.LinkedList;
/**
* @Auther: zhoulz
* @Description: test5_LinkedList
* @version: 1.0
*/
public class Test1 {
public static void main(String[] args) {
/*
LinkedList常用方法:(这里介绍其特有的,常见的如add等就不介绍了)
增加:addFirst(E e)、addLast(E e)
offer(E e)、offerFirst(E e)、offerLast(E e)
删除:poll()
pollFirst()、pollLast() —— JDK1.6以后新出的方法,提高了代码的健壮性
removeFirst()、removeLast() —— JDK1.0就有了
修改:
查看:element()
getFirst()、getLast()
indexOf(Object o)、lastIndexOf(Object o)
peek()、peekFirst()、peekLast()
判断:
*/
//创建一个LinkedList集合对象:
LinkedList<String> list = new LinkedList<>();
//点进去发现是LinkedList<E>,即泛型类,
// 而我们推荐泛型类在创建实例的时候把泛型加上
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("aaa"); //验证能不能添加相同的元素,结果是可以的
System.out.println(list);
//1、增加方法:
//首尾添加元素:
list.addFirst("qqq");
list.addLast("www");
System.out.println(list);
list.offer("eee");//添加元素在尾端
System.out.println(list);
list.offerFirst("oo");
list.offerLast("pp");
System.out.println(list);
System.out.println("删除方法-------");
//2、删除方法:
list.poll(); //检索并删除此列表的头(第一个元素)
System.out.println(list);
//或直接:
System.out.println(list.poll()); //还会把删除的元素进行返还
System.out.println(list);
//检索并删除此列表的 第一个/最后一个 元素,如果此列表为空,则返回 null 。
System.out.println(list.pollFirst());
System.out.println(list.pollLast());
System.out.println(list);
//删除—同上:
System.out.println(list.removeFirst());
System.out.println(list.removeLast());
System.out.println(list);
//疑惑:
//同样是删除(后面的查看系列方法-同理),有什么区别吗?
//list.clear();//清空集合
//System.out.println(list);
//System.out.println(list.pollFirst());//空集合删除返回null
//System.out.println(list.removeFirst()); //空集合删除会报错/异常
//后面的查看系列方法——类似,不在举例
//集合的遍历:
System.out.println("遍历-----");
System.out.println(list);
//1、普通for循环 —— 可以的,有一个get()方法
System.out.println("普通for循环——遍历:");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//2、增强for循环
System.out.println("增强for循环——遍历:");
for (String s : list){
System.out.println(s);
}
//3、迭代器-iterator
System.out.println("迭代器-iterator —— 遍历:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//关于迭代器,还有一种写法:
//下面这种方式好,节省内存:it 作用域
System.out.println("另一种写法—— 遍历");
for (Iterator<String> it = list.iterator();it.hasNext();){
System.out.println(it.next());
}
}
}
2、LinkedList简要底层原理图
3、模拟LinkedList源码
代码示例:
先创建节点类:Node
package test6_LinkedList_simulation;
/**
* @Auther: zhoulz
* @Description: test5_LinkedList
* @version: 1.0
*/
public class Node { //创建节点类
//三个属性 —— 不确定什么类型,所以就用Object
//1、上一个元素的地址:
//Object pre;
//由于上一个节点也是Node类型,故就用Node
private Node pre;
//2、当前存入的元素:
private Object obj;
//3、下一个元素的地址:
//Object next;
private Node next;
//可以加个修饰符private,但是同时得加上set、get方法
//set、get方法
public Node getPre() {
return pre;
}
public void setPre(Node pre) {
this.pre = pre;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
//再加上一个toString()方法
@Override
public String toString() {
return "Node{" +
"pre=" + pre +
", obj=" + obj +
", next=" + next +
'}';
}
}
再创建自己的 LinkedList 集合:
package test6_LinkedList_simulation;
/**
* @Auther: zhoulz
* @Description: test6_LinkedList_simulation
* @version: 1.0
*/
/**
创建自己的 LinkedList 集合 —— 规范注释!!!!
*/
public class MyLinkedList {
//里面有一个个节点Node
/*Node a;
Node b;
Node c;*/
//但是如果节点太多,还这么创建的话,就不友好
//链中一定有一个首节点:
Node first;
//链中一定有一个尾节点:
Node last;
//计数器:
int count = 0;
//提供一个构造器:
public MyLinkedList(){
}
//定义方法
//1、添加元素的方法
public void add(Object o){ //不知道什么类型,所以用Object
if(first == null){//如果你添加的元素是第一个节点
//将添加的元素封装为一个Node对象
Node n = new Node();
n.setPre(null); //没有上一个元素
n.setObj(o); //设置当前元素
n.setNext(null); //下一个元素也没有
//即,只有这一个节点
//当前链中,第一个节点变为n
first = n;
//当前链中最后一个节点也是n
last = n;
}else {//如果已经不是链中第一个节点了
//首先还是的创建一个Node对象:
//将添加的元素封装为一个Node对象
Node n = new Node();
n.setPre(last);//上一个元素就是当前链的最后一个元素
n.setObj(o);
n.setNext(null);
//现在,这个元素已经指向了上一个元素
//但是,还要加一个操作:
// 就是让上一个元素(即:当前链(添加前的链)中的最后一个节点)指向该元素n
//即:当前链(添加前的链)中的最后一个节点的下一个元素 要指向n
last.setNext(n);
//然后,将最后一个节点变为n
last = n;
}
//链中元素数量加1
count++;
}
//2、得到集合中元素的数量的方法:
public int getSize(){
return count;
}
//3、通过下标得到元素
public Object get(int index){
//首先,获取链表的头元素:
Node n = first;
//一路next得到想要的元素
for (int i = 0; i < index; i++) {
n = n.getNext();//一直循环,会将n置为当前的节点
}
//再将这个节点的元素(中间那个元素)获取到
return n.getObj();
}
}
//测试类
class Test{
public static void main(String[] args) {
//创建一个MyLinkedList集合对象
MyLinkedList ml = new MyLinkedList();
//只创建对象还不行,因为集合里面还什么都没有
// 所以,还要往里添加元素
//记住,添加操作之前,要在集合里面先模拟,如:add这个动作/方法
ml.add("aa");
ml.add("bb");
ml.add("cc");
System.out.println(ml.getSize());
//获取元素
System.out.println(ml.get(1));
System.out.println(ml.get(2));
}
}
debug验证数据添加成功:
4、LinkedList源码解析
【1】JDK1.7和JDK1.8的LinkedList的源码是一致的
【2】源码:
1.public class LinkedList<E>{//E是一个泛型,具体的类型要在实例化的时候才会最终确定
2. transient int size = 0;//集合中元素的数量
3. //Node的内部类
4. private static class Node<E> {
5. E item;//当前元素
6. Node<E> next;//指向下一个元素地址
7. Node<E> prev;//上一个元素地址
8.
9. Node(Node<E> prev, E element, Node<E> next) {
10. this.item = element;
11. this.next = next;
12. this.prev = prev;
13. }
14. }
15.
16. transient Node<E> first;//链表的首节点
17. transient Node<E> last;//链表的尾节点
18. //空构造器:
19. public LinkedList() {
20. }
21. //添加元素操作:
22. public boolean add(E e) {
23. linkLast(e);
24. return true;
25. }
26. void linkLast(E e) {//添加的元素e
27. final Node<E> l = last;//将链表中的last节点给l 如果是第一个元素的话 l为null
28. //将元素封装为一个Node具体的对象:
29. final Node<E> newNode = new Node<>(l, e, null);
30. //将链表的last节点指向新的创建的对象:
31. last = newNode;
32.
33. if (l == null)//如果添加的是第一个节点
34. first = newNode;//将链表的first节点指向为新节点
35. else//如果添加的不是第一个节点
36. l.next = newNode;//将l的下一个指向为新的节点
37. size++;//集合中元素数量加1操作
38. modCount++;
39. }
40. //获取集合中元素数量
41. public int size() {
42. return size;
43. }
44. //通过索引得到元素:
45. public E get(int index) {
46. checkElementIndex(index);//健壮性考虑
47. return node(index).item;
48. }
49.
50. Node<E> node(int index) {
51. //如果index在链表的前半段,那么从前往后找
53. if (index < (size >> 1)) {
54. Node<E> x = first;
55. for (int i = 0; i < index; i++)
56. x = x.next;
57. return x;
58. } else {//如果index在链表的后半段,那么从后往前找
59. Node<E> x = last;
60. for (int i = size - 1; i > index; i--)
61. x = x.prev;
62. return x;
63. }
64. }
66.}
Collection接口持续扩充:子接口List下的三个实现类:ArrayList、LinkedList、Vector。
(其中,常用的为ArrayList、LinkedList实现类,Vector已经被淘汰了。)
其中,List接口下的实现类的遍历方式有3种:
分别为:1)普通for循环; 2)增强for循环; 3)迭代器 iterator
下面再重点讲一下 迭代器 。
(以一个面试提展开)
5、面试题:iterator(), Iterator, Iterable 关系
【1】面试题:对应的关系
发现,在接口Iterable中,有一个抽象方法:iterator()方法,而抽象方法要在具体的实现类中的得到实现。又发现在迭代器遍历(即:ArrayList底层)的时候用到/实现了这个方法.
(接口里面的方法都是抽象方法,但是在JDK1.8以后,添加了default修饰的非抽象方法。)
即:接口 Iterable中的抽象方法 iterator() 在底层 ArrayList 实现类中得到了实现。
并且,iterator()方法的返回值为 Iterator,点进去发现又是一个接口:
在这个 Iterator接口里面,有两个经典方法:
hasNext()、next(),这两个方法也是抽象方法。
那这两个方法抽象方法在哪实现的呢?(在Itr这个类中得到了具体的实现)
见上图,iterator()方法返回的(new Itr() 就是具体的接口实现类,然后用返回值Iterator接口去接收(多态的一个小的应用)
Itr 点进去发现, Itr这个类是ArrayList的内部类。
其中,Itr implements Iterator<E>:即 Itr 实现了Iterator,并且在其下,把hasNext()、next()、remove()等方法都重写了一下(这两个方法原来是在 Iterator接口里面的)。
【2】hasNext()、next()的具体实现
说明:
ArrayList<E> 类下,有两个重要的属性:
//对应底层一个数组
transient Object[] elementData; // non-private to simplify nested class access
private int size;
【3】增强for循环 底层也是通过迭代器实现的
下面讲一个新的迭代器:
6、ListIterator迭代器
【1】加入字符串
代码示例:
package test8_ListIterator;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @Auther: zhoulz
* @Description: test8_ListIterator
* @version: 1.0
*/
public class Test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
//首先是对集合进行遍历:
Iterator<String> it = list.iterator();
while (it.hasNext()){
//System.out.println(it.next());
//将输入的结果和“cc”进行比较
//如果遍历出来的字符串刚好和cc相等,
if ("cc".equals(it.next())){
list.add("kk"); //则在list后添加 kk
}
} //报错了!!!
}
}
发现报错:ConcurrentModificationException —— 这是并发修改异常。
出错原因:就是迭代器和 list 同时对集合进行操作:
解决办法:事情让一个“人”做 --》引入新的迭代器:ListIterator
迭代和添加操作都是靠ListIterator来完成的:
代码示例:
package test8_ListIterator;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**
* @Auther: zhoulz
* @Description: test8_ListIterator
* @version: 1.0
*/
public class Test1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
//首先是对集合进行遍历:
/*Iterator<String> it = list.iterator();
while (it.hasNext()){
//System.out.println(it.next());//遍历
//将遍历的结果和“cc”进行比较
//如果遍历出来的字符串刚好和cc相等,
if ("cc".equals(it.next())){
list.add("kk"); //则在list后添加 kk
} // 报错了
}*/
//解决:
//引入新的迭代器:ListIterator
ListIterator<String> it = list.listIterator();
while (it.hasNext()){
//System.out.println(it.next());//将输入的结果和“cc”进行比较
if ("cc".equals(it.next())){
it.add("kk"); //注意,不是list.add("kk");了
//通过it,即迭代器进行添加,上面的遍历也是迭代器进行的
}
}
System.out.println(list);
//这个迭代器的高级之处在于:除了可以判断/查看下一个元素
System.out.println(it.hasNext());//遍历到底了,就没有下一个元素了
//结果:false
//还可以:
//判断/查看上一个元素
System.out.println(it.hasPrevious());//但是还有上一个元素
//结果:true
//所以可以做一个
// 逆向遍历:
while (it.hasPrevious()){
System.out.println(it.previous());
}
//逆向遍历后(遍历到了数组的最上面),再查看:
//应该是:没有上一个元素了,但是还有下一个元素
System.out.println(it.hasNext());//true
System.out.println(it.hasPrevious());//false
}
}
即:现在有两个迭代器:iterator() 和 listIterator() 。