Java源码解析之接口List
作者:不会编程的派大星 发布时间:2022-06-13 08:46:44
前言
List接口是Collection接口的三大接口之一,其中的数据可以通过位置检索,用户可以在指定位置插入数据。List的数据可以为空,可以重复。我们来看看api文档是怎么说的:
一、List特有的方法
我们这里就只关注和Collection不同的方法,主要有以下这些:
//在指定位置,将指定的集合插入到当前的集合中
boolean addAll(int index, Collection<? extends E> c);
//这是一个默认实现的方法,会通过Iterator的方式对每个元素进行指定的操作
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
//排序,依据指定的规则对当前集合进行排序,可以看到,排序是通过Arrays这个工具类完成的。
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//获取指定位置的元素
E get(int index);
//修改指定位置元素的值
E set(int index, E element);
//将指定元素添加到指定的位置
void add(int index, E element);
//将指定位置的元素移除
E remove(int index);
//返回一个元素在集合中首次出现的位置
int indexOf(Object o);
//返回一个元素在集合中最后一次出现的位置
int lastIndexOf(Object o);
//ListIterator继承于Iterator,主要增加了向前遍历的功能
ListIterator<E> listIterator();
//从指定位置开始,返回一个ListIterator
ListIterator<E> listIterator(int index);
//返回一个子集合[fromIndex, toIndex),非结构性的修改返回值会反映到原表,反之亦然。
//如果原表进行了结构修改,则返回的子列表可能发生不可预料的事情
List<E> subList(int fromIndex, int toIndex);
通过对上面方法的研究,我们不难发现,collection接口主要提供一些通常的方法,而List接口则针对线性表的结构,提供了对位置以及字表的操作。
二、超级实现类AbstractList
我们先看看源文档是怎么来说AbstractList的,要实现一个不可修改的集合,只需要复写get和size方法。如果要实现一个可以修改的集合,还需要复写set方法,如果要动态调整大小,就必须实现add和remove方法。
接下里我们一起来来看看源码吧!
//在AbstractCollection中,add方法默认会抛出异常,
//而在这里是调用了add(int index, E e)方法,但这个方法也是没有实现的。
//这里默认会把元素添加到末尾。
public boolean add(E e) {
add(size(), e);
return true;
}
//同上,这个只需要进行一次遍历即可
public boolean addAll(int index, Collection<? extends E> c) {
//...
}
接下里我们在继续看看其他几个方法,这几个是与Iterator和ListIterator息息相关的,在AbstractList中有具体的实现,我们先来看看它是如何把集合转变成Iterator对象并支持foreach循环的吧。
我们通过源码发现:在Iterator方法中,是直接返回一个 Itr对象
public Iterator<E> iterator() {
return new Itr();
}
其实我们很快也就会明白,它是实现了一个内部类,这个内部类实现了Iterator接口,合理的处理hasNext、next、remove方法。这个源码就不粘贴啦,其中仅仅在remove时考虑了一下多线程问题,有兴趣的可以自己去看看。
我们来看看另一个吧–ListIterator吧他也是通过一个内部类是实现的
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
事实证明,和我们想的一样,AbstractList内部还定义了一个ListItr,实现了ListIterator接口,其实现也很简单,就不粘贴源码啦。
接下俩让我们来看看AbtractList是怎么利用这两个类来做事情的
//寻找一个元素首次出现的位置,只需要从前往后遍历,找到那个元素并返回其位置即可。
public int indexOf(Object o) {
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();
}
return -1;
}
//同理,寻找一个元素最后一次出现的位置,只需要从列表最后一位向前遍历即可。
//看到listIterator(int index)方法是可以传递参数的,这个我想我们都可以照着写出来了。
public int lastIndexOf(Object o) {
//...
}
//这个方法是把从fromIndex到toIndex之间的元素从集合中删除。
//clear()方法也是调用这个实现的(我认为clear实现意义并不大,因为在其上级AbstractCollection中已经有了具体实现)。
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
在接下来让我们来说一说两个比较重要的内容一个是关于SubList,另一个是关于equals和hascode的。
三、SubList、equals和hascode
SubList并不是新建了一个list,只是持有当前集合的引用,然后控制了用户可以操作的范围,所以在接口定义时就说明了其更改会直接反应到原集合中。SubList是定AbstractList内部,并且是AbstractList的基础上增加了对可选范围的控制。
而equals和hascode的实现,也关乎我们的使用。在AbstractList中,这两个方法不仅与其实例有关,也和其内部包含的元素有关,所以在定义数据元素时,也应该复写这两个方法,以保证程序的正确运行。这里看下其源码加深一下印象吧。
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();
//这里用到了数据元素的equals方法
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
public int hashCode() {
int hashCode = 1;
for (E e : this)
//这里用到了数据元素的hashCode方法
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
来源:https://blog.csdn.net/weixin_45827693/article/details/116699644


猜你喜欢
- 开发环境为android4.1.Handler使用例1这个例子是最简单的介绍handler使用的,是将handler绑定到它所建立的线程中.
- 问题描述1.可以访问同一个文件夹下面的success.jsp文件,如图:2、却不能访问同一个文件夹下面的 index.html文件,如图:问
- 本章节内容很丰富,主要有基本的表单操作,数据的格式化,数据的校验,以及提示信息的国际化等实用技能。首先看效果图项目结构图接下来用代码重点学习
- Object.MemberwiseClone 方法创建当前 Object 的浅表副本。protected Object Memberwise
- handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段。使用起来很简单,就两个步骤,在主线程重写handler的handl
- RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。RP
- 在使用fastJson时,对于泛型的反序列化很多场景下都会使用到TypeReference,例如:void testTypeReferenc
- 当初学的是通信专业,毕业以后,同学们各奔东西,去追逐自己的梦想,奔波于大大小小的工地之间。哈哈,开个玩笑,也有厉害的,进了某某研究所,嗯?他
- Android动画 实现开关按钮动画(属性动画之平移动画),最近做项目,根据项目需求,有一个这样的功能,实现类似开关的动画效果,经过自己琢磨
- 前言关于mybatis-plus的简介以及基本使用,我在《SpringBoot整合mybatis-plus–入门超详细》一文中已做介绍,此处
- 可以给已有实体类动态的添加字段并返回新的实体对象,不影响原来的实体对象结构。添加依赖<dependency> &n
- 用C++流成员函数put输出单个字符在程序中一般用cout和插入运算符“<<”实现输出,cout流在内存中有相应的缓冲区。有时用
- 一、前言android客户端开发进入尾声,负责SEO同事突然发给我一个涉及45个发布渠道的噩耗,之前只发布自有渠道的工作方式(手动修改参数打
- 引言JVM进程消失可能有哪些原因?这个问题也是面试中经常出现的,如下图所示ps:由于两年多没写crud了,所以忘记mybatis怎么用了,所
- 在 C# 中,数组实际上是对象,而不只是像 C 和 C++ 中那样的可寻址连续内存区域。 Array 是所有数组类型的抽象基类型。 可以使用
- 前言:今年是我的第二个 1024 了 ,和我一起大声说出来,技术宅改变世界!!!本节主要介绍的是:SpringBoot 整合阿里 Druid
- 俄罗斯方块Tetris是一款很经典的益智游戏,之前就做了一款桌面版的java俄罗斯方块,这次就尝试着写了一款适用于Android平台的俄罗斯
- 有时候我们从数据库中查询出来数据之后,需要按照DataTable的某列进行分组,可以使用下面的方法实现,代码如下:using System;
- 目录一. 已有倒计时方案存在的问题1. CountDownTimer2. Handler3. Timer二. 自己封装倒计时总结一. 已有倒
- 在我们应用程序的业务逻辑中,经常会碰到参数校验的情况,手动的在代码层上面进行校验就会带来很不好的体验,阅读、维护的成本会大大增加,造成冗余。