Java之ArrayList源码分析(第五篇:遍历元素)

ArrayList的遍历方式 

1、普通for循环

for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

2、增强for循环(foreach用法)

for (String str : list) {
    System.out.println(string);
}

3、迭代器(其中之一)

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

说明:增强for循环与迭代器方式都调用了ArrayList的iterator方法,另外ArrayList不止iterator方法可以返回迭代器对象,还有两个listIterator方法(包含重载方法),以及一个spliterator方法都返回的迭代器对象

 

注意:本篇仅分析iterator()方法的相关实现(listIterator()方法、spliterator()方法将在其他文章中进行分析)

 

iterator()方法分析

    public Iterator<E> iterator() {
        return new Itr();
    }

用于返回一个迭代器对象的方法,迭代器对象可以用来遍历ArrayList的元素,该方法无需传入参数

ArrayList实现了List接口、List接口继承了Collection接口、Collection接口又继承了Iterable接口,在Iterable接口中定义了可作为迭代器的要求,这个iterator()方法会返回一个Iterator对象(迭代器对象表示迭代器),表示实现Iterable接口的类可作为迭代器

这是ArrayList中重写后的iterator()方法,在iterator()方法中,直接返回了一个创建的Itr对象,接下来我们就继续学习Itr类的实现

 

ArrayList中的Itr类分析

private class Itr implements Iterator<E> {

            …………省略很多源码…………
}

Itr类定义在ArrayList的内部,它是作为普通内部类而存在的,Itr类实现了Iterator接口。Iterator接口规范了作为一个迭代器应该具备哪些能力,我们马上先学习Iterator接口

 

Iterator接口分析

public interface Iterator<E> {
    
    boolean hasNext();

    
    E next();

    
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

Iterator接口规范了作为迭代器对象应该具备的功能,它是一个范型接口

1、hasNext()方法

判断是否有更多元素的方法,如果存在更多元素则会返回true,否则就会返回false

2、next()方法

遍历过程中返回一个元素的方法,next方法在第一次被调用时,会返回第一个元素,注意该方法在ArrayList没有元素时被调用,规范要求会抛出一个NoSuchElementException对象

3、JDK1.8新增的default方法:remove方法

该方法是用于移除元素,默认实现则是抛出UnsupportedOperationException对象

4、JDK1.8新增的另一个default方法

forEachRemaining方法,该方法用于干什么的???没明白!!当为其传入的Consumer为null时,会抛出NullPointerException

 

Itr构造方法分析

        Itr() {}

用于创建对象,一个无参数的构造方法

 

Itr对象持有的实例变量

        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

1、cursor代表返回元素的下标,默认值是0

2、lastRet代表最后一次返回元素的下标,-1说明没有元素被返回

3、expectedModCount代表预期改变数量,它的默认值是ArrayList对象持有的modCount的值

 

hasNext()方法分析

        public boolean hasNext() {
            return cursor != size;
        }

位于Itr类中的hasNext()方法,用于判断是否还有下一个元素

size是ArrayList对象持有的元素总数,在hasNext()方法的内部将Itr对象持有的cursor与size进行比较,不相等代表还有未遍历的元素,此时该方法会返回true,如果cursor与size相等,说明没有更多元素可用于遍历,此时整个hasNext()方法会返回false

 

next()方法的具体实现

        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

位于Itr类中的next()方法,用于返回下一个元素,第一次调用该方法时,将返回ArrayList中的第一个元素

1、fail-fast机制,检查是否在多线程下使用ArrayList

开始先执行了checkForComodification()方法,执行fail-fast机制的检查,确保ArrayList未在多线程下使用,未通过检查,会在checkForComodification()方法中抛出ConcurrentModificationException对象,

2、定义局部变量保存cusor值

Itr对象持有的cursor赋值给局部变量i保存

3、检查局部变量i的值是否超出范围

对局部变量i进行检查,若i的值大于等于元素总数size,此处会抛出NoSuchElementException对象(这也是建议先使用hasNext()方法判断是否有未遍历完元素的原因,没有元素时会抛该异常)

4、定义局部变量,用于保存外部类ArrayList对象持有的底层数组对象

接着获取当前ArrayList对象持有的实例变量elementData(底层数组对象),然后赋值给局部变量elementData保存

5、检查cusor值是否大于等于ArrayList的底层数组对象容量,防止ArrayList在多线程下的错误使用

再一次做容错保护,局部变量i的值大于等于底层数组对象的长度length时会抛出ConcurrentModificatoinException对象,

6、更新遍历元素后的游标

为Itr对象持有的cursor执行加1操作(在当前cursor值基础上)

7、存储最后一次遍历元素的下标值

先将即将要返回元素的下标值保存到Itr对象持有的实例变量lastRet中

8、从lastRet下标处,取出元素,并强制类型转换为指定的参数类型E上

接着从局部变量elementData数组对象的指定下标处lastRet处取出元素对象,并且又做了一个强制的向下转型为参数类型E的对象(因为elementData指向的是一个Object数组对象,每个元素的类型是Object),最后就是return元素对象

 

checkForComodification()方法分析

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

位于Itr类中的checkForComodification()方法,用于检查modCount值与expectedModCount是否相等,可以防止ArrayList在多线程使用,每当modCount与expectedModCount不相等时,就会抛出ConcurretnModificationException对象

 

remove()方法分析

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

位于Itr类中的remove()方法,重写了Iterator接口中定义的default的remove()方法,用于移除遍历过的最后一个元素

1、检查lastRet值

若lastRet小于0时,说明ArrayList并没有遍历过任何元素,lastRet的默认值是-1,或者ArrayList压根就没有持有元素,此处针对lastRet小于0的情况,会抛出一个IllegalStateException对象

2、检查fail-fast机制,防止多线程下使用ArrayList

调用Itr中的checkForComodification()方法的执行(搜索本文即可)

3、通过两个检查后,获取当前ArrayList对象,并调用ArrayList自身的remove()方法(在删除元素文章中的remove()方法),并为其传入Itr对象持有的lastRet值

4、更新Itr对象持有的cursor值

成功删除元素后,更新Itr对象持有的cursor值为lastRet值(因为后继元素已经前移,所以cursor需要回退为lastRet值)

5、重置lastRet值

将lastRet值重置为-1,这里充分说明Itr对象的remove()方法绝对无法连续调用两次,因为lastRet为-1时,将会抛出IllegalStateException对象。

6、更新用于fail-fast机制用的expectedModeCount值

接着更新Itr对象持有的expectedModeCount值,为ArrayList对象持有的modCount值,remove()操作已经改变了modCount值,这也是为何Itr的remove()方法不能在多线程下安全删除元素的原因。

7、捕获可能抛出的IndexOutOfBoundsException对象

在这里为什么要捕获一个IndexOutOfBoundsException对象呢?因为ArrayList的remove()方法,有一个rangeCheck()方法,这个rangeCheck()方法会在下标不符合要求时抛出IndexOutOfBoundsException对象,此处捕获该异常对象后,会再抛出一个ConcurrentModificationExcepton对象,因为下标值出现错误的原因是,在多线程下对ArrayList进行了并发修改

 

forEachRemaining()方法分析

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

位于Itr类中重写的forEachRemaining()方法,Itr实现的Iterator接口中定义了default的forEachRemaining方法!传入的参数为一个Consumer对象

1、检查传入的参数consumer

使用Objects的静态方法requireNonNull检查传入的consumer对象是否为null,对传入的对象是否要求的,必须不能为null,否则抛出异常

2、定义局部变量存储Array对象当前持有元素的总数

获取当前ArrayList对象持有的size,size表示元素总数,暂时由final修饰的一个同名的局部变量size保存

3、定义局部变量保存当前Itr遍历元素后的游标值

局部变量i负责保存当前Itr对象持有的cursor值

4、检查游标值cursor

做一个游标值的容错保护,局部变量i表示的游标值如果大于等于size值,直接调用return,此时整个方法运行结束

5、定义局部变量保存ArrayList对象持有的底层数组elementData

接着获取当前ArrayList对象持有的底层数组对象elementData,并交给final修饰的同名的局部变量elementData变量保存(为啥作者喜欢用同名啊,多容易让人懵逼)

6、检查游标值是否超出范围

接着对局部变量i的值进行检查,如果i值大于等于elementData数组的长度时,表示游标值超出范围,通过抛出ConcurrentModificationException对象用于提示用户不要在多线程下使用ArrayList

7、将ArrayList持有的元素对象一个一个的传递到传入的Consumer对象中

开始while循环,在while的代码块中,则回调了Consumer对象的accept()方法,每次都会传入一个ArrayList持有的元素对象(做了强转),while循环的终止条件有两个:

a、一个是局部变量i与size相等

b、另一个是modCount不等于expectedModCount时

只要有一个条件不达标,终止while循环!

8、打扫战场

while循环结束后,则是一个什么操作?(作者注释:在迭代结束时更新一次,以减少堆写流量)……更新Itr对象持有的cursor值为局部变量i的值,更新Itr对象持有的lastRet的值为i-1

9、再一次执行fail-fast机制,防止多线程下使用ArrayList

最后又执行了一次checkForComodification()方法

Consumer是一个接口!这个forEachRemaining()方法到底是干甚的?为剩余的元素执行一个特殊的业务逻辑,去实现Consumer接口确实可以办的到,拭目以待!

 

Objects的requreNonNull()方法

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

用于检查某个对象是否为null,为null,直接抛出NullPointerException对象,不为null则直接返回传入的对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值