分析完Arraylist后来看一下List的另一种实现LinkedList。LinkedList底层使用的是双向链表结构,内部类定义了节点结构,一个头结点和尾节点表示队列的开始和结尾。与ArrayList不同的是,LinkedList除了实现了List接口外,还实现了Deque接口,而Deque接口继承自Queue接口,也就是说,LinkedList还可以用来实现双端队列。
关于具体类对象与接口的关系,我个人的理解就是,一个接口定义了它可以实现的全部功能,比如List接口,定义了add、set等等方法,而具体的类比如ArrayList或LinkedList如果实现了这个接口,就必须实现接口中定义的所有方法,所以ArrayList和LinkedList中肯定实现了List接口中定义的所有方法,但是一个类可以实现多个接口,比如LinkedList还实现了Deque接口,那么它内部肯定也实现了Deque接口定义的所有方法。在使用过程中,一般都是定义一个接口指向具体实例比如List list=new LinkedList(); 这样的意义在于只能调用该接口中定义的方法。虽然LinkedList中实现了其他的方法,但是List类型的引用只能调用该接口中定义的方法。
一、基本属性与构造函数
1 | public class LinkedList<E> |
既然是双端队列,首先自己定义了结点,然后用first和last来指向头结点与尾结点。
构造函数中也没有具体实现,因为不需要像ArrayList一样初始化数组什么的。
二、内部操作
由于底层由双向链表实现,所以我们先来看一下它内部的操作,主要包括添加节点与删除节点,这些方法没有重写任何接口,而是内部定义,在重写接口的方法的时候,主要还是调用这些底层的操作来实现的。
1、增加结点
1 | /* |
2、删除结点
1 | /* |
注释中可以看到,删除一个节点的前提都是该节点肯定不为null,因为在其他方法调用这些底层方法之前,肯定会先进行判断,然后再调用该方法直接对底层结构进行修改。
三、Deque接口
LinkedList实现了Deque接口,也就是双端队列,我们先来看一下该接口的定义
1 | public interface Deque<E> extends Queue<E> { |
它继承自Queue接口,关于Queue接口的方法上面的注释中给出了介绍,在Queue的基础上,它还添加了很多Deque的方法和两个Stack的方法,但是该接口并没有继承Stack接口,此处并不是很明白。
在接口定义中明确指出了哪些方法处于哪些操作,但是在LinkedList的代码中这些方法的继承关系并不是很明确,比如在有些方法前面明确指出了// Deque operations和// Queue operations.但是这些注释下面的方法与Deque接口中定义的并不符合,而且也没有Stack的相关操作的说明。
下面列出了LinkedList类中关于Deque接口的方法。
1 | public E getFirst() {// |
可以看出,这些方法基本都会调用上面第2节介绍的添加和删除操作,也就是说,这些方法仅仅的实现接口时对外的封装,内部的实现全部都依赖与第2节介绍的对底层数据结构的操作的那几个方法。
四、List接口
接下来这几节是根据源码中的注释进行分类的。注释中把剩下的方法分类为Positional Access Operations、Search Operations下面来看一下分类中对应的方法和实现。
1、Positional Access Operations
对指定位置元素的操作。一般参数中都会指定一个index位置,对该位置的元素进行操作。
1 | // Positional Access Operations |
与Deque接口一样,对指定位置元素的操作,内部还是会调用第二节的内部操作,不同的是,这里封装的是对元素操作,而第二节的方法是对底层链表的节点操作,两者是不一样的。
所以部分方法中基本都会先调用checkElementIndex(index);方法来确认index参数是否合法,如果不合法就直接抛出异常。
如果index参数合法,就会调用最下面的node(index)方法,即在链表中找到指定位置的节点,查找的方法采用顺序查找,先判断index是在前半部分还是后半部分,然后决定是从前向后查找还是从后向前查找。
2、Search Operations
查找操作,有两个方法查找某个元素第一次出现的位置,一个是从前向后查找,一个是从后向前查找。
1 | // Search Operations |
有一点需要注意的就是List中允许出现null,所以在判断元素相等的时候,需要判断该元素是否为null,如果是是null则用==判断,否则使用equals判断相等。
3、toArray
使用方法与ArrayList中一样。一个是返回Object数组,还有一个是返回指定类型的数组。
1 | public Object[] toArray() { |
4、其他
1 | public boolean contains(Object o) { |