【高能预警】:两万字长文,建议先收藏再看,深度源码分析,二十个问题带你一网打尽集合面试。由于文章篇幅过长,本文将持续更新中 添加小助手VX:xuanwo008即可领取全部面试题
(PS:截图自《编程思想》)
答:
这个图由Map指向Collection的Produces并不是说Map是Collection的一个子类(子接口),这里的意思是指Map的KeySet获取到的一个视图是Collection的子接口。
我们可以看到集合有两个基本接口:Map和Collection。但是我个人认为Map并不能说是一个集合,称之为映射或许更为合适,因为它的KeySet视图是一个Set类型的键集,所以我们姑且把它也当做集合。
Collection继承了Iterator接口,而Iterator的作用是给我们提供一个只能向后遍历集合元素的迭代器,也就是说所有实现Collection的类都可以使用Iterator遍历器去遍历。
每种接口都有一个Abstract开头的抽象子类,这个子类中包括了一些默认的实现,我们在自定义类的时候都需要去继承这个抽象类,然后根据我们不同的需求,对于其中的方法进行重写。
从容器角度上来说,只有四种容器:Map,Queue,Set,List。
答:
从鸟瞰图中我们可以看到,所有实现Collection的子类都继承了Iterable接口。这个接口提供了一个iterator()方法可以构造一个Iterator接口对象。然后我们可以使用这个迭代器对象依次访问集合中的元素。
迭代器一般使用方法是这样的:
Collection c = ...;
Iterator iter = c.iterator();
while (iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
复制代码
或者是这样的:
//适用于JDK1.8以后的版本
iter.forEachRemaining(element -> System.out.println(element));
复制代码
迭代器的next()工作原理是这样的:
迭代器是位于两个集合元素之间的位置,当我们调用next()方法的时候迭代器指针就会越过一个元素,并且返回刚刚越过的元素,所以,当我们迭代器的指针在最后一个元素的时候,就会抛出会抛出一个NoSuchElementException的异常。所以,在调用next()之前需要调用hasNext()去判断这个集合的迭代器是否走到了最后一个元素。
通过调用next()方法可以逐个的去访问集合中的每个元素,而访问元素的顺序跟该容器的数据结构有关,比如ArrayList就是按照索引值开始,每次迭代都会使索引值加1,而对于HashSet这种数据结构是散列表的集合,就会按照某种随机的次序出现。
Iterator的接口中还有一个remove()方法,这个方法实际上删除的是上次调用next()方法返回的元素,下面我来展示一下remove()方法的使用方法
Collection c = ...;
Iterator iter = c.iterator();
iter.next();
iter.remove();
复制代码
这样就可以删除该集合中的第一个元素,但是需要注意一点,如果我们需要删除两个元素,必须这样做:
iter.remove();
iter.next();
iter.remove();
复制代码
而不能这么做:
iter.remove();
iter.remove();
复制代码
因为next()方法和remove()方法之间是有依赖性的,如果调用remove之前没有调用next就会抛出一个IllegalStateException的异常。
可以看出,作为顶级的框架,Collection仅仅是继承了Iterable接口,接下来,我们来看一下Iterable的源码,看看有什么收获。
public interface Iterable {
Iterator iterator();
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
复制代码
可以看到这个接口中有三个方法,其中iterator()方法可以给我们提供一个迭代器,这个在之前的教程就已经说过了,而forEach()方法提供了一个函数式接口的参数,我们可以使用lambda表达式结合来使用:
Collection collection = ...;
collection.forEach(String s -> System.out.println(s));
复制代码
这样就可以获取到每个值,它的底层实现是加强for循环,实际上也是迭代器去遍历,因为编译器会把加强for循环编译为迭代遍历。 Spliterator()是1.8新加的方法,字面意思可分割的迭代器,不同以往的iterator()需要顺序迭代,Spliterator()可以分割为若干个小的迭代器进行并行操作,既可以实现多线程操作提高效率,又可以避免普通迭代器的fail-fast(fail-fast机制是java集合中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件)机制所带来的异常。Spliterator()可以配合1.8新加的Stream()进行并行流的实现,大大提高处理效率。
Collection()中提供了17个接口方法(除去了继承自Object的方法)。接下来,我们来了解一下这些方法的作用:
作为第一级的集合接口,Collection提供了一些基础操作的借口,并且可以通过实现Iterable接口获取一个迭代器去遍历获取集合中的元素。