前言:因为最近要重新找工作,Collection(集合)是面试中出现频率非常高的基础考察点,所以好好恶补了一番。
复习过程中深感之前的学习不系统,而且不能再像刚毕业那样死背面试题,例如:String 是固定长度的,StringBuffer 和 StringBuilder 的长度是可以变化的。如果一旦问得深入一点,问为什么有这样的区别就傻眼了,只能一脸呆萌地看着面试官。
因此想要通过写文章的形式,系统地总结学习的内容,例如 Collection 架构是怎样的、有哪些相关的继承和接口实现,这样才能了解什么时候应该用哪个类,以及类之间要如何搭配合作,才知道出了问题应该如何解决。这一系列文章适用于 Java 技术岗的应聘者、高校计算机专业的学生以及培训机构学习 Java 的初学者阅读。
1.1 认识 Collection 架构
我们都使用过 ArrayList 类收集对象,例如 add() 方法新增对象,remove() 方法移除对象,这些都不会陌生。但是这些方法是怎么来的呢?下图是该类的继承架构图:
ArrayList 一个类就这么复杂,如果要把全部 Collection 架构表现在一张图上,那估计就跟蜘蛛网一样纠缠不清。简化一下,忽略一些不那么重要的接口和实现类,我们可以得到以下这张架构图。
从图上可知,Colletion 是一个接口,实现了另一个接口 Iterable。Collection 下面有三个接口直接实现了它,分别是 List、Set 和 Queue。List 下面有两个实现类,分别是 ArrayList 和 LinkedList;Set 的常用实现类是 TreeSet 和 HashSet;Queue 下面有 Deque 接口实现,再下面是实现类 ArrayDeque.
这张图将会是这一系列文章的核心,之后会反复提及,不妨称之为 Collection 架构图,每一篇文章都是介绍其中的一部分。熟悉这张图,不仅有助于理解学习,还可以帮助记忆。至于详细周全的继承关系和实现架构,到底有哪些类实现了哪些接口、继承了哪些类,可以自行在 API 说明文档中查询。
1.2 具有索引的 List
List 实现了 Collection 接口,所以我们可以说 List 是一种 Colletion,作用就是收集对象,特点是以索引的方式记录所收集的对象顺序。List 中常见的实现类是刚才所提及架构图中的 ArrayList,忘了的读者可以翻到前面对照着看。
- 1
- /**
- 2 * ArrayList的实验用例
- 3 */
- 4 5 import java.util. * ;
- 6 7 public class Student {
- 8 public static void main(String[] args) {
- 9 List list = new ArrayList(); //使用JavaSE的List和ArrayList
- 10 Scanner scanner = new Scanner(System. in );
- 11 String name;
- 12
- while (true) {
- 13 System.out.print("学生签到:");
- 14 name = scanner.nextLine();
- 15
- if (name.equals("quit")) {
- 16
- break;
- 17
- }
- 18 list.add(name); //实用Add()方法收集对象
- 19
- }
- 20 System.out.println("今天上来上课的学生名单:");
- 21 foreach(list);
- 22
- }
- 23 24 private static void foreach(List list) {
- 25
- for (int i = 0; i < list.size(); i++) {
- 26 String student = (String) list.get(i); //使用get()方法依据索引取得收集的对象
- 27 System.out.println(student);
- 28
- }
- 29
- }
- 30
- }
以上是 ArrayList 类的一个简单使用例子,模拟的是学生上课签到的情景。强烈建议读者跟我一样自己试着写一个简单用例,尤其是之前很少使用 ArrayList 的初学者,单纯的看和读跟实际敲代码产生的效果完全不一样。也可以照着我给出的例子敲,偷懒一点的话可以直接复制在机器上跑一遍。
从 Collection 架构图中可知,LinkedList 同样也实现了 List 接口。就算只是把上面那个实验中的 ArrayList 全部改为 LinkedList,程序照样可以运作,而且效果看起来完全相同。那么问题来了,我们什么时候应该使用 ArrayList,什么时候又应该使用 LinkedList 呢?
1.2.1 ArrayList 的特性
卡车和轮船都可以运送货物,我们可以根据不同的情况选择不同的运输方式。如果时间紧、运输量小,而且两个地点都在陆地上(例如北京到南京),那么我们可以使用汽车;如果时间多、运输量大,出发地和目的之间隔着海洋(例如大连到纽约),那么用船运是更好的选择。
刚毕业那会要找工作,为了面试背过 "ArrayList 像数组,读取速度快,但是需要调整索引的话表现很差;LinkedList 像链表,调整索引的表现非常好,但是随机读取的速度比较慢"。那么我们可以问深一句,为什么会这样呢?不妨从源代码中找寻答案。
- 1 public boolean add(E e) {
- 2 ensureCapacityInternal(size + 1); // Increments modCount!!
- 3 elementData[size++] = e;
- 4
- return true;
- 5
- }
上面这一段是 JavaSE 的源代码,我们可以看到 ArrayList 中的 add() 方法非常简单,跟我们平时使用数组一样。查看源代码中更多内容你会发现,ArrayList 内部就是使用 Object 数组来保存所收集的对象,这就是为什么说"ArrayList 就像数组 " 的原因。在考虑是否使用 ArrayList 的时候,我们可以相当于考虑是否要使用数组的特性。
1.2.2 LinkedList 的特性
在学习 Collection 架构的时候,我们不妨可以多看源代码,看的时候优先比较几个基本方法的实现,例如 add()、remove() 等。从这些方法的实现,我们就可以看到不同实现类的特性。
- public boolean add(E e) {
- linkLast(e);
- return true;
- }
- /**
- * Links e as last element.
- */
- void linkLast(E e) {
- final Node<E> l = last;
- final Node<E> newNode = new Node<>(l, e, null);
- last = newNode;
- if (l == null)
- first = newNode;
- else
- l.next = newNode;
- size++;
- modCount++;
- }
看到 LinkdedList.add() 的源代码,我们会发现其实现方式跟链表的实现如出一辙。如果 last 结点为 null,那么说明链表为空,所以新添加的结点为头结点。如果 last 结点不等于 null,那么把新添加的结点设为 last 的下一个结点,作为新的尾结点。
根据链表的特性,我们可以很快总结两点点特性:1. 想要指定索引随机存取时,链接方式都得使用从第一个元素开始查找下一个元素的方式,效率比较糟糕;2. 链接的每个元素都会参考下一个元素,这有利于调整索引顺序。
1.2.3 List 总结
作为 Collection 三大阵营之一的 List,最大的特点就是索引,我们可以通过索引做到随机存取。
List 中常用的实现有 ArrayList 和 LinkedList,各自的特性可以分别参考数组和链表。在比较它们之间区别的过程中,我们看了源代码,提倡在比较同一接口不同实现类时重点查看它们共同需要实现的方法,例如 Collection 中规定的 add(),remove() 等。
面试中常见的 List 实现类其实还有 Vector, 其特性与 ArrayList 相同。不同在于 Vector 具有线程安全的特性,性能开销比较大,具体的内容会放在以后关于多线程的文章里。
在总结过程中,给初学者提供两个建议,一是做实验,通过写一些 demo 来熟悉所学内容;二是在力所能及的情况下多看源代码,知其然也要知其所以然。
如果你喜欢我的文章,可以扫描关注我的个人公众号 "李文业的思考笔记"。
不定期地会推送我的原创思考文章。
来源: http://www.cnblogs.com/levenyes/p/7117559.html