Java题集 - Iterator迭代器和集合体系结构面试题总结

01. 迭代器 Iterator 是什么?

Iterator则主要用于遍历Collection集合中的元素,Iterator对象也被称为迭代器。

02. 迭代器 Iterator 有什么特点?

Iterator 迭代器的特点是更加安全,它可以保证当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次next方法返回的集合元素才可以;否则将会引发java.util.Concurrent ModificationException异常。

Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发ConcurrentModificationException异常,这样可以避免共享资源而引发的潜在问题。

03. 迭代器 Iterator 怎么使用?

Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。

在这里插入图片描述

使用iterator()方法要求容器返回一个Iterator 对象:

(1) 当创建指向集合的 Iterator 对象后,指针指向第一个元素;

(2) 调用 hasNext() 方法:判断集合中是否还有元素;

(3) 调用 next() 方法:返回指针指向的元素,并将光标向下移动;

(4) 调用 remove() 方法:一般和next()方法一起用,用来删除next()方法返回的元素;

ArrayList集合中的迭代器源码分析:

private class Itr implements Iterator<E> {
    // 下一个返回元素的索引
    int cursor;   
    // 最后一个返回元素的索引
    int lastRet = -1;  
    // 将集合实际修改次数赋值给预期修改次数:调用add()方法,remove()方法时modCount都会加1
    // 由于expectedModCount是Itr的成员变量,那么只会被赋值一次!!!
    // 在迭代的过程中,只要实际修改次数和预期修改次数不一致就会产生并发修改异常
    int expectedModCount = modCount;

    Itr() {}

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

   // 调用next() 方法时会先返回光标处的元素,然后将光标向下移动
    @SuppressWarnings("unchecked")
    public E next() {
        // 集合迭代器的快速失败机制
        checkForComodification();
        int i = cursor;
        // 将光标赋值给i
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 光标向下移动
        cursor = i + 1;
        // 返回索引i位置的元素
        // 将最后一个返回的元素索引lastRet设置为i
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        // 集合迭代器的快速失败机制
        checkForComodification();

        try {
            // 移除调用next()方法获取的元素
            ArrayList.this.remove(lastRet);
            // 将光标指向删除元素处
            cursor = lastRet;
            lastRet = -1;
            // 将集合实际修改次数赋值给预期修改次数
            // 因此在迭代的过程中调用迭代器的remove()方法不会抛出异常
            // 而调用集合的remove()方法会抛出ConcurrentModificationException异常
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

04. 如何边遍历边移除 Collection 中的元素?

① 《阿里巴巴编码规范》中的一条:不要在foreach循环里进行元素的remove/add操作,使用foreach循环进行删除则会抛出ConcurrentModificationException异常。

public class Main {
    public static void main(String[] args) {
        DepartmentMemberInput departmentMemberInput1 
            = new DepartmentMemberInput("张三","18767880909");
        DepartmentMemberInput departmentMemberInput2 
            = new DepartmentMemberInput("张四","18767880910");

        List<DepartmentMemberInput> list1 = new ArrayList<>();
        list1.add(departmentMemberInput1);
        list1.add(departmentMemberInput2);

        for (DepartmentMemberInput departmentMemberInput : list1) {
            if(departmentMemberInput.getName().equals("张三")){
                list1.remove(departmentMemberInput);
            }
        }
    }
}

执行结果:

在这里插入图片描述

② 不要在 iterator 遍历的过程中使用 list.remove() 方法:

public class Main {
    public static void main(String[] args) {
        DepartmentMemberInput departmentMemberInput1 
            = new DepartmentMemberInput("张三","18767880909");
        DepartmentMemberInput departmentMemberInput2 
            = new DepartmentMemberInput("张四","18767880910");

        List<DepartmentMemberInput> list1 = new ArrayList<>();
        list1.add(departmentMemberInput1);
        list1.add(departmentMemberInput2);

        for (Iterator iterator = list1.iterator(); iterator.hasNext();) {
            DepartmentMemberInput departmentMemberInput = (DepartmentMemberInput) iterator.next();
            if(departmentMemberInput.getName().equals("张三")){
                // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                list1.remove(departmentMemberInput);
            }
        }
    }
}
public class IteratorErrorTest
{
    public static void main(String[] args)
    {
        // 创建一个集合
        Collection books=new HashSet();
        books.add("轻量级Java EE企业应用实战");
        books.add("Java讲义");
        // 获取books集合对应的迭代器
        Iterator it=books.iterator();
        while(it.hasNext())
        {
            String book=(String)it.next();
            System.out.println(book);
            if (book.equals("疯狂Android讲义"))
            {
                //使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                books.remove(book);
            }
        }
    }
}

执行结果:

在这里插入图片描述

③ 推荐在 iterator 遍历的过程中使用iterator.remove() 方法:

public class Main {
    public static void main(String[] args) {
        DepartmentMemberInput departmentMemberInput1 
            = new DepartmentMemberInput("张三","18767880909");
        DepartmentMemberInput departmentMemberInput2 
            = new DepartmentMemberInput("张四","18767880910");

        List<DepartmentMemberInput> list1 = new ArrayList<>();
        list1.add(departmentMemberInput1);
        list1.add(departmentMemberInput2);

        for (Iterator iterator = list1.iterator(); iterator.hasNext();) {
            DepartmentMemberInput departmentMemberInput = (DepartmentMemberInput) iterator.next();
            if(departmentMemberInput.getName().equals("张三")){
                iterator.remove();
            }
        }
    }
}

05. Iterator 和 ListIterator 有什么区别?

(1) Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

(2) Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

(3) ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引等等。

06. 数组和集合的区别?

数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。

07. 常见集合类的继承体系?

Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

在这里插入图片描述

Set和List接口是Collection接口派生的两个子接口,它们分别代表了无序集合和有序集合;Queue是Java提供的队列实现,有点类似于List。

在这里插入图片描述

08. 常见集合类的特点?

在这里插入图片描述

在这里插入图片描述

09. 常见集合类的底层数据结构?

集合实现类集合底层数据结构是否线程安全
ArrayList数组非线程安全
LinkedList双向链表非线程安全
Vector数组线程安全,效率较低,使用少
HashSetHashMap非线程安全
TreeSetTreeMap非线程安全
HashMap哈希表非线程安全
HashTable哈希表线程安全,所有的方法都有synchronized关键字,效率较低,使用少
PropertiesHashMap线程安全,key和value只能存储String字符串
TreeMap红黑树非线程安全

10. Collection 和 Collections 有什么区别?

Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。

Collections是一个操作Set、List和Map等集合的工具类,该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。

常用集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则可能出现错误。Collections提供了多个synchronizedXxx()方法可以把它们包装成线程同步的集合。

public class SynchronizedTest{
    public static void main(String[] args){
        //下面程序创建了4个同步的集合对象
        Collection c=Collections.synchronizedCollection(new ArrayList());
        List list=Collections.synchronizedList(new ArrayList());
        Set s=Collections.synchronizedSet(new HashSet());
        Map m=Collections.synchronizedMap(new HashMap());
    }
}

11. List、Set、Map 之间的区别是什么?

List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector;

Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet;

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap;

img

12. 怎么确保一个集合不能被修改?

Collections提供了如下三类方法来返回一个不可变的集合:

(1) emptyXxx():返回一个空的、不可变的集合对象,此处的集合可以是List,Set,Map。

(2) singletonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合可以是List,Set,Map。

(3) unmodifiableXxx:返回指定集合对象的不可变视图,此处的集合可以是List,Set,Map。

上面三类方法的参数是原有的集合对象,返回值是该集合的“只读”版本。通过Collections提供的三类方法,可以生成“只读”的Collection或Map。

public class UnmodifiableTest{
    public static void main(String[] args){
        // 创建一个空的、不可改变的List对象
        List unmodifiableList=Collections.emptyList();
        // 创建一个只有一个元素,且不可改变的Set对象
        Set unmodifiableSet=Collections.singleton("Java讲义");
        // 创建一个普通的Map对象
        Map scores=new HashMap();
        scores.put("语文" , 80);
        scores.put("Java" , 82);
        //返回普通的Map对象对应的不可变版本
        Map unmodifiableMap=Collections.unmodifiableMap(scores);
        // 下面任意一行代码都将引发UnsupportedOperationException异常
        unmodifiableList.add("测试元素");    //①
        unmodifiableSet.add("测试元素");     //②
        unmodifiableMap.put("语文" , 90);   //③
    }
}

13. ArrayList 和 Vector 的区别?

ArrayList 和 Vector 均是 List 有序集合接口的实现类,都提供按照位置进行定位、添加或者删除的操作。

ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性;但Vector集合则是线程安全的,无须程序保证该集合的同步性。因为Vector是线程安全的,所以Vector的性能比ArrayList的性能要低。实际上,即使需要保证List集合线程安全,也同样不推荐使用Vector实现类。可以使用Collections工具类提供的 List list = Collections.synchronizedList(new LinkedList());

14. ArrayList 和 LinkedList 的区别?

LinkedList与ArrayList、ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部以动态数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能非常出色(只需改变指针所指的地址即可)。需要指出的是,虽然Vector也是以数组的形式来存储集合元素的,但因为它实现了线程同步功能,所以各方面性能都有所下降。除此之外,LinkedList还实现了Deque接口,因此它可以被当成双端队列来使用,自然也可以被当成“栈”来使用了。

15. ArrayList,LinkedList,Vector的使用场景?

如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该采用迭代器(Iterator)来遍历集合元素。

如果需要经常执行插入、删除操作来改变List集合的大小,则应该使用LinkedList集合,而不是ArrayList。使用ArrayList、Vector集合需要经常重新分配内部数组的大小,其时间开销常常是使用LinkedList的时间开销的几十倍,效果很差。

如果有多个线程需要同时访问List集合中的元素,可考虑使用Collections将集合包装成线程安全的集合。

16. Array 和 ArrayList 有何区别?

(1) Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。

(2) Array大小是固定的,定义后就不可被改变,ArrayList的大小是可变的。

(3) ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等 。

17. 如何实现数组和 List 之间的转换?

(1) List转为数组:

直接调用ArrayList中的toArray方法就可以实现,List接口中,toArray有两个重载的方法:

Object[] toArray();
<T> T[] toArray(T[] a);

可见toArray可以用无入参的方式调用,返回一个Object数组;也可以用指定返回类型的方式调用,返回一个指定类型的数组。

@Test
public void test() {
    List<Object> rawList = new ArrayList();
    rawList.add("0");
    rawList.add("1");
    String[] arr = rawList.toArray(new String[rawList.size()]);
    for(String each:arr) {
        System.out.println(each);
    }
}

(2) 数组转换为 List:

如果需要将数组转为List,只需要使用Arrays.asList方法即可。

@Test
public void t5() {
    String[] arr = {"a","b","c"};
    List<String> list = Arrays.asList(arr);
    list.forEach(x -> System.out.println(x));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值