经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?
来源:cnblogs  作者:JavaBuild  时间:2024/2/23 8:59:51  对本文有异议

写在开头

某大厂的面试现场,一位目光深邃,头顶稀疏的中年面试官坐在椅子上,这时候的我走了进来。
面试官:“小伙子,学过Java中容器和数据结构了吧?”
我:“嗯,学了”
面试官:“ok,那你来聊一聊Java中的迭代器(Iterator ),要说清楚他们的应用场景哈”
我:“哦,好滴”
内心独白:“这面试官不按套路出牌啊,本来以为会问问ArrayList,HashMap呢,或者手撕排序算法,这上来直接让撕迭代器”
虽然面试官不按套路出牌,但这时我们也不能乱,迅速的平复心情后,大脑飞速运转,回想着之前学的内容,其实迭代器和比较器确实在容器和数据结构中有所体现。

Iterator (迭代器)

在解释迭代器之前,我们先来聊一下23种设计模式之一:迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
而Iterator则是在这种设计思想下诞生的产物,Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。

【源码解析1】

  1. public interface Iterator<E> {
  2. //是否有下一个元素
  3. boolean hasNext();
  4. //下一个元素
  5. E next();
  6. //从迭代器指向的集合中删除迭代器返回的最后一个元素
  7. default void remove() {
  8. throw new UnsupportedOperationException("remove");
  9. }
  10. //遍历所有元素
  11. default void forEachRemaining(Consumer<? super E> action) {
  12. Objects.requireNonNull(action);
  13. while (hasNext())
  14. action.accept(next());
  15. }
  16. }

那我们日常使用中如何通过迭代器去遍历集合中的数据呢?我们接下往下看(一步一步耐心的给面试官解释,不要紧张,保持逻辑清楚!)

【代码示例1】

  1. public class Test {
  2. public static void main(String[] args) {
  3. ArrayList<String> str = new ArrayList<>();
  4. str.add("aaa");
  5. str.add("bbb");
  6. str.add("ccc");
  7. //迭代器遍历
  8. Iterator it = str.iterator();
  9. while (it.hasNext()) {
  10. System.out.print(it.next() + ",");
  11. }
  12. }
  13. }

输出:

  1. aaabbbccc

我们创建一个包含ArrayList容器,里面包含aaa,bbb,ccc元素,通过调用str对象的iterator()方法去遍历元素,然后将遍历的元素打印出来,如上输出(这部分可以手撕给面试官看,方便后续的展开阐述)

面试官:“那你知道为什么ArrayList可以调用迭代器方法吗,底层逻辑有没有看过?”

当面试官问到这个问题的时候,我们心中一喜,因为他成功的被我们引导到了我们熟悉的方向上,那么接下来,我们要好好唠一唠了!

我:“嗯,之前学习的时候,有跟过这部分底层源码,我说说看,不对的麻烦您多给指正哈”

我们知道Collection是Set、List、Queue的父接口,而Collection接口又继承了另外一个接口,叫Iterable,得到了它的一个接口方法iterator()。

【源码解析2】

  1. public interface Collection<E> extends Iterable<E> {
  2. ...
  3. Iterator<E> iterator();
  4. ...

而对于我们日常使用的集合类来说,如ArrayList,它的继承关系让它可以得到iterator对象,我们可以画一个流程图来分析一下。

ArrayList中重写了AbstractList中的iterator()方法,并返回一个内部类对象Itr,我们看一下这个内部类的实现源码。

【源码解析3】

  1. private class Itr implements Iterator<E> {
  2. int cursor; // index of next element to return
  3. int lastRet = -1; // index of last element returned; -1 if no such
  4. int expectedModCount = modCount;
  5. Itr() {}
  6. public boolean hasNext() {
  7. return cursor != size;
  8. }
  9. @SuppressWarnings("unchecked")
  10. public E next() {
  11. checkForComodification();
  12. int i = cursor;
  13. if (i >= size)
  14. throw new NoSuchElementException();
  15. Object[] elementData = ArrayList.this.elementData;
  16. if (i >= elementData.length)
  17. throw new ConcurrentModificationException();
  18. cursor = i + 1;
  19. return (E) elementData[lastRet = i];
  20. }
  21. public void remove() {
  22. if (lastRet < 0)
  23. throw new IllegalStateException();
  24. checkForComodification();
  25. try {
  26. ArrayList.this.remove(lastRet);
  27. cursor = lastRet;
  28. lastRet = -1;
  29. expectedModCount = modCount;
  30. } catch (IndexOutOfBoundsException ex) {
  31. throw new ConcurrentModificationException();
  32. }
  33. }
  34. @Override
  35. @SuppressWarnings("unchecked")
  36. public void forEachRemaining(Consumer<? super E> consumer) {
  37. Objects.requireNonNull(consumer);
  38. final int size = ArrayList.this.size;
  39. int i = cursor;
  40. if (i >= size) {
  41. return;
  42. }
  43. final Object[] elementData = ArrayList.this.elementData;
  44. if (i >= elementData.length) {
  45. throw new ConcurrentModificationException();
  46. }
  47. while (i != size && modCount == expectedModCount) {
  48. consumer.accept((E) elementData[i++]);
  49. }
  50. // update once at end of iteration to reduce heap write traffic
  51. cursor = i;
  52. lastRet = i - 1;
  53. checkForComodification();
  54. }
  55. final void checkForComodification() {
  56. if (modCount != expectedModCount)
  57. throw new ConcurrentModificationException();
  58. }
  59. }

讲到这里,我们可以给一个阶段性的总结:所以在继承了Collection接口,并实现了iterator()方法的所有集合类,都可以使用迭代器进行元素的遍历。
【温馨提示】
若此时的你足够强大,足够自信,还可以进一步引出增强for循环遍历,它的底层原理也是Iterator

【代码示例2】

  1. for (String str : list) {
  2. System.out.print(str + ",");
  3. }

【反编译】

  1. Iterator var3 = list.iterator();
  2. while(var3.hasNext()) {
  3. String str = (String)var3.next();
  4. System.out.print(str + ",");
  5. }

反编译后我们可以看得出,底层的实现就是迭代器,而这个for-each的写法不过是Java的一个语法糖罢了,这部分属于附加题,讲不明白的,可以不提。
本来以为迭代器这个话题到此就结束了,没想到面试官紧接着又抛出了一个问题
面试官:“LinkedList也是如此吗?”
听到这里咱们心里一紧,他终究是要挖光我们的家底呀
确实!LinkedList有所不同,LinkedList 并没有直接重写 Iterable 接口的 iterator 方法,而是由它的父类 AbstractSequentialList 来完成。
进入源码会发现,这个AbstractSequentialList 中提供了一个listIterator对象,而LinkedList进行了方法的重写。
【源码解析4】

  1. public interface ListIterator<E> extends Iterator<E> {
  2. boolean hasNext();
  3. E next();
  4. boolean hasPrevious();
  5. E previous();
  6. }

这让它拥有了可以从任意下标开始遍历,而且支持双向遍历的能力。注意ListIterator只支持List类型集合。
到此,我们对于Iterator的了解全盘拖出了,当然还有一些细枝末节的知识,但我相信能回答到这里,已经获得面试官的认可啦。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

原文链接:https://www.cnblogs.com/JavaBuild/p/18026569

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号