首页 > 编程语言 >面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?

面试官不按套路出牌,上来就让聊一聊Java中的迭代器(Iterator ),夺命连环问,怎么办?

时间:2024-02-22 09:03:01浏览次数:36  
标签:面试官 遍历 Java Iterator 迭代 ArrayList str

写在开头

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

Iterator (迭代器)

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

【源码解析1】

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());
	}
}

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

【代码示例1】

public class Test {
    public static void main(String[] args) {
        ArrayList<String> str = new ArrayList<>();
        str.add("aaa");
        str.add("bbb");
        str.add("ccc");
        //迭代器遍历
        Iterator it = str.iterator();
        while (it.hasNext()) {
            System.out.print(it.next() + ",");
        }
    }
}

输出:

aaa,bbb,ccc,

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

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

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

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

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

【源码解析2】

public interface Collection<E> extends Iterable<E> {
	...
	Iterator<E> iterator();
	...

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

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

【源码解析3】

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
        int expectedModCount = modCount;

        Itr() {}

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

        @SuppressWarnings("unchecked")
        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];
        }

        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();
            }
        }

        @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();
        }

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

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

【代码示例2】

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

【反编译】

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

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

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
}

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

结尾彩蛋

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

标签:面试官,遍历,Java,Iterator,迭代,ArrayList,str
From: https://www.cnblogs.com/JavaBuild/p/18026569

相关文章

  • idea的Java项目打成jar包并服务器部署运行
    idea的Java项目打成jar包并服务器部署运行文章目录一、idea对Java项目打jar包二、上传jar包到服务器运行一、idea对Java项目打jar包1.File—>ProjectStructure---->Artifacts2.点击+号选择JAR,再选择第二个3.出现这个框,第一个Module是你最后jar包的名字,第二个是Ma......
  • JavaScript 的新数组分组方法
    对数组中的项目进行分组,你可能已经做过很多次了。每次都会手动编写一个分组函数,或者使用lodash的groupBy函数。好消息是,JavaScript现在有了分组方法,所以你再也不必这样做了。Object.groupBy和Map.groupBy这两个新方法将使分组变得更简单,并节省我们的时间或依赖性。以前......
  • hash表与java集合框架
       1.对象被new出来后,会通过哈希函数Hash(key)建立关键字key与存储地址Addr的关系,Addr=Hash(Key),这个Addr又称HashCode。即:每个对象有唯一的HashCode。-另外:底层对对象进行hash时因为hash表长度有限所以存储可能会有冲突,为了解决冲突而在顺序表的某个存储地址上加......
  • 深入剖析 Java 类属性与类方法的应用
    Java类属性Java类属性,也称为字段,是类中的变量。它们用于存储与类相关的数据。创建类属性在类定义中声明属性:publicclassMain{intx;//属性Stringname;//属性}访问类属性使用点语法访问对象的属性:MainmyObj=newMain();myObj.x=5;//设置属性值Sy......
  • Java虚拟机小记
    目录运行时数据区域Java堆对象创建对象的内存布局对象的访问定位句柄直接指针GC判断对象是否已死引用计数算法可达性分析算法引用的类别垃圾收集算法分代收集理论标记清除算法标记复制算法标记整理算法实现细节并发的可达性分析垃圾收集器serial收集器ParNew收集器ParallelScaven......
  • idea创建spring项目的时候只有java 21和17
    1.问题我们在用IDEA创建一个spring项目时,发现java版本只能选用java21,java17,导致我们的jdk版本无法选择jdk1.8(我最常用的版本)2.解决参考:idea创建项目的时候只有java21和17原因是spring2在23年11月24日停止维护了,所以通过spring来创建,没有spring2,只有spring3+,最低jdk版本也是1......
  • Java复习
    目录Java面向对象程序设计(madebyzhoujin)第一章Java开发入门Java语言的优点什么是JDK?:SUN公司提供的一套Java开发环境,是整个java的核心,包括Java编译器、Java运行工具、Java文档生成工具、Java打包工具等JDK安装目录介绍用命令窗口开发java程序两个系统环境变量:path和classpath......
  • java运算符(赋值,关系,逻辑,三目,位运算)
    赋值运算符赋值运算符:=+=-=*=/=%=publicclassFuZhiOptDemo{publicstaticvoidmain(Strng[]args){inta1=3;a1+=2;System.out.println("a1:"+a1);a1-=2;System.out.println("a1:"+a1);......
  • 阿里面试:Java开发中,应如何避免OOM
    Java内存管理:避免OOM的10个实用小技巧引言在Java开发中,OutOfMemoryError(OOM)错误一直是令开发者头疼的问题,也是Java面试中出现核心频率很高的问题。那么我们究竟怎么样才能够有效正确的管理内存,日常开发中究竟要注意哪些核心技巧来避免OOM错误。本文将带大家一起学习10个避免OO......
  • 04-JavaScript介绍
      注意:JS是脚本语言,不需要经过编译,直接经过浏览器的解释就可以运行了。而Java语言需要先编译成class文件,再通过虚拟机进行运行。  <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>JS-引入方式</title></head><body>......