关于java:奇葩java迭代器笔试题很少有人能做对

6次阅读

共计 4065 个字符,预计需要花费 11 分钟才能阅读完成。

有位小朋友最近正在为年后换工作做筹备,然而遇到一个问题,感觉很不堪设想的一道口试题。而后我把这道题发到技术群里,发现很多人竟然不晓得,很多都是连蒙带猜的说。感觉很有必要写一篇文章来说道说道。

奇怪的口试题

浏览上面这段代码,请写出这段代码的输入内容:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
​
public class Test {public static void main(String[] args) {
​
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {String str = (String) iterator.next();
            if (str.equals("2")) {iterator.remove();
            }
        }
        while (iterator.hasNext()) {System.out.println(iterator.next());
        }
        System.out.println("4");
    }
}

他写进去的答案是:

1
3
4

奇怪的是,你把这道题目发给你身边人,让他们答复这道面试题输入后果是什么,说这个后果的人十分多。不行你试试

~

答案显著不对,因为在第一个 while 里的 iterator.hasNext()==false 后才会到第二个 while 里来,同一个 Iterator 对象,后面调一次 iterator.hasNext()==false,再判断一次后果不还是一样吗?,

所以第二个 while 判断为 false,也就不会再去遍历 iterator 了,由此可知本体答案是:4。

上面咱们来剖析一下为什么是具体底层是怎么实现的。

这里的 Iterator 是什么?

  • 迭代器是一种模式、具体可见其设计模式,能够使得序列类型的数据结构的遍历行为与被遍历的对象拆散,即咱们无需关怀该序列的底层构造是什么样子的。只有拿到这个对象, 应用迭代器就能够遍历这个对象的外部
  • Iterable 实现这个接口的汇合对象反对迭代,是能够迭代的。实现了这个能够配合 foreach 应用~
  • Iterator 迭代器,提供迭代机制的对象,具体如何迭代是这个 Iterator 接口标准的。

Iterator 阐明

public interface Iterator<E> {// 每次 next 之前,先调用此办法探测是否迭代到起点     boolean hasNext();
    // 返回以后迭代元素,同时,迭代游标后移     E next(); 
    /* 删除最近一次已近迭代出进来的那个元素。只有当 next 执行完后,能力调用 remove 函数。比方你要删除第一个元素,不能间接调用 remove()   而要先 next 一下(); 在没有先调用 next 就调用 remove 办法是会抛出异样的。这个和 MySQL 中的 ResultSet 很相似 */
    default void remove() {throw new UnsupportedOperationException("remove");
    } 
    default void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

这里的实现类是 ArrayList 的外部类 Itr。

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return         int lastRet = -1; // index of last element returned; -1 if no such         //modCountshi ArrayList 中的属性,当增加或删除的时候 moCount 值会减少或者缩小         // 这里次要是给 fail-fast 应用,防止一遍在遍历,一遍正在批改导致数据出错         // 此列表在结构上被批改的次数。构造批改是指扭转构造尺寸的批改列表,// 或者以这样的形式对其进行扰动, 提高可能会产生谬误的后果。int expectedModCount = modCount;
​
        public boolean hasNext() {//cursor 初始值为 0,没掉一次 next 办法就 +1             //size 是 ArrayList 的大小             return cursor != size;}
​
        @SuppressWarnings("unchecked")
        public E next() {checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            // 把 ArrayList 中的数组赋给 elementData             Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            // 每调用一次 next 办法,游标就加 1             //cursor=lastRet+1             cursor = i + 1;
            // 返回 ArrayList 中的元素             return (E) elementData[lastRet = i];
        }
​
        public void remove() {if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
​
            try {// 调用 ArrayList 中 remove 办法,溢出该元素                 ArrayList.this.remove(lastRet);
                //cursor=lastRet+1,// 所以此时相当于 cursor=cursor-1                 cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {if (modCount != expectedModCount)
                throw new ConcurrentModificationException();}
}

再回到下面题目中:

第一个 iterator.hasNext()

第 1 次循环

  • hasNext 办法中:cursor==0,size==3,所以 cursor != size 返回 true。
  • next 办法中:cursor=0+1。返回 ”1″。

第 2 次循环

  • hasNext 办法中:cursor==1,size==3,所以 cursor != size 返回 true。
  • next 办法中:cursor=1+1。返回 ”2″。
  • remove 办法中:cursor==cursor-1==2-1=1,把 ArrayList 中的 ”2″ 给删除了,所以 size==2。

第 3 次循环

  • hasNext 办法中:cursor==1,size==2,那么 cursor != size 返回 true。
  • next 办法中:cursor=1+1==2;返回 ”3″。

第 4 次循环

  • hasNext 办法中:cursor==2,size==2,那么 cursor != size 返回 false。

第二个 iterator.hasNext()

hasNext 办法中:cursor==2,size==2,所以 cursor != size 返回 false。

所以,最初只输入 ”4″,即答案为 4.

Iterator 与泛型搭配

  • Iterator 对汇合类中的任何一个实现类,都能够返回这样一个 Iterator 对象。能够实用于任何一个类。
  • 因为汇合类 (List 和 Set 等) 能够装入的对象的类型是不确定的, 从汇合中取出时都是 Object 类型, 用时都须要进行强制转化, 这样会很麻烦, 用上泛型, 就是提前通知汇合确定要装入汇合的类型, 这样就能够间接应用而不必显示类型转换. 十分不便.

foreach 和 Iterator 的关系

  • for each 以用来解决汇合中的每个元素而不必思考汇合定下标。就是为了让用 Iterator 简略。然而删除的时候,区别就是在 remove,循环中调用汇合 remove 会导致原汇合变动导致谬误,而应该用迭代器的 remove 办法。

应用 for 循环还是迭代器 Iterator 比照

  • 采纳 ArrayList 对随机拜访比拟快,而 for 循环中的 get()办法,采纳的即是随机拜访的办法,因而在 ArrayList 里,for 循环较快
  • 采纳 LinkedList 则是程序拜访比拟快,iterator 中的 next()办法,采纳的即是程序拜访的办法,因而在 LinkedList 里,应用 iterator 较快
  • 从数据结构角度剖析,for 循环适宜拜访程序构造, 能够依据下标疾速获取指定元素. 而 Iterator 适宜拜访链式构造, 因为迭代器是通过 next()和 Pre()来定位的. 能够拜访没有程序的汇合.
  • 而应用 Iterator 的益处在于能够应用雷同形式去遍历汇合中元素,而不必思考汇合类的外部实现(只有它实现了 java.lang.Iterable 接口),如果应用 Iterator 来遍历汇合中元素,一旦不再应用 List 转而应用 Set 来组织数据,那遍历元素的代码不必做任何批改,如果应用 for 来遍历,那所有遍历此汇合的算法都得做相应调整, 因为 List 有序,Set 无序, 构造不同, 他们的拜访算法也不一样.(还是阐明了一点遍历和汇合自身拆散了)。

总结

  • 迭代进去的元素都是原来汇合元素的拷贝。
  • Java 汇合中保留的元素本质是对象的援用,而非对象自身。
  • 迭代出的对象也是援用的拷贝,后果还是援用。那么如果汇合中保留的元素是可变类型的,那么能够通过迭代出的元素批改原汇合中的对象。
正文完
 0