日常小结-java-AbstractList-Itr和ListItr的实现

AbstractList

API文档

AbstractList实现了List接口,又因为List继承自Collection,Collection继承自Iterable。因此List接口包含很多的方法。

AbstractList实现了List接口的最小实现。

他是针对随机访问储存数据的方式的,如果需要使用顺序访问储存数据方式的List还有一个AbstractSequentialList它是继承自AbstractList的子类,顺序访问时应该优先使用它。

对于不可修改的list,只需要覆盖get和size就可以

对于修改的list,还需要覆盖set,如果要求list大小可变还需要拓展add和remove方法

跟所有的Collection类一样,通常子类都需要实现无参和带参数的两个构造器

AbstractList是一个字节实现了iterator的方法。这和其他的抽象Collection类不同。并且实现了迭代器get、set、add和remove方法。

源码分析

AbstractList

protected AbstractList() {
    }

首先构造器必然是protected。

如果是public有可能会被外界调用,虽然这个类是一个抽象类不可被调用构造器,但是从实现的目的上说AbstractList是为了编程人员实现一个List做一些铺垫工作,而不是直接用于构造对象的,因此不应该是protected方法。

当然也不可能是private,首先来说private修饰构造器过后这个类就没有办法被继承了,从这个角度说抽象类的构造器是不可以用private来修饰的。

private 通常来说用来做单例模式使用,具体搜索单例模式。

这样的情况下,希望子类可以实例化,protected是最好也是唯一的选择。

这里还需要说明的是java里面的构造器即使是protected的方法其子类却可以将其变成public方法。或者这种的说法不严谨,更严谨点的说法应该是基类的构造器和子类的构造并没有什么太大的关系。除非子类调用了super(),否则两者基本上是互相独立的。

我目前的认识是这样,如果有问题以后再修改

Itr

接下来是重头戏,也就是AbstractList实现的迭代器,但是在说迭代器之前需要说明一下:AbstractList的迭代器其实定义了两个迭代器,一个是实现了Iterator接口的迭代器Itr,另一个是实现了ListIterator并且继承自Itr的迭代器ListItr。

而ListItr实际上是继承自Itr的,这么一说就比较清晰了。迭代器的接口Itr实际上是公共的基本接口,就像Collection,它本身也有自己的继承体系,但实际官方只给出了两个子的继承子接口,一个是ListIterator一个是PrimitiveIterator。这里就见到一个。

另外有一点说明,我记得之前在看java核心技术的时候记得里面详细说明过java迭代器的实现方式,应该说的就是这里。如果希望看到更权威的说法可以参考下java核心技术。个人认为java核心技术对这里的描述还是比较彻底而清晰的。

我们先介绍基本的Itr实现:

Itr是实际上是一个内部类。主要来说有三个有意思的部分来说,从这三个部分也基本描述了迭代器的使用方式:

  1. 如何判断当前迭代器的位置,及如何移动?
  2. 如何对迭代的元素进行删除,及删除后的影响?
  3. 如何防止多个迭代器之间的相互影响?

1. 如何判断当前迭代器的位置,及如何移动?

java的迭代器和C++中的指针类型的迭代器有很大的不同。主要区别在于两大点。

一是java的迭代器是无法将随机访问的,也就是说不可以根据输入的数字来跳转到指定的位置,迭代器到达任何一个位置都需要经过next或者可能存在的previous方法实现,只能一个一个的移动。

二是迭代器并不是指向一个元素的,而是指向元素之间的。迭代器初始化之后再0号元素之前,迭代到尾之后是在最后一个元素之后,期间任何一点都是指向两个元素之间的位置。

因此在每次调用迭代器之前通常都需要进行查询即使用hasNext方法确定是否已经迭代到尾,如果迭代到尾就返回false。如果已经迭代到尾还强行的运行next则会抛出异常。

2. 如何对迭代的元素进行删除,及删除后的影响?

由于java的迭代器没有办法进行随机访问,因此如果需要删除元素也只能删除刚刚跳过的元素。这样也导致每次remove之前必定有一个next。如果连续进行两次remove则会抛出异常。因为并不知道删除的元素是哪一个。

3. 如何防止多个迭代器之间的相互影响?

在使用迭代器的时候,如果一个迭代器正在迭代器另一个迭代器修改了元素这样可能会导致很严重的问题。java采用一个计数器来保证不会有两个迭代器同时修改这个对象,具体的部分等到实现来说明。通常来说如果迭代器在进行访问时,另一个迭代器结构性的修改了对象,则正在访问的迭代器会抛出异常。但是如果是多个迭代器只进行读操作就可以。当然也可以自定义的修改成多个读迭代器和一个写迭代器。这样可以提高访问速率。但是这个功能在AbstractList类中并没有实现,而是只提供了基本的迭代器功能。



以上说明完了迭代器的使用方法,这里就开始讨论迭代器是如何实现的:

 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        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 {
                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();
        }
    }
    protected transient int modCount = 0;

首先概述一下。这个类是private的内部类。因为是内部类因此拥有和内部变量相同的访问权限,对于AbstractList类的所有私有成员都有访问权限,更不用说protected和public,因此可以直接访问modCount而不需要加其他范围声明。而又因为是private所有所有变量都不需要加private等修饰词,因为无法从外部访问不需要用范围限定词。

1. 如何判断当前迭代器的位置,及如何移动?

首先对于每一个迭代器,保存了三个变量。一个是cursor,一个是lastRet,一个是expectedModCount。

cursor对于每次调用next方法就增加一次。并且将上一个cursor的值传给lastRet(用于可能的删除操作),将刚刚跳过的第i个元素作为返回值。这里得到刚刚跳过的元素的方法是使用了AbstractList类的get方法。

对于如何判断迭代到尾的hasNext方法,采用的是比较cursor的值和size的大小。如果两者相等则说明迭代到尾了。

2. 如何对迭代的元素进行删除,及删除后的影响?

上问说道了三个变量中lastRet,保存了每次跳过的元素的坐标,如果需要删除则只要调用remove就行。这里需要考虑的是为什么使用AbstractList.this.remove(lastRet);这样的句式。

这样的句式当然很好理解是使用的Abstract的remove方法。但是在使用而get的时候没有这样用。而且对于get方法并没有用这么复杂的句式。显然不可能是因为Itr类有自己的同名方法,因为方法的签名不一样。

一种可能的解释是:实际上来说AbstractList.this.remove才是标准的语法,但是为何这里使用但是get并没有使用,我才想可能是仅仅是因为这里不想给引起误解吧。但是在get和modCount 这些外部类的引用的时候不会引起误解,因此采用更为简洁的写法。但是内部类本身是一个比较trick的问题。很多问题我个人经验也不足,如果以后想到其他更为合理的解释再修改。

第二个需要说明的是程序在每次删除lastRet位的之前进行了判断,如果lastRet如果为-1,则抛出一个异常。

lastRet在两种情况下回变成-1,一个是删除完成后,一个是初始化的时候,两种情况也可以概括为一种情况,在调用remove之前没有执行next。

第三个小问题是还需要判断删除的迭代器是否是在cursor之前,如果是的话还需要将cursor减一。其实这个也很有意思。实际上这里应该是单向的迭代,为何还需要进行判断而不是直接减一。可能的解释是这里还有一个继承了Itr的ListItr,而它包含了向前迭代的可能,因此如何换成一个不包含向前迭代的实现就不需要判断了。因此这里的写法实际上是依赖于实现的。当然你可以选择在写ListItr方法的时候重写remove方法。不过后文可以看到这里的实现非常漂亮没有覆盖覆盖任何方法。

3. 如何防止多个迭代器之间的相互影响?

要实现多个迭代器之间的互不影响的访问,或者说如果有迭代器对对象进行了结构性的修改其他迭代器可以立即检测出问题并抛出异常(也就是所谓的fail-fast),这里采用的是这样的方式:

首先对每个需要迭代的对象(这里就是ArrayList对象)保存一个modCount的数字。这个数字在每次迭代器进行结构性修改的时候加1。换句话说,这个变量表示的是对象的修改次数,因此需要放在Itr类外面,AbstractList类里面。此外这个变量是transient的,也就说序列化的时候是不需要储存的。

其次对每个迭代器保存一个expectedModCount ,来记录这个迭代器对对象进行结构性修改的次数。这样每次迭代器进结构性修改的时候都将expectedModCount 和modCount进行对比,如果两种相等则说明没有其他迭代器修改了对象,可以进行。如果不相等则说明有迭代进行了修改,立刻抛出异常。

需要说明的是这里实现的迭代器只能进行多个读访问,如果适当修改可以实现多个读和一个写共存的情况,但是这里没有给出实现,如果以后看到了会补充进来。


ListItr

Itr看完了其实ListItr就没有什么特别需要说明的了。只是继承了继承了Itr,并且增加了几个方法个人认为并没有特别需要说明的。基本方法和Itr的方法类似。

但是有一个很有意思的问题是。为何在AbstractList中同时实现了Itr和ListItr。因为很显然Itr实现的是Iterator接口,按道理说应该在更早的Collection集合中实现。我自己考虑了两种可能的原因

第一个是实际上在整个java实现中抽象类里面,这里指直接继承自Collection类的几个抽象类中。AbstractList是唯一一个实现了Itr的类。其他的抽象类并没有这么复杂。因此如果在Collection类中实现了,那就无法使用private的内部类,这样可能会破坏封装性。但是如果不使用private就没办法使用ListItr继承Collection中的继承类。这样会很麻烦。

第二个问题是实际上Itr的实现还是依赖具体实现的。比如说如果在不可寻址的地方无法使用get或者remove删除指定的cursor,因此其他地方仅仅给出接口就行不需要具体实现,具体实现需要结合实际情况来看。

当然以上作为个人猜测。

时间: 2024-10-08 08:25:44

日常小结-java-AbstractList-Itr和ListItr的实现的相关文章

equals小结 java

一.什么是equals方法 Equals方法是Object类中(所有的类都有equals这个方法)提供定义对象是否相等的逻辑. 方法如下: public boolean equals(Object obj) { return (this == obj); } 调用方法:x.equals(y):当x和y是同一个对象的应用时,返回true,否则返回false. 二.与"=="的关系? 在java中用"=="就可以比较两个对象是否相等,为什么还要用equals方法呢?对于

java.util.NoSuchElementException at java.util.AbstractList$Itr.next(AbstractList.java:350) at

我的解决方案是,我的struts2 标签中 自己 添加了一个 <s:select  list="#{'0':'待处理','1':'处理中'}"  readOnly="" headerKey="-1" headerValue="请选择" listKey="key" listValue="value" /> readOnly ,去掉即可.

数据类型与运算符小结(JAVA)

初步学习了Java的数据类型和运算符,小结一下! 四种变量 1.属性(定义在类里)实例变量2.静态属性(定义在类里且有static)类变量3.局部变量(定义在方法里)4.参数 定义变量 数据类型 变量名1.直接加分号(未初始化)2.=value; 变量命名(规则) 1.必须以字母,下划线_或美元符$开头,汉字可以,但不建议2.之后的部分可以是字母,下划线,美元符以及数字3.变量名长度可以无限长4.变量名不可以是java关键字eg:static,public,final ,this,new ,tr

日常小结-java-AbstractCollection

AbstractCollection 概述 概述首先AbstractCollection是java自己提供的一个最基本的Collection的实现.当然它依然是一个抽象类. 对于一个不可更改的集合,只要继承这个类并且实现迭代器和size()方法就行. 对于一个可更改的集合,需要实现add和返回Iterator的方法,当然可选的实现remove方法 通常应该提供两个构造器,一个无参的,一个是包含集合元素的 public Object[] toArray() API文档 这个方法返回一个包含集合内所

Enum 枚举小结 java **** 最爱那水货

1 import java.util.HashMap; 2 import java.util.Map; 3 4 /** 5 * 收单行 大写首字母 和对应的编码<br/> 6 * 7 * ABC 农业银行<br/> 8 BC 中国银行<br/> 9 CBC 建设银行<br/> 10 CITIC 中信银行<br/> 11 CMBC 招商银行<br/> 12 HSBC 汇丰银行<br/> 13 ICBC 工商银行<br/

浅谈并小结java内存泄漏

一.定义 首先什么是内存泄漏,简单点说就是用完了忘了回收,而其他对象等资源想用却没法用的一种"站着茅坑不拉屎"的浪费资源的情况.在C/C++中,多数泄漏的场景就是程序离开某一运行域时,如在某个方法体中new出的对象或者malloc出的结构体等,并且只有该方法体中的局部变量指向这块内存区域时,在该方法返回时,存在栈中的局部变量指针随着栈帧被一起销毁,那么就没有任何指针可以指向该内存区域了,那么这块内存区域便是泄漏了. 而java的内存泄漏呢?众所周知,java的内存回收是由gc管理的.g

hibernate日常小结和优化

1.对于类之间是依赖不是关联关系的类之间的数据库设计,最好采用精粒度对象模型,建立组件模型,不是采用多对一什么的关联.

C语言 日常小结

1.当数组当作函数参数的时候会退化为指针 #include<stdio.h> #include<stdlib.h> void sort(int a[]){ int num = sizeof(a); printf("数组的大小num=%d\n", num);//打印4,此时a是一个指针 //打印数组 for (int i = 0; i < 10; i++) { printf("%5d", a[i]); } } void main(){ i

ansible日常小结

一.ansible优化 vim /etc/ansible/ansible.cfg host_key_checking = False #不进行验证 log_path = /var/log/ansible.log #打开日志 基于ssh vim /etc/ssh/sshd_config UseDNS no #重启sshd # systemctl restart sshd 二.常用模块 1.coyp 模块 ansible date -m copy -a 'src=/etc/selinux/confi