本文标题大纲:
前言
一转眼就到 6 月了,还有一个月左右这学期就结束了,想着这学期结束就得去找实习了。。。时间过的好快,突然想起《许多年以后》中的一句歌词:时间过的好快不会再重来。大学生活就剩最后一年了,之后就得变成上班族了,还是要好好珍惜和利用剩余的大学时光。
集合框架概述
好了,还是不感慨人生了,步入正题,本篇文章是 Java 集合框架的第一篇,从这篇开始,我们将一起来学习一下关于 Java 中集合的一些知识,集合是我们在 Java 编程中相当常用的一个数据结构集。在看这个集合系列之前,希望你对 Java 中一些常见的集合有初步的了解,这样的话这个系列对你来说就没有很大的难度了,当然,如果你没有任何关于集合的基础也没有关系,我会尽力将知识点写的简单一些。
整个集合框架大体上可以分为三种:线性集合类型(List 接口:ArrayList、LinkedList...)
、集合类型(Set 接口:HashSet、TreeSet...)
、映射类型(Map 接口:HashMap、TreeMap...)
。整个 Java 集合框架大致可以用以下图来表示:
图片取自:https://blog.csdn.net/ylyg050518/article/details/48683303,在原图的基础上,我在最上面添加了一个 Iterable
接口,并且 Collection
接口虚线指向它,证明 Collection
接口继承了 Iterable
接口。我为什么要特意加上这个接口呢?我想大家都应该用过 Java 中的 for each
语句吧。不知道大家有没有想过为什么对于一些数据结构(数组、ArrayList 等)可以使用 for each
语句去遍历它,其实就是通过这个 Iterable
接口来实现的,在这个接口中有一个用于产生 Iterator
(迭代器)对象的 iterator()
方法:
/**
* Returns an iterator over elements of type {
@code T}.
* @return an Iterator.
*/
Iterator<T> iterator();
可以看到这个方法返回一个 Iterator
的泛型对象,Iterator
也是一个接口,也就是迭代器,其作用就是提供了统一的方法接口来方便我们遍历容器。即我们可以通过一个集合提供的迭代器对象来遍历这个集合中的元素。同样的我们把提供了迭代器遍历元素的对象称为可迭代对象。如果你记得设计模式中的相关知识的话会发现 Collection
接口在元素遍历的设计上采用迭代器的设计模式,我们来具体看看:
迭代器 Iterator
我们来看看这个接口的定义:
public interface Iterator<E> {
/**
* 如果这个可迭代对象(集合)中还有下一个元素,那么返回 true,否则返回 false
*/
boolean hasNext();
/**
* 返回可迭代对象(集合)中的下一个元素,
* 如果没有下一个元素,方法应该抛出一个 NoSuchElementException 异常
*/
E next();
/**
* 移除集合中最后一次访问的(最后一次调用 next 方法得到的)元素,
* 如果这个方法在第一次调用 next 方法之前调用,或者被连续调用,
* 那么方法应该抛出一个 IllegalStateException 异常,
* 默认实现是抛出一个 UnsupportedOperationException 异常,即不支持的操作
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
当我们得到了一个集合对象提供的 Iterator
对象之后,一个典型的遍历这个集合的对象元素的代码块就是:
Iterator<T> it = obj.iterator();
// 如果集合对象有下一个元素,就遍历元素
while(it.hasNext()) {
// 得到并打印出集合的下一个元素
System.out.print(it.next().toString() + " ");
}
回到上面的框架图,我们可以看到,List
接口和 Set
接口都是直接继承了 Collection
接口,那么就意味着线性集合类型 (List
)和集合类型(Set
)中的元素都是可以通过 for each
语句来进行遍历的,而对于 Map
接口来说,其并没有继承 Iterable
接口,因此对于映射类型,我们不能直接通过 for each
进行遍历。当然,对于映射类型元素的遍历,我们另有方法,在之后的文章中我们再一起探讨。在上图中我们还注意到对于 List
接口来说,它是可以产生一个 ListIterator
对象的,而这个接口也是继承于 Iterator
接口,我们可以看看这个接口中多了什么方法:
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
/**
* 判断当前元素之前是否还有元素,即类似于反方向的 hasNext 方法
*/
boolean hasPrevious();
/**
* 返回前一个元素,如果前面没有元素,那么抛出 NoSuchElementException 异常,
* 类似于反方向的 next 方法
*/
E previous();
/**
* 下一个元素在集合中的下标位置
*/
int nextIndex();
/**
* 前一个元素在集合中的下标位置
*/
int previousIndex();
/**
* 移除集合中上一次调用 next 方法所返回的元素
*/
void remove();
/**
* 将上一次通过 next 方法 / previous 方法访问的元素替换为参数指定的对象
*/
void set(E e);
/**
* 添加一个元素到上一次通过 next 方法访问的元素之后,
* 操作完成后,通过 previous 方法可以访问到该元素
*/
void add(E e);
}
这个接口提供了更加符合 线性结构类型
特点的方法,即提供了向前访问和向后访问两种方式,同时提供了插入元素和修改元素的方法。
为了更加深入的理解 Iterable
接口和迭代器,这里举一个小例子,用自定义的类来实现 Iterable
接口和 Iterator
接口,从而我们可以通过 for each
语句和迭代器来遍历类对象中的元素,新建一个类 ForEachTest.java
:
import java.util.Iterator;
/**
* 一个类要支持 for each 语句,必须实现 Iterable<E> 接口,
* 同时在 iterator() 方法中返回一个 Iterator<E> 对象,
* 一种常用方法是这个类本身也实现 Iterator<E> 接口,这样的话既可以使用 for each 语句,
* 在 iterator() 方法中直接返回 this 的引用即可,也可以使用迭代器形式遍历和访问数据
*/
public class ForEachTest<E> implements Iterable<E>, Iterator<E> {
Object[] arr;
// 当前访问的元素所在数组下标
int curIndex = -1;