理解List & AbstractList & ArrayList

ArrayList 实现了 List 接口,继承了 AbstractList 抽象类。

Q: 为什么要ArrayList继承AbstractList,让AbstractList实现List?而不是让ArrayList直接实现List?

A: 接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法。因此让AbstractList实现接口中一些通用的方法,而具体的类(如ArrayList)继承AbstractList类获得通用的方法,再实现一些自己特有的方法,使代码更简洁。

索引和游标的关系示意图,帮助理解后续源码。

AbstractList实现了List中的部分方法,剩余的方法被转为 abstract 方法,由 AbstractList 的子类(如ArrayList, LinkedListVector)实现。

java 复制代码
// Search Operations

// 1.获取某个元素在集合中的索引
public int indexOf(Object o) {
    // AbstractList内部提供了Iterator, ListIterator迭代器的实现类,分别为Itr,ListItr
    ListIterator<E> it = listIterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return it.previousIndex();
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return it.previousIndex();
    }
    //如果集合中不存在该元素,返回-1
    return -1;
}

// 2.获取某个元素在集合中最后一次出现的索引
public int lastIndexOf(Object o) {
    ListIterator<E> it = listIterator(size());
    if (o==null) {
        while (it.hasPrevious())
            if (it.previous()==null)
                return it.nextIndex();
    } else {
        while (it.hasPrevious())
            if (o.equals(it.previous()))
                return it.nextIndex();
    }
    return -1;
}


// Iterators

// 1.获取Iterator接口Itr实现类迭代器
public Iterator<E> iterator() {
    return new Itr();
}

// 2.获取从0开始(初始位置)的ListIterator的实现类ListItr
public ListIterator<E> listIterator() {
    return listIterator(0);
}

// 3.获取从索引等于index的位置的迭代器
public ListIterator<E> listIterator(final int index) {
    // 检查下标合法性
    rangeCheckForAdd(index);
    return new ListItr(index);
}


// 内部实现了Iterator接口的实现类Itr
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();
        }
    }
       
    // 删除上一次迭代器越过的元素
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // 调用需要子类去实现的remove方法
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            // 每次删除后,将lastRet置为-1,防止连续的删除
            lastRet = -1;
            // 更新 expectedModCount 确保下次迭代时能通过并发检查
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

    /**
    AbstractList 内部获取迭代器。迭代过程使用迭代器本身的 remove() 移除元素是被允许的,
    迭代器会更新 expectedModCount,保证下一次的 next() 通过 checkForComodificatio() 检查。
    "用户"在迭代过程中调用 public 的会导致数组结构性修改的方法(add、remove)是不被允许的。
    **/
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}


// 继承自Itr的ListIterator的实现类ListItr
private class ListItr extends Itr implements ListIterator<E> {

    // 指定光标位置等于索引的迭代器构造
    ListItr(int index) {
        cursor = index;
    }
    public boolean hasPrevious() {
            return cursor != 0;
    }
    
    public E previous() {
        checkForComodification();
        try {
            // 注意索引和游标的区别
            int i = cursor - 1;
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
    
    // 下一位的索引值等于光标值
    public int nextIndex() {
        return cursor;
    }
    // 上一位的索引值等于光标值减一
    public int previousIndex() {
        return cursor-1;
    }

    // 设置元素
    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // 调用需要子类去实现的 set 方法
            AbstractList.this.set(lastRet, e);
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    // 添加元素
    public void add(E e) {
        checkForComodification();
        try {
            // 设置添加的位置为当前光标所在的位置
            int i = cursor;
            AbstractList.this.add(i, e);
            // 添加的元素不允许立即删除
            lastRet = -1;
            cursor = i + 1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

其实这里我在分析的时候是存在疑问的(为什么 Itr 中的 next() 方法的 cursor 实际上等于 lastRet + 1,而 previous() 方法中 lastRet = cursor),请看图解。

lastRet代表上一次迭代到的元素的光标位置,在next操作中,上一次迭代的元素为"E",对应光标位置为4;在previous操作中,上一次迭代的元素为"D",对应光标位置为3。

我们根据以上代码也可以解释阿里巴巴开发手册的这一点:

由于checkForComodification,"用户"在迭代过程中调用 public 的会导致数组结构性修改的方法(add、remove)是不被允许的。而 Iterator 的 remove 方法在删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步。

java 复制代码
// 如何正确地删除元素
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

// 反例,使用for-each会报错
for (String str : list) {
	if ("B".equals(str)) {
		list.remove(str);
	}
}

// 反例,使用for循环
// 初始list.size()=3,当i=1时,执行remove后list.size=2,意味着"C"元素被跳过了
for (int i = 0; i < list.size(); i++) {
	String str = list.get(i);
	if ("B".equals(str)) {
		list.remove(str);
	}
}

// 正例
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
	String str = itr.next();
	if ("B".equals(str)) {
		itr.remove();
	}
}

除此之外,在 AbstractList 里面有一个很有意思的equals 方法

java 复制代码
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

在第二步检查时,判断了是否为实现了 List 的类的实例。要知道 List 是有很多实现类的,它们都能通过第二步判断,而且同时也都能通过 ListIterator 获取迭代器进行遍历。说明不同的 List 实现类只要装载的内容相同,那么通过 equals 判断的结果就为 true

java 复制代码
public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("peterxx");

    LinkedList<String> linkedList = new LinkedList<>();
    linkedList.add("peterxx");
    
    // 输出为True
    System.out.println(arrayList.equals(linkedList));
}

只要是同一类型的容器(都实现了 List),并且所有元素相同,那么两者就是相等,无需关心容器的实现细节差别(如 ArrayListLinkedList

参考:ArrayList和LinkedList的区别:如何选择? | 二哥的Java进阶之路 (javabetter.cn)

Java基础系列(四十二):集合之AbstractList - 《山禾说Java》 - 极客文档 (geekdaxue.co)

相关推荐
顾北川_野2 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航4 分钟前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself20 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041525 分钟前
J2EE平台
java·java-ee
XiaoLeisj32 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
豪宇刘1 小时前
SpringBoot+Shiro权限管理
java·spring boot·spring
Elaine2023911 小时前
02多线程基础知识
java·多线程
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea