理解List AbstractList ArrayList

news2024/10/2 6:40:04

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

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

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

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

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

// 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。

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

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

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),并且所有元素相同,那么两者就是相等,无需关心容器的实现细节差别(如 ArrayList 与 LinkedList

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2065634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【达梦数据库】shell脚本获取集群内确认监视器地址

目录 1、需求2、想法3、实现代码4、检验效果4.1、集群内任意节点使用非dmdba用户执行4.2、集群内任意节点使用dmdba用户执行4.2.1、数据库主备节点执行4.2.1、数据库确认监视器节点执行 4.3、非集群内节点执行 1、需求 有确认监视器的集群&#xff0c;在集群的任何一个集群上执…

Android13 app后台无法启动Abort background activity starts from

总纲 android13 rom 开发总纲说明 目录 1.前言 2.log分析 3.代码查找分析 4.修改方法 5.编译测试 6彩蛋 1.前言 Android13 用户app后台无法启动,提示Abort background activity starts from 10111 2.log分析 08-07 21:37:36.703: W/ActivityTaskManager(440): Back…

Llama3.1大模型

背景 Llama 3.1是一款由Meta&#xff08;前Facebook&#xff09;推出的先进大型语言模型。它在自然语言处理领域具有显著优势&#xff0c;为用户提供高质量的文本生成、理解和推理能力。 Transformer架构 Transformer是一种神经网络架构&#xff0c;可以处理文本、音频、视频和…

无线数传模块有啥特点?

一 、 模块特点  支持 RS485RTU 、RS232、UART 标准协议  AES加密  供电电压DC4.5V——5.5V  工作频段 410~525MHz, 免申请频段  标准配置提供多达 115信道 …

数据结构-递归算法-第四天

参考文献&#xff1a; 华为云 博客园 labuladong 的算法笔记 递归是一种编程技巧&#xff0c;一种解决问题的思维方式&#xff1b;分治算法和动态规划很大程度上是递归思想基础上的&#xff08;虽然动态规划的最终版本大都不是递归了&#xff0c;但解题思想还是离不开递归&…

数学建模之数据分析【七】:对Pandas DataFrame 进行切片

文章目录 一、切片简介二、创建Pandas数据框三、使用iloc进行切片3.1 对行进行切片3.2 对列进行切片3.3 Dataframe选中特定单元格 四、使用loc创建切片4.1 使用Python对Dataframe中的行进行切片4.2 指定单元格 五、在Python中使用布尔条件六、结论 对 Pandas DataFrames 进行切…

水战再起波澜,“怡宝”要下好怎样一盘棋?

不少投资者常把那些刚需性强、永远也不可能淘汰的产业称为“日不落产业”&#xff0c;从细分板块来看&#xff0c;水无疑具有一定代表性。农夫山泉掌门人钟晱晱曾直言&#xff1a;“我选择了一个日不落的产业&#xff0c;你永远要喝水&#xff0c;不可能不喝水。” 多年下来&a…

Python | Leetcode Python题解之第367题有效的完全平方数

题目&#xff1a; 题解&#xff1a; class Solution:def isPerfectSquare(self, num: int) -> bool:x0 numwhile True:x1 (x0 num / x0) / 2if x0 - x1 < 1e-6:breakx0 x1x0 int(x0)return x0 * x0 num

SpringBoot集成kafka-获取生产者发送的消息(阻塞式和非阻塞式获取)

说明 CompletableFuture对象需要的SpringBoot版本为3.X.X以上&#xff0c;需要的kafka依赖版本为3.X.X以上&#xff0c;需要的jdk版本17以上。 1、阻塞式&#xff08;等待式&#xff09;获取生产者发送的消息 生产者&#xff1a; package com.power.producer;import org.ap…

<数据集>车内视角行人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6470张 标注数量(xml文件个数)&#xff1a;6470 标注数量(txt文件个数)&#xff1a;6470 标注类别数&#xff1a;1 标注类别名称&#xff1a;[pedestrian] 序号类别名称图片数框数1pedestrian647029587 使用标注…

c++链表(list)

前言 链表作为一个常见的数据结构&#xff0c;在高频插入删除的场景下有独特的优势&#xff0c;在内存的使用上也极少有浪费可以按需申请。今天我们就来简单的学习一下这种数据结构&#xff0c;链表也有很多不同的实现&#xff0c;我们这里和标准库保持一致&#xff0c;实现带…

UDP通信函数补充 | TCP

UDP流程补充&#xff1a; recvfrom() 这是一个系统调用&#xff0c;用于从套接字接收数据的函数。该函数通常与无连接的数据报服务&#xff08;如 UDP&#xff09;一起使用&#xff0c;但也可以与其他类型的套接字使用。 函数原型为&#xff1a; ssize_t recvfrom(int sock…

使用Node-RED实现和部署物联网入侵检测的机器学习管道

整理自 《Implementing and Deploying an ML Pipeline for IoT Intrusion Detection with Node-RED》&#xff0c;由 Yimin Zhang 等人撰写&#xff0c;发表于 2023 年 CPS-IoT Week Workshops。以下是根据提供的 PDF 内容整理的论文的详细主要内容&#xff1a; 摘要 (Abstra…

Linux入门——09 共享内存

1.共享内存原理 OS内的每个进程都会有自己的内核结构&#xff08;task_struct&#xff09;和虚拟地址空间,通过页表与物理内存进程映射。 如果让两个不同的进程共享内存&#xff0c;首先就是在内存中申请一块空间&#xff08;共享内存&#xff09;&#xff0c; 然后将建立好…

Unity XR Interaction Toolkit 踩坑记录

1&#xff1a;按下 grap/select 键 物品直接飞到手上 2 按下 grap/select 键 物品一点点的想自己移动

《机器学习》—— AUC评估指标

文章目录 一、什么是AUC&#xff1f;1、什么是ROC曲线&#xff1f;2、ROC曲线的绘制 二、如何计算AUC的值三、代码实现AUC值的计算四、AUC的优缺点 一、什么是AUC&#xff1f; 机器学习中的AUC&#xff08;Area Under the Curve&#xff09;是一个重要的评估指标&#xff0c;特…

走进虚拟机逃逸技术之VMware Escape漏洞CVE-2023-20872复现

走进虚拟机逃逸技术之VMware Escape漏洞CVE-2023-20872复现 技术分享 技术分享 起初&#xff0c;为了学习虚拟机逃逸相关技术&#xff0c;也为了搞懂硬件虚拟化。于是请教了某巨佬后告诉我一本书&#xff0c;看完之后为了验证我理解到的硬件虚拟化及虚拟化逃逸原理是否正确&am…

图书管理系统详细设计

需求概述 按照需求分析文档中的规格要求&#xff0c;使用条形码扫描器进书、借书、还书&#xff0c;使得信息传递准确、流畅。同时&#xff0c;系统最大限度地实现易安装&#xff0c;易维护性&#xff0c;易操作性&#xff0c;运行稳定&#xff0c;安全可靠。 软件结构 系统由…

如何让虚拟机识别到宿主机的USB设备

我的实验环境&#xff1a; Windows宿主机VirtualBox虚拟化软件一个Linux虚机一个8G的USB磁盘 首先要让虚拟机能看到宿主机的USB设备&#xff0c;这是在VirtualBox中设置的。 选中虚机&#xff0c;右键选择“设置”菜单&#xff0c;再单击“USB设备”&#xff1a; 选中“启用…

Python | Leetcode Python题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution:def canMeasureWater(self, x: int, y: int, z: int) -> bool:if x y < z:return Falseif x 0 or y 0:return z 0 or x y zreturn z % math.gcd(x, y) 0