03.List

110 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

概要

List和Collection最大的区别就是元素规定是有序的, 所以List在Collection的基础上添加了索引相关操作. 同时在AbstractList中对List的equals和hashCode方法进行了重定义.

List中的设计

List中几乎把Collection里所有的方法都添加了一个带有索引的重载方法, 并扩展了修改(修改索引位置元素)/索引查询/子列表视图/排序的操作. 同样AbstractList给List中的绝大部分方法都提供了默认实现, 其中clear方法的实现比较值得关注.

modCount

AbstractList除了在方法上的扩展之外同时扩展了一个modCount字段,这是个子类可选择实现的字段, 如果用户需要实现fail-fast iterators(我理解是感知list变化可以快速失败的迭代器),则只需要在add和remove方法成功时给modCount字段自增就好了.

iterator实现

AbstractList中的Itr借助于List可以根据索引操作元素的特性实现了一个公共的Iterator, 他的兄弟Set和Queue都没法做到这一点. 当然实现是一回事子类买不买账就是另外一回事了, 由于迭代器想要有更好性能和底层的数据结构是息息相关的.假如底层实现是链表结构直接使用这个默认迭代器,O(n²)的迭代效率怕是直接爆炸.

private class Itr implements Iterator<E> {  
   
	int cursor = 0;  
  
    int lastRet = -1;  
  
    int expectedModCount = modCount;  
  
    public boolean hasNext() {  
        return cursor != size();  
    }  
  
    public E next() {  
        checkForComodification();  
        try {  
            int i = cursor;  
            E next = get(i);  
            lastRet = i;  
            cursor = i + 1;  
            return next;  
        } catch (IndexOutOfBoundsException e) {  
            checkForComodification();  
            throw new NoSuchElementException(e);  
        }  
    }  
  
    public void remove() {  
        if (lastRet < 0)  
            throw new IllegalStateException();  
        checkForComodification();  
  
        try {  
            AbstractList.this.remove(lastRet);  
            if (lastRet < cursor)  
                cursor--;  
            lastRet = -1;  
            expectedModCount = modCount;  
        } catch (IndexOutOfBoundsException e) {  
            throw new ConcurrentModificationException();  
        }  
    }  
  
    final void checkForComodification() {  
        if (modCount != expectedModCount)  
            throw new ConcurrentModificationException();  
    }  
}

ListIterator

list都有索引了,那么list需要一个有索引操作的迭代器也就合情合理了, ListIterator扩展了查询当前元素索引的方法,同时也提供往前查询的能力,使ListIterator不光能正序遍历还能倒叙或者来回遍历,当然也能修改当前元素.

public interface ListIterator<E> extends Iterator<E> {  
    // Query Operations  
   boolean hasNext();  
  
   E next();  
  
   boolean hasPrevious();  
  
   E previous();  
  
   int nextIndex();  
  
   int previousIndex();  
  
  
   void remove();  
  
   void set(E e);  
  
   void add(E e);  
}

SubList

SubList是AbstractList中给subList方法提供的一个默认返回实现, 它的工作模式就是把操作委托给原始List进行完成,自己内部维护一套offset/size来实现对原始list操作的上下界保护. 这里挑选removeRange来学习SubList的工作模式.

  1. 首先SubList会先进行并发修改判断, 即自己不能落后于原始容器的修改版本,如果原始容器脱离子容器发生修改操作,那么子容器就失效了.
  2. 委托给root即原始容器进行删除操作.
  3. 更新size和modCount. 注意这里做了一些加速操作, 因为SubList的父容器可能也是SubList, 所以在做修改的时候SubList直接绕过同为视图的父容器直接对原始容器进行执行命令, 减少了许多不必要的栈帧, 如果有需要进行size和modCount更新的地方统一在一个方法处理.
protected void removeRange(int fromIndex, int toIndex) {  
	checkForComodification();  
	root.removeRange(offset + fromIndex, offset + toIndex);  
	updateSizeAndModCount(fromIndex - toIndex);  
}  

private void checkForComodification() {  
	if (root.modCount != this.modCount)  
		throw new ConcurrentModificationException();  
}  

private void updateSizeAndModCount(int sizeChange) {  
	SubList<E> slist = this;  
	do {  
		slist.size += sizeChange;  
		slist.modCount = root.modCount;  
		slist = slist.parent;  
	} while (slist != null);  
} 

前文有提到过removeRange比较值得被关注, 事实上值得被关注的地方就在这里, 当子容器执行clear方法时只需要转化为removeRange操作, 语义清晰也不需要什么骚操作.

实现一个简单的List

package util.collection;  
  
import java.util.AbstractList;  
import java.util.Arrays;  
import java.util.List;  
import java.util.Objects;  

public class ListExample {  
  
    public static void main(String[] args) {  
        SimpleList<Integer> simpleList = new SimpleList<>();  
        simpleList.add(1);  
        simpleList.add(2);  
        simpleList.add(3);  
        simpleList.add(4);  
        System.out.println(Arrays.toString(simpleList.toArray()));  
        simpleList.set(2, 2);  
        System.out.println(Arrays.toString(simpleList.toArray()));  
        simpleList.remove(2);  
        System.out.println(Arrays.toString(simpleList.toArray()));  
        System.out.println(simpleList.size);  
    }  
  
    public static class SimpleList<E> extends AbstractList<E> implements List<E> {  
  
        private Node<E> head;  
  
        private int size;  
  
        @Override  
		public E get(int index) {  
            checkIndex(index);  
            Node<E> node = head;  
            for (int i = 0; i < index; i++) {  
                node = node.next;  
            }  
            return node.data;  
        }  
  
        @Override  
		public int size() {  
            return size;  
        }  
  
        @Override  
		public E set(int index, E element) {  
            checkIndex(index);  
            Node<E> pre = null;  
            Node<E> node = head;  
            for (int i = 0; i < index; i++) {  
                pre = node;  
                node = node.next;  
            }  
            Node<E> cur = new Node<>(element);  
            if (pre != null) {  
                pre.next = cur;  
            } else {  
                head = cur;  
            }  
            cur.next = node.next;  
            return node.data;  
        }  
  
        @Override  
		public void add(int index, E element) {  
            if (index > size || index < 0)  
                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);  
            Node<E> pre = index > 0 ? head : null;  
            for (int i = 0; i < index - 1; i++) {  
                pre = pre.next;  
            }  
            Node<E> cur = new Node<>(element);  
            if (pre != null) {  
                cur.next = pre.next;  
                pre.next = cur;  
            } else {  
                cur.next = head;  
                head = cur;  
            }  
            size++;  
        }  
  
        @Override  
		public E remove(int index) {  
            Node<E> pre = null;  
            Node<E> node = head;  
            for (int i = 0; i < index; i++) {  
                pre = node;  
                node = node.next;  
            }  
  
            if (pre != null) {  
                pre.next = node.next;  
            } else {  
                head = node.next;  
            }  
            size--;  
            return node.data;  
        }  
  
        private void checkIndex(int index) {  
            Objects.checkIndex(index, size);  
        }  
    }  
  
    public static class Node<E> {  
        final E data;  
  
        Node<E> next;  
  
        public Node(E data) {  
            this.data = data;  
        }  
  
    }  
}