JAVA中数组列表类的实现

〇、写在前面

1. 为什么要用数组列表

数组是最差的数据结构之一。因为不能调整大小,数组总是要面临越界或者内存占用过多的问题。因为其有序的存储方式,插入和删除又有很大不便。

数组列表就很有用了,保留了数组原来的特点(即,可以通过索引直接访问),又可以动态地调整大小,从而避免数组越界或者内存浪费的问题。

2. 数组列表凭什么动态

“动态”是一个骗局!数组列表的内部,肯定是一个不能调整大小 的数组。 要“调整”数组大小,只能是另外再创建一个数组,替换原来的数组。 动态就是这样实现的。所以,频繁地调整容器的大小会降低效率。

我们给一个普普通通的数组穿上外套之后,就可以骗别人说“这是个动态的容器”了

JVM 会回收已弃用的数组所占用的空间。

3. 要在定义类之前就确定元素的类型吗

我们要在数组列表类的内部创建一个数组来存放元素。这个数组应该是什么类型的呢?我们可以把它声明为 Object [ ] ,这样,数组列表就可以存储任何类型的数据了( 不能存放基本数据类型的数据;应该使用对应的封装类 )。

但是,有时候我们需要一个只能存储特定类型数据 的数组列表。这时候,可以将数组列表类定义为泛型类泛型就是将数据类型作为可变参数。 下面是泛型类的使用示例,其中的 Generics 代表一种类(不能代表基本数据类型):

class MyClass<Generics> {
	
	private Generics variable;

	public setVariable(Generics v) {
		// variable = new Generics(); // 不可以
		variable = v;
	}

	public static void main(String[] args) {
		//  MyClass object = new MyClass(); 也可以
		MyClass<Integer> object1 = new MyClass<>(); //1
		MyClass object2 = new MyClass<>(); //2

}

可以在实例化对象的时候将对象的泛型指定为实际的类,如语句1,这有点像是传递参数;也可以不指定为实际的类,如语句2,这时候,类中的 Generics 可以表示任意的类。

多数情况下,泛型看起来和类一样。然而, 不能实例化泛型的对象,也不能创建泛型的对象数组。 所以数组列表类中的数组还是只能声明为 Object [ ] 。不过我们可以在类方法中使用泛型对象作为参数,从而限制数组列表中的对象类型。

一、需要什么属性

属性名称 属性类型 说明
data Object [ ] 实际上的 容器
size int 记录数组列表实际存放了多少元素
  • 访问权限?

这些属性都应该是私有的。因为,只有当通过数组列表的方法来进行操作时,数组列表才真正地表现为“动态”。所以,不应该在类外直接访问上述属性。如果把上述属性设为公有,那么这些属性可能会被没有睡醒的人无意中修改掉。

  • 为什么需要属性 size

后面再说。 不过我觉得,真正在思考的人,应该早就知道为什么需要 size

二、需要什么方法

  • 换句话说:需要什么功能?

创建、获取元素数量、在末尾添加元素、在指定位置添加元素、访问指定的元素、更改指定的元素、删除指定的元素 / 元素区间 等等。

在下文中,我把这个类叫做 “MyArrayList"。

方法名 返回值类型 参数列表 说明
构造方法 不可用 初始化 data ,使其大小合适
构造方法 不可用 int length 初始化 data ,将其大小设为 length
size int 返回 size
get T int index 返回索引为 index 的对象
add void T obj 在数组末尾增加对象 obj
add boolean T obj, int index 把原来的某些元素向后移动,空出
索引为 index 的位置,然后插入 obj
插入成功则返回true
addList boolean MyArrayList array,
int index
把原来的某些元素向后移动,空出足够
的位置,然后在索引为 index 的位置
插入 array ,插入成功则返回true
change boolean int index, T obj 将指定位置的元素更改为 obj
更改成功则返回true
changeAll int T old, T intention 把与 old 相同的元素更改为intention
返回更改的个数
remove T int index 删除指定位置的元素,
返回被删除的元素
removeAll int T target 删除与obj相同的元素,
返回删除的个数
remove boolean int left, int right 删除闭区间 [left, right] 中的元素,
删除成功则返回true
  • 访问权限?

上述方法需要在类外甚至包外使用,应设为公有。

  • 默认构造方法中的 “合适大小” 是指什么?

应该设置一个不大不小的值。 如果默认大小太大,会占用不必要的内存;如果默认大小太小,以后可能需要频繁地扩容数组,降低效率。

  • “实际存放的元素数量” 是 data.length 吗?

不是。从默认构造方法就可以看出来,我们还没有调用 add() 方法时,data.length 就大于 0 了;但是这时候 data 数组中已有的数据并不是我们真正想要存放的数据。因此,我们需要一个用来记录“有效数据”数量的变量。
这也就是为什么我们要定义一个 size 属性。我们通过 size 属性而不是 data.length 来确定存放了多少元素; 数组 data 中常常会有几个 “空位”。(此处及下文所说的“空位”,是指 data 数组中多余的、可以删去的空间。这些空间并不是真正的“空”,它们中还存放着一些数据,然而,公有方法不允许从外部访问它们,从类外看来,它们是不存在的。)

  • 为什么我们要让 data 留有 “空位” 呢?

我们完全可以 “动态” 地调整容器大小,保证 data 的大小完全等于所需的大小。如果这样做,每次添加元素,我们都要把容器扩大一次;每次删除元素,我们都要缩小容器。每次增删都要改变容器大小,效率太低。所以我们选择让 data 留有少许 “空位” ,虽然会浪费一些空间,但是可以减少改变容器大小的次数,从而保证效率。

  • 上面就是所有的类方法吗?

有一些代码是要我们重复写数次的。比如说,在删除指定的元素时,我们要判断符合条件的元素是否存在;还有,在添加元素之前,要检查内部的数组是否足够大。
不妨把要重复使用的代码也写成方法,代码重用得越多,我们就越轻松,代码的可读性也会有所提升。

方法名 返回值类型 参数列表 说明
accessible boolean int index 如果 index 处存放了数据,返回 true
full boolean int addition 如果添加了 addition 个元素之后,
data 数组空间不足,返回true
space boolean int index 如果 data 数组有索引为 index
元素,返回true
resize void int size data 数组大小改为 size
moveLeft void int left, int right, int step 将闭区间 [left, right] 中的元素向
移动 step 个单位(覆盖原有元素)
moveRight void int left, int right, int step 将闭区间 [left, right] 中的元素向
移动 step 个单位(覆盖原有元素)
  • 访问权限?

私有。不应该在类外访问上面这些方法。

四、琐事

  • 扩大数组的时候,应该扩大到多大?

可以一个一个地扩大。
不过,在需要大量的元素时,一个一个地扩大效率太低了。因此我在扩大数组的时候,每次扩大都让数组大小增加一倍。这样可能会浪费一些空间,不过不必频繁地扩大了。 要扩大多少取决于喜好。

  • 有必要缩小数组吗?什么时候缩小?

有必要缩小数组。我们选用数组列表的目的之一就是减少内存空间的浪费,所以如果有机会缩小数组,应该缩小数组。
可以在 data 数组有大量 “空位” 时缩小数组。我的做法是,当 “空位” 数量大于 data.length 的一半时,缩小数组到原来的一半。要缩小多少、什么时候缩小取决于喜好。

  • 我为什么把这么简单易懂的内容说得这么复杂?

因为我不但代码写得差,语文水平还很臭。

三、我的代码

public class MyArrayList<T> {

    private Object[] data;
    private int size;

    public MyArrayList() {
        data = new Object[10];
    }

    public MyArrayList(int length) {
        data = new Object[length];
    }

    private boolean accessible(int index) {
        return index > -1 && index < size;
    }

    private boolean full(int addition) {
        return size + addition > data.length;
    }

    private boolean space(int index) {
        return index > -1 && index < data.length;
    }

    private void resize(int size) {
        Object[] r = new Object[size];
        int right = size < this.size ? size : this.size;
        for (int i = 0; i < right; i++) {
            r[i] = data[i];
        }
        data = r;
        System.out.println('=');
    }

    private void moveLeft(int left, int right, int step) {
        if (left <= right && space(left - step) && space(right)) {
            for (int i = left; i <= right; i++) {
                data[i - step] = data[i];
        }
    }

    private void moveRight(int left, int right, int step) {
        if (left <= right && space(right + step) && space(left)) {
            for (int i = right; i >= left; i--) {
                data[i + step] = data[i];
        }
    }

    public int size() {
        return size;
    }

    public T get(int index) {
        return accessible(index) ? (T) data[index] : null;
    }

    public void add(T obj) {
        if (full(1)) {
            resize(2 * data.length);
        }
        data[size++] = obj;
    }

    public String toString() {
        StringBuilder r = new StringBuilder();
        for (int i = 0; i < size; i++) {
            T t = (T) data[i];
            r.append(t instanceof MyArrayList ? "A MyArrayList" : t);
            r.append('\t');
        }
        r.append('\n');
        return r.toString();
    }

    public boolean add(T obj, int index) {
        if (accessible(index) || index == size) {
            if (full(1)) {
                resize(2 * data.length);
            }
            moveRight(index, size - 1, 1);
            data[index] = obj;
            size++;
            return true;
        }
        return false;
    }

    public boolean addList(MyArrayList<T> array, int index) {
        if (accessible(index) || index == size) {
            if (full(array.size)) {
                resize(array.size + size);
            }
            moveRight(index, size - 1, array.size);
            for (int i = 0; i < array.size; i++) {
                data[i + index] = array.data[i];
            }
            size += array.size;
            return true;
        }
        return false;
    }

    public T remove(int index) {
        if (accessible(index)) {
            T old = (T) data[index];
            moveLeft(index + 1, --size, 1);
            return old;
        }
        return null;
    }

    public int removeAll(T target) {
        int counter = 0;
        for (int i = 0; i < size; i++) {
            if (data[i].equals(target)) {
                moveLeft(i + 1, --size, 1);
                counter++;
            }
        }
        return counter;
    }

    public boolean remove(int left, int right) {
        if (accessible(left) && accessible(right)) {
            int number = right - left + 1;
            moveLeft(right + 1, size - 1, number);
            size -= number;
            if (size < data.length / 2) {
                resize(data.length / 2);
            }
            return true;
        }
        return false;
    }

    public boolean change(int index, T obj) {
        if (accessible(index)) {
            data[index] = obj;
            return true;
        }
        return false;
    }

    public int changeAll(T old, T intention) {
        int counter = 0;
        for (int i = 0; i < size; i++) {
            if (data[i].equals(old)) {
                data[i] = intention;
                counter++;
            }
        }
        return counter;
    }
}

你可能感兴趣的:(JAVA入门学习笔记,数据结构和算法)