本节给出的实现代码,将不提供上层接口的实现,是独立的一段代码。
一、实现细节
- MyArrayList将保持基础数组,数组的容量,以及存储在MyArrayList中的当前项数
- MyArrayList将提供改变基础数组容量的机制,通过获得新数组并将老数组内容拷贝,实现变长扩容
- MyArrayList将提供get、set方法实现
- MyArrayList将提供size、isEmpty等功能,还提供remove和可扩容的add
- MyArrayList将提供一个实现Iterator接口的类,并提供对应方法
二、实现代码
package ColletionsAPI.ListInterface;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<T> implements Iterable<T>{
//数组默认长度
private static final int DEFAULT_CAPACITY = 10;
//设置表长、数组参数
private int theSize;
private T [] theItems;
//构造方法,构造时保证为空表
public MyArrayList(){
clear();
}
//清空表,并重新设置数组长度
private void clear() {
theSize = 0;
ensureCapacity(DEFAULT_CAPACITY);
}
//获取表长度
public int size(){
return theSize;
}
//判断是否为空表
public boolean isEmpty(){
return size()==0;
}
//修改数组容量为当前表长
public void trimToSize(){
ensureCapacity(size());
}
//获得指定索引处元素
public T get(int index){
if (index < 0 || index >= size() ){
throw new ArrayIndexOutOfBoundsException();
}else {
return theItems[index];
}
}
//将指定索引处的元素重新赋值,并返回赋值前的元素
public T set(int index, T newVal){
if (index < 0 || index >= size()){
throw new ArrayIndexOutOfBoundsException();
}else {
T old = theItems[index];
theItems[index] = newVal;
return old;
}
}
//设置数组长度
private void ensureCapacity(int newCapacity) {
if (newCapacity < theSize){
//新长度小于表长,返回
return;
}else {
//创建新数组
T[] old = this.theItems;
theItems = (T[]) new Object[newCapacity];
//遍历表,并把原数据重新赋值给新数组
for (int i = 0 ; i < size() ; i++){
theItems[i] = old[i];
}
}
}
//表尾添加
public boolean add(T x){
//调用指定位置添加
add(size(),x);
return true;
}
//指定位置添加
private void add(int index, T x) {
if (theItems.length == size()){
//如果当前表容量已满,设置新数组长度为原来的2倍+1,并
ensureCapacity(size() * 2 + 1);
}else {
//将原索引后的元素全部后移一位
for (int i = theSize ; i < index ; i--){
theItems[i] = theItems[i-1];
}
//在指定索引处添加元素
theItems[index] = x;
//表长自增
theSize++;
}
}
//删除指定索引处元素
public T remove(int index){
//获得指定索引处元素
T removeItem = this.theItems[index];
//将指定索引之后的全部元素前移一位
for (int i = index ; i < size()-1 ; i++){
theItems[i] = theItems[i+1];
}
//表长自减
theSize--;
//返回被删除的元素值
return removeItem;
}
/*实现迭代器*/
//存储当前位置的概念
@Override
public Iterator<T> iterator() {
return new ArrayListIterator();
}
//迭代器
private class ArrayListIterator implements Iterator<T>{
//要被查看的下一个元素的索引,起始为0
private int current = 0;
//判断是否有下个元素
@Override
public boolean hasNext() {
return size() < current;
}
//获得下一个元素
@Override
public T next() {
if (!hasNext()){
throw new NoSuchElementException();
}
return theItems[current++];
}
//删除元素
@Override
public void remove() {
//因为current给出的是下一个元素索引,所以先自减
MyArrayList.this.remove(--current);
}
}
}
这里给出的注释已经比较充分,不再做详细赘述。
值得注意的是ArrayList的底层是使用数组存储,数组的长度和表长并非一个概念;另外,迭代器中的current变量提供的是下一个访问索引,而非当前索引。
三、迭代器代码分析
上述代码中,相信表的实现部分应该较好理解,然而关于迭代器的部分可能会有读者进行不同的实现尝试。首先,我们把上述代码中的迭代器部分取出。
//迭代器
private class ArrayListIterator implements Iterator<T>{
//要被查看的下一个元素的索引,起始为0
private int current = 0;
//判断是否有下个元素
@Override
public boolean hasNext() {
return size() < current;
}
//获得下一个元素
@Override
public T next() {
if (!hasNext()){
throw new NoSuchElementException();
}
return theItems[current++];
}
//删除元素
@Override
public void remove() {
//因为current给出的是下一个元素索引,所以先自减
MyArrayList.this.remove(--current);
}
}
相信,在看到迭代器的实现代码时,肯定有读者会产生尝试另外创建一个类来实现的想法。笔者一开始,也是试图进行这样的实现,但是在实现过程中发现了不妥。
当我们直接另外创建一个类来实现迭代器时,迭代器会因为不能使用theItem和size()而出现错误。这是理所当然的,因为它们并不是迭代器类的一部分,这时编译器会直接报错。
对于上述问题有一个很常见的解决方案,将MyArrayList作为成员变量放入迭代器类中。这样的做法看似解决了问题,编译器不在有错误提示,但是这种方案仍有隐患。
因为theItem在MyArrayList中是私有的,而因为不是同一个类,所以在访问私有域时必然是非法的。
解决可见性问题的最简单方法就是直接修改权限修饰符,我们可以将private改为public或使用默认。
虽然这时迭代器已经可以正常使用了,但是,使用这种做法却大大降低了数据的安全性。良好的面向对象编程的基本原则之一就是,要求数据尽可能的隐蔽。
四、嵌套类和内部类
通过上述的分析,可以肯定,将迭代器类单独实现是不太可能的,这时就要考虑到嵌套了和内部类了。
1)嵌套类
我们通过将迭代器类放入 MyArrayList 类的内部,使它成为一个嵌套类,相应的
MyArrayList 就被称为外部类
@Override
public Iterator<T> iterator() {
return new ArrayListIterator<T>();
}
//迭代器
private class ArrayListIterator<T> implements Iterator<T>{
//要被查看的下一个元素的索引,起始为0
private int current = 0;
//将MyArrayList<T>做成员变量
private MyArrayList<T> theList;
//判断是否有下个元素
@Override
public boolean hasNext() {
return theList.size() < current;
}
//获得下一个元素
@Override
public T next() {
if (!hasNext()){
throw new NoSuchElementException();
}
return theList.theItems[current++];
}
//删除元素
@Override
public void remove() {
//因为current给出的是下一个元素索引,所以先自减
MyArrayList.this.remove(--current);
}
}
注意对比这段代码与上一节的代码,这里的迭代器类被修饰为static,并且通过将MyArrayList作为成员变量来使用MyArrayList中的私有属性。
嵌套类的问题在于,当编写theItems而不引用其所在的MyArrayList类时,代码看起来还可以,但却是无效的,因为编译器不可能计算出哪个MyArrayList在被引用。为此,我们要自己声明引用了哪个类。
2)内部类
当声明一个内部类时,编译器会添加对外部类对象的一个隐式引用,该对象引起内部类对象的构造。例如,外部类名为Outer,则隐式引用为Outer.this。
当我们的迭代器类作为一个内部类而没有被static修饰时,那么MyArrayList.this和theList就都会引用同一个MyArrayList。这样我们就不必再声明MyArrayList作为成员变量。代码就回归为最初的版本。