LinkedList

概述

LinkedList 是 Java 集合框架中一个重要的实现,其底层采用的双向链表结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
transient int size = 0;

/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;

/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;

和 ArrayList 一样,LinkedList 也支持空值和重复值。

由于 LinkedList 基于链表实现,存储元素过程中,无需像 ArrayList 那样进行扩容。但有得必有失,LinkedList 存储元素的节点需要额外的空间存储前驱和后继的引用。另一方面,LinkedList 在链表头部和尾部插入效率比较高,但在指定位置进行插入时,效率一般。原因是,在指定位置插入需要定位到该位置处的节点,此操作的时间复杂度为O(N)

最后,LinkedList 是非线程安全的集合类,并发环境下,多个线程同时操作 LinkedList,会引发不可预知的错误。如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

1
List list=Collections.synchronizedList(new LinkedList(...));

内部结构分析

LinkedList类中的一个内部私有类Node,这个类就代表双端链表的节点Node:

1
2
3
4
5
6
7
8
9
10
11
private static class Node<E> {
E item;//节点值
Node<E> next;//后继节点
Node<E> prev;//前驱节点

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

l1

继承体系

LinkedList的继承体系较为复杂,继承自 AbstractSequentialList,同时又实现了 List 和 Deque 接口。

LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有

队列的特性,我们可以这样使用:

1
Queue<T> queue = new LinkedList<>();

继承体系图如下(删除了部分实现的接口):

l2

LinkedList继承自 AbstractSequentialList ,AbstractSequentialList 又是什么呢?从实现上,AbstractSequentialList提供了一套基于顺序访问的接口。通过继承此类,子类仅需实现部分代码即可拥有完整的一套访问某种序列表(比如链表)的接口。深入源码,AbstractSequentialList 提供的方法基本上都是通过 ListIterator实现的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
protected AbstractSequentialList() {
}

/**
* Returns the element at the specified position in this list.
*
* <p>This implementation first gets a list iterator pointing to the
* indexed element (with <tt>listIterator(index)</tt>). Then, it gets
* the element using <tt>ListIterator.next</tt> and returns it.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}

/**
* Replaces the element at the specified position in this list with the
* specified element (optional operation).
*
* <p>This implementation first gets a list iterator pointing to the
* indexed element (with <tt>listIterator(index)</tt>). Then, it gets
* the current element using <tt>ListIterator.next</tt> and replaces it
* with <tt>ListIterator.set</tt>.
*
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> if the list iterator does not
* implement the <tt>set</tt> operation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
try {
ListIterator<E> e = listIterator(index);
E oldVal = e.next();
e.set(element);
return oldVal;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}

/**
* Inserts the specified element at the specified position in this list
* (optional operation). Shifts the element currently at that position
* (if any) and any subsequent elements to the right (adds one to their
* indices).
*
* <p>This implementation first gets a list iterator pointing to the
* indexed element (with <tt>listIterator(index)</tt>). Then, it
* inserts the specified element with <tt>ListIterator.add</tt>.
*
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> if the list iterator does not
* implement the <tt>add</tt> operation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}

/**
* Removes the element at the specified position in this list (optional
* operation). Shifts any subsequent elements to the left (subtracts one
* from their indices). Returns the element that was removed from the
* list.
*
* <p>This implementation first gets a list iterator pointing to the
* indexed element (with <tt>listIterator(index)</tt>). Then, it removes
* the element with <tt>ListIterator.remove</tt>.
*
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> if the list iterator does not
* implement the <tt>remove</tt> operation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
try {
ListIterator<E> e = listIterator(index);
E outCast = e.next();
e.remove();
return outCast;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}


// Bulk Operations

/**
* Inserts all of the elements in the specified collection into this
* list at the specified position (optional operation). Shifts the
* element currently at that position (if any) and any subsequent
* elements to the right (increases their indices). The new elements
* will appear in this list in the order that they are returned by the
* specified collection's iterator. The behavior of this operation is
* undefined if the specified collection is modified while the
* operation is in progress. (Note that this will occur if the specified
* collection is this list, and it's nonempty.)
*
* <p>This implementation gets an iterator over the specified collection and
* a list iterator over this list pointing to the indexed element (with
* <tt>listIterator(index)</tt>). Then, it iterates over the specified
* collection, inserting the elements obtained from the iterator into this
* list, one at a time, using <tt>ListIterator.add</tt> followed by
* <tt>ListIterator.next</tt> (to skip over the added element).
*
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> if the list iterator returned by
* the <tt>listIterator</tt> method does not implement the <tt>add</tt>
* operation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public boolean addAll(int index, Collection<? extends E> c) {
try {
boolean modified = false;
ListIterator<E> e1 = listIterator(index);
Iterator<? extends E> e2 = c.iterator();
while (e2.hasNext()) {
e1.add(e2.next());
modified = true;
}
return modified;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}


// Iterators

/**
* Returns an iterator over the elements in this list (in proper
* sequence).<p>
*
* This implementation merely returns a list iterator over the list.
*
* @return an iterator over the elements in this list (in proper sequence)
*/
public Iterator<E> iterator() {
return listIterator();
}

/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
*
* @param index index of first element to be returned from the list
* iterator (by a call to the <code>next</code> method)
* @return a list iterator over the elements in this list (in proper
* sequence)
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public abstract ListIterator<E> listIterator(int index);
}

所以只要继承类实现了 listIterator 方法,它不需要再额外实现什么即可使用。

对于随机访问集合类一般建议继承 AbstractList 而不是 AbstractSequentialList。

LinkedList和其父类一样,也是基于顺序访问。所以 LinkedList 继承了AbstractSequentialList,但 LinkedList 并没有直接使用父类的方法,而是重新实现了一套的方法。

源码分析

构造方法

LinkedList有两个构造方法,一个是空构造方法,一个是用已有的集合创建链表的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Constructs an empty list.
*/
public LinkedList() {
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}

查找

LinkedList 底层基于链表结构,无法向 ArrayList 那样随机访问指定位置的元素。

LinkedList 查找过程要稍麻烦一些,需要从链表头结点(或尾节点)向后(向前)查找,时间复杂度为 `O(N)`。相关源码如下:

根据指定位置取数据的方法

get(int index): 根据指定索引返回数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public E get(int index) {
//检查index范围是否在size之内
checkElementIndex(index);
//调用Node(index)去找到index对应的node然后返回它的值
return node(index).item;
}

Node<E> node(int index) {
/*
* 则从头节点开始查找,否则从尾节点查找
* 查找位置 index 如果小于节点数量的一半,
*/
if (index < (size >> 1)) {
Node<E> x = first;
// 循环向后查找,直至 i == index
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

上面的代码比较简单,主要是通过遍历的方式定位目标位置的节点。获取到节点后,取出节点存储的值返回即可。这里面有个小优化,即通过比较 index 与节点数量 size/2 的大小,决定从头结点还是尾节点进行查找。

获取头结点(index = 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}

public E element() {
return getFirst();
}

public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}

public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}

区别: getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null。

其中getFirst()element() 方法将会在链表为空时,抛出NoSuchElementException。

获取尾结点(index = -1)

1
2
3
4
5
6
7
8
9
10
11
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}

public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}

两者区别: getLast() 方法在链表为空时,会抛出NoSuchElementException,而peekLast() 则不会,只是会返回 null

根据对象得到索引的方法

int indexOf(Object o): 从头遍历找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int indexOf(Object o) {
int index = 0;
if (o == null) {
//从头遍历
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
//从头遍历
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}

int lastIndexOf(Object o): 从尾遍历找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
//从尾遍历
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
//从尾遍历
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

检查链表是否包含某对象

contains(Object o): 检查对象o是否存在于链表中

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Returns {@code true} if this list contains the specified element.
* More formally, returns {@code true} if and only if this list contains
* at least one element {@code e} such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
*
* @param o element whose presence in this list is to be tested
* @return {@code true} if this list contains the specified element
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}

遍历

链表的遍历过程也很简单,和上面查找过程类似,我们从头节点往后遍历就行了。但对于 LinkedList 的遍历还是需要注意一些,不然可能会导致代码效率低下。通常情况下,我们会使用 for each 遍历 LinkedList,而 for each 最终转换成迭代器形式。所以分析 LinkedList 的遍历的核心就是它的迭代器实现,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}

private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;

/** 构造方法将 next 引用指向指定位置的节点 */
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}

public boolean hasNext() {
return nextIndex < size;
}

public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();

lastReturned = next;
next = next.next; // 调用 next 方法后,next 引用都会指向他的后继节点
nextIndex++;
return lastReturned.item;
}

// 省略部分方法
}

插入

LinkedList 除了实现了 List 接口相关方法,还实现了 Deque 接口的很多方法,所以我们有很多种方式插入元素。

LinkedList 插入元素的过程实际上就是链表链入节点的过程,下面我们分析 List 接口中相关的插入方法:

将元素添加到链表尾部

add(E e) 方法:将元素添加到链表尾部

在指定位置添加元素

add(int index,E e):在指定位置添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/** 在链表尾部插入元素 */
public boolean add(E e) {
linkLast(e);
return true;
}

/** 在链表指定位置插入元素 */
public void add(int index, E element) {
checkPositionIndex(index);//检查索引是否处于[0-size]之间

// 判断 index 是不是链表尾部位置,如果是,直接将元素节点插入链表尾部即可
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}

/** 将元素节点插入到链表尾部 */
void linkLast(E e) {
final Node<E> l = last;
// 创建节点,并指定节点前驱为链表尾节点 last,后继引用为空
final Node<E> newNode = new Node<>(l, e, null);
// 将 last 引用指向新节点
last = newNode;
// 判断尾节点是否为空,为空表示当前链表还没有节点
if (l == null)
first = newNode;
else
l.next = newNode; // 让原尾节点后继引用 next 指向新的尾节点
size++;
modCount++;
}

/** 将元素节点插入到 succ 之前的位置 */
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
// 1. 初始化节点,并指明前驱和后继节点
final Node<E> newNode = new Node<>(pred, e, succ);
// 2. 将 succ 节点前驱引用 prev 指向新节点
succ.prev = newNode;
// 判断尾节点是否为空,为空表示当前链表还没有节点
if (pred == null)
first = newNode;
else
pred.next = newNode; // 3. succ 节点前驱的后继引用指向新节点
size++;
modCount++;
}

上面是插入过程的源码,我对源码进行了比较详细的注释,应该不难看懂。上面两个 add 方法只是对操作链表的方法做了一层包装,核心逻辑在 linkBefore 和 linkLast 中

这里以 linkBefore 为例,linkBefore方法需要给定两个参数,一个插入节点的值,一个指定的node,所以我们又调用了Node(index)去找到index对应的node,它的逻辑流程如下:

  1. 创建新节点,并指明新节点的前驱和后继
  2. 将 succ 的前驱引用指向新节点
  3. 如果 succ 的前驱不为空,则将 succ 前驱的后继引用指向新节点

对应于下图:

l3

将集合插到链表尾部

addAll(Collection c ):将集合插入到链表尾部

1
2
3
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}

将集合从指定位置开始插入

addAll(int index, Collection c): 将集合从指定位置开始插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public boolean addAll(int index, Collection<? extends E> c) {
//1:检查index范围是否在size之内
checkPositionIndex(index);

//2:toArray()方法把集合的数据存到对象数组中
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;

//3:得到插入位置的前驱节点和后继节点
Node<E> pred, succ;
//如果插入位置为尾部,前驱节点为last,后继节点为null
if (index == size) {
succ = null;
pred = last;
}
//否则,调用node()方法得到后继节点,再得到前驱节点
else {
succ = node(index);
pred = succ.prev;
}

// 4:遍历数据将数据插入
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//创建新节点
Node<E> newNode = new Node<>(pred, e, null);
//如果插入位置在链表头部
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}

//如果插入位置在尾部,重置last节点
if (succ == null) {
last = pred;
}
//否则,将插入的链表与先前链表连接起来
else {
pred.next = succ;
succ.prev = pred;
}

size += numNew;
modCount++;
return true;
}

上面可以看出addAll()方法通常包括下面四个步骤:

  1. 检查index范围是否在size之内
  2. toArray()方法把集合的数据存到对象数组中
  3. 得到插入位置的前驱和后继节点
  4. 遍历数据,将数据插入到指定位置

将元素添加到链表头部

addFirst(E e): 将元素添加到链表头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void addFirst(E e) {
linkFirst(e);
}

private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点
first = newNode;
//如果链表为空,last节点也指向该节点
if (f == null)
last = newNode;
//否则,将头节点的前驱指针指向新节点,也就是指向前一个元素
else
f.prev = newNode;
size++;
modCount++;
}

将元素添加到链表尾部

addLast(E e): 将元素添加到链表尾部,与 add(E e) 方法一样

1
2
3
public void addLast(E e) {
linkLast(e);
}

删除

如果大家看懂了上面的插入源码分析,那么再看删除操作实际上也很简单了。删除操作通过解除待删除节点与前后节点的链接,即可完成任务。过程比较简单,看源码吧:

删除指定元素

remove(Object o): 删除指定元素

删除指定位置的元素

remove(int index):删除指定位置的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//删除指定元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 遍历链表,找到要删除的节点
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x); // 将节点从链表中移除
return true;
}
}
}
return false;
}

//删除指定位置的元素
public E remove(int index) {
checkElementIndex(index);
// 通过 node 方法定位节点,并调用 unlink 将节点从链表中移除
return unlink(node(index));
}

/** 将某个节点从链表中移除 */
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;

// prev 为空,表明删除的是头节点
if (prev == null) {
first = next;
} else {
// 将 x 的前驱的后继指向 x 的后继
prev.next = next;
// 将 x 的前驱引用置空,断开与前驱的链接
x.prev = null;
}

// next 为空,表明删除的是尾节点
if (next == null) {
last = prev;
} else {
// 将 x 的后继的前驱指向 x 的前驱
next.prev = prev;
// 将 x 的后继引用置空,断开与后继的链接
x.next = null;
}

// 将 item 置空,方便 GC 回收
x.item = null;
size--;
modCount++;
return element;
}

和插入操作一样,删除操作方法也是对底层方法的一层保证,核心逻辑在底层 unlink 方法中。所以长驱直入,直接分析 unlink 方法吧。unlink 方法的逻辑如下(假设删除的节点既不是头节点,也不是尾节点):

  1. 将待删除节点 x 的前驱的后继指向 x 的后继
  2. 将待删除节点 x 的前驱引用置空,断开与前驱的链接
  3. 将待删除节点 x 的后继的前驱指向 x 的前驱
  4. 将待删除节点 x 的后继引用置空,断开与后继的链接

对应下图:

l4

删除头节点

remove()removeFirst(),pop(): 删除头节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
   public E pop() {
return removeFirst();
}

public E remove() {
return removeFirst();
}

public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}

/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}

删除尾节点

removeLast(),pollLast(): 删除尾节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}

public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}

/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}

区别: 区别在于对于链表为空时的处理,removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。

参考:

http://www.tianxiaobo.com/2018/01/31/LinkedList-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-JDK-1-8/#4%E6%80%BB%E7%BB%93

https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/LinkedList.md

秉持初心,继续向前。
显示 Gitment 评论