面试回答
Java 的整个集合框架中,主要分为 List
、Set
、Queue
、Stack
、Map
等五种数据结构。其中,前四种数据结构都是单一元素的集合,而最后的 Map 则是以 KV 对的形式使用。
从继承关系上讲,List
、Set
、Queue
都是 Collection
的子接口,Collection
又继承了 Iterable
接口,说明这几种集合都是可以遍历的。
从功能上讲,List 代表一个容器,可以是先进先出,也可以是先进后出。而 Set 相对于 List 来说,是无序的,同时也是一个去重的列表,既然会去重,就一定会通过 equals,compareTo,hashCode 等方法进行比较。Map 则是 KV 的映射,也会涉及到 Key 值的查询等能力。
从实现上讲,List 可以有链表实现或者数组实现,两者各有优劣,链表增删快,数组查询快。Queue 则可以分为优先队列,双端队列等等。Map 则可以分为普通的 HashMap 和可以排序的 TreeMap 等等。
知识扩展
Collection 和 Collections 有什么区别?
- Collection 是一个集合接口:它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。是 list、set 等的父接口。
- Collections 是一个包装类:它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架。
日常开发中,不仅要了解 Java 中的 Collection 及其子类的用法,还要了解 Collections 用法。可以提升很多处理集合类的效率。
Java 中的 Collection 如何遍历迭代?
- 传统的 for 循环遍历,基于计数器的:遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。
- 迭代器遍历,Iterator:每一个具体实现的数据集合,一般都需要提供相应的 Iterator。相比于传统 for 循环,Iterator 取缔了显式的遍历计数器。所以基于顺序存储集合的 Iterator 可以直接按位置访问数据。而基于链式存储的 Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。
- foreach 循环遍历:根据反编译的字节码可以发现,foreach 内部也是采用了 Iterator 的方式实现,只不过 Java 编译器帮我们生成了这些代码。
- 迭代器遍历:Enumeration:Enumeration 接口是 Iterator 迭代器的“古老版本”,从 JDK 1.0 开始,Enumeration 接口就已经存在了(Iterator 从 JDK 1.2 才出现)
Iterable 和 Iterator 如何使用?
Iterator 和 Iterable 是两个接口,前者代表的是迭代的方式,如 next 和 hasNext 方法就是需要在该接口中实现。后者代表的是是否可以迭代,如果可以迭代,会返回 Iterator 接口,即返回迭代方式。
常见的使用方式一般是集合实现 Iterable 表明该集合可以遍历,同时选择 Iterator 或者自定义一个 Iterator 的实现类去选择遍历方式,如:
public class AbstractList<E> implements Iterable<E> {
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator{
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
return null;
}
}
}
为什么不把 Iterable 和 Iterator 合成一个使用
- 通过 javadoc 文档我们可以发现,Iterable 和 Iterator 并不是同时出现的,Iterator 于 1.2 就出现了,目的是为了代替 Enumeration,而 Iterable 则是 1.5 才出现的。
- 将是否可以迭代和迭代方式抽出来,更符合单一职责原则,如果抽出来,迭代方式就可以被多个可迭代的集合复用,更符合面向对象的特点。