在 Java 中不使用 Arraylist 的情况下为类制作动态数组,并且没有正确教授如何去做

Posted

技术标签:

【中文标题】在 Java 中不使用 Arraylist 的情况下为类制作动态数组,并且没有正确教授如何去做【英文标题】:Making a Dynamic Array for a class without using Arraylists in Java and have not been properly taught how to do it 【发布时间】:2018-06-01 06:47:48 【问题描述】:

所以我正在和一位不擅长教我们做作业的老师一起上 Java 课。

这个任务,我们应该创建一个包含构造函数、方法和所需特性的动态整数数组类。不允许使用 ArrayLists 我已经写了一些,有几个不工作,其他的我不知道该怎么做。

以下是要求:

    私有 int 数组 [] 字段。您必须将数据内部存储在 常规部分填充的整数数组。请不要使用 数组列表。分配数组的大小是它的容量,并且将 下面讨论。

    私有 int 大小字段。该变量存储的数量 数组中的“占用”元素。在构造函数中设置为 0。

    带参数的构造函数。该参数定义了容量 初始数组。分配给定容量的数组,将 size 字段设置为 零。如果给构造函数的参数小于0, 正在抛出 IllegalArgumentException。

    无参数构造函数。分配大小为 10 的数组,设置大小字段 到 0。

    复制构造函数。构造函数接受一个类型的对象 DynamibcArray 作为参数并将其复制到它的对象中 创建。构造函数抛出 IllegalArgumentException 如果 传递给副本的对象为空。

    int getSize() 返回大小——数组中被占用的元素数。

    int [] toArray() 访问器返回数组。确保你不 返回私有数组字段。相反,为新的分配内存 数组,将您的数组字段复制到该新对象中,然后返回 新数组。

    public void push(int num) 在数组末尾添加一个新元素 并增加大小字段。如果数组已满,则需要 增加数组的容量: 一种。创建一个大小等于原始数组容量两倍的新数组。 湾。将数组字段中的所有元素复制到新数组。 C。将新元素添加到新数组的末尾。 d。使用新数组作为数组字段。

    public int pop() throws RuntimeException 删除最后一个元素 数组并返回它。减小大小字段。如果数组是 必须清空带有消息“数组为空”的 RuntimeException 抛出。此时检查阵列的容量。如果 容量是占用元素数量的 4 倍 (size),是时候缩小数组了:

    一个。创建一个新数组,其大小等于其容量的一半 原来的。 湾。将数组字段中的所有元素复制到新数组。 C。使用新数组作为数组字段。

    int get(int index) throws IndexOutOfBoundsException 返回元素 具有请求索引的数组。如果提供的索引太 大或负,IndexOutOfBoundsException 与 消息“非法索引”。

    int indexOf(int key) 返回第一次出现的索引 给定的数字。未找到数字时返回 -1。

    void add(int index, int num) throws IndexOutOfBoundsException 添加 一个新元素(作为参数 num 传递)到 由索引参数指定的数组。如果索引大于 数组的大小或小于 0,IndexOutOfBoundsException 是 抛出。将元素添加到数组中间时, 您必须将所有元素向右移动以腾出空间 新的那一个。如果阵列已满且没有空间容纳新的 元素,数组的大小必须加倍。请按照步骤 push() 方法描述中列出的将容量加倍 数组。

    int remove (int index) throws IndexOutOfBoundsException removes 此数组中指定位置的元素。当。。。的时候 元素从数组中间移除,所有元素 必须移动以关闭已删除元素创建的间隙。如果 传递给方法的索引值大于或等于大小或 小于 0 必须抛出 IndexOutOfBoundsException。在这 点检查阵列的容量。如果容量是4倍 大于占用元素的数量(大小),是时候 缩小数组。

这是我到目前为止所创建的方法的类。

/**
 *
 * @author Lisa Hergert
 */
public class DynamicArray 
    private int array[];
    private int size;

    /*
    * Constructor
    * @param capacity - integer
    * throws an IllegalArgumentExeception if capacity is less than 0
    */
    public DynamicArray (int capacity) 
        if (capacity < 0) 
            throw new IllegalArgumentException("Size cannot be less than 0.");
        
    

    /*
    * no-arg constructor
    */
    public DynamicArray () 
        array = new int [10];
        size = 0;
    

    /*
    * Copies the array to a new one
    * @param - array [] - integer array
    */
    public DynamicArray (int array[]) 
        int arrayCopy [] = new int [array.length];
        for (int i = 0; i < array.length; i++) 
            arrayCopy[i] = array[i];
        
    

    /*
    * getSize returns the size.
    * @return - size
    */
    public int getSize () 
        return size;
    

    /*
    * @param array[] - integer
    * @return array
    */
    public int [] toArray (int array[]) 
        return array;
    

    /*
    * @param num - integer
    */
    public void push (int num) 

    

    /*
    * @return pop - integer
    */
    public int pop() throws RuntimeException  
        return pop();
    

    /*
    * @param index - integer
    */
    public int get(int index) throws IndexOutOfBoundsException 
        if (index >= size || index < 0)
            throw new IndexOutOfBoundsException("Illegal Index");
        return array[index];
    

    public int indexOf(int key) 
        int index = 0;

        return index;
    

    public void add(int index, int num) throws IndexOutOfBoundsException 
        if (index >= size || index < 0)
            throw new IndexOutOfBoundsException("Illegal Index");
        int oldValue = array[index];
        array[index] = num;
        return oldValue;
    

    public int remove(int index) throws IndexOutOfBoundsException 

    

我正在寻求帮助,甚至是关于在哪里可以查找我需要做的事情的建议。我要去校内的辅导实验室,看看他们能不能帮助我理解老师没有教好的地方,但在那之前我想自己尝试一下。

这是几个问题之一。

/*
* @return pop - integer
*/
public int pop() throws RuntimeException  
    return pop();

我知道这是不对的。即使你不想给我看代码,你能不能给我一个网站上的建议,我可以查到我需要做什么,这个要求是#9。

感谢您的帮助。我希望我有知识在没有帮助的情况下做到这一点,但事实并非如此,所以我必须使用外部资源。这个网站过去很有帮助。

【问题讨论】:

你的第一个构造函数什么都不做,你可能也想分配数组 要求我们推荐或查找书籍、工具、软件库、教程或其他非现场资源的问题对于 Stack 来说是题外话溢出,因为它们往往会吸引固执己见的答案和垃圾邮件 @ScaryWombat 正如 Ryan 上面所说的那样,我想这只是一个基类,需要根据要求进行调整才能正常工作 public int pop() throws RuntimeException return pop(); 这会一直调用它自己,直到你得到一个 *** @RyanTheLeach OP 说,这是我迄今为止制作的方法的类。,所以可能是也可能不是 【参考方案1】:

这是一个很棒的编程作业,可以让学生清楚地了解 ArrayList。正如您可能已经猜到的那样,您的讲师希望您实现自己的 ArrayList,这是一种教授数据结构的绝妙方法。

从需求逐一开始

带参数的构造函数:目的是你可以初始化一个特定长度的数组。但是,由于数组没有元素,所以保持它的大小为 0

公共动态数组(整数容量) 如果(容量

不带参数的构造函数:与前面类似,只是容量将替换为常数值 10。为了便于阅读,将此常数指定为

public static final int DEFAULT_CAPACITY = 10;

对于在推送或弹出期间缩小/增加容量,请这样想,如果您知道您的 cookie 罐很快就会用完空间,您会怎么做。你会得到一个新罐子。现在,你如何决定你想要什么尺寸的罐子?好吧,如果你想变大,答案是双倍的,如果你想变小,答案是 1/4。获取一个新 jar 类似于创建一个新的整数数组对象。然后你把你所有的饼干从小罐子移到大罐子里,然后扔掉罐子。同样,将每个项目从旧数组复制到新数组。

这个答案可能会很长,我知道如果我将源代码传递给你会作弊,但据我了解,你真的很愿意看看它是如何完成的,因此你可以查找参考ArrayList 的实现:http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java

这最初可能令人生畏,但如果您仔细查看方法 grow()、ensureCapacity()、indexOf()、clone()、add(),您会更好地了解它是如何在 java 中实现的

【讨论】:

请注意,查看 ArrayList 的来源可能会吓到你。 ArrayList 比这个任务的规范需要更多的关注,并且还需要能够在数百万开发人员的生产使用中生存下来。它有一个非常高的标准,我怀疑大多数 Java 开发人员会辜负它。【参考方案2】:

让我们重新开始。从一开始就。我将评论本教程中的每个步骤。 (请注意,这个问题通常过于宽泛而无法回答,但今天我还是会这样做。)


这个任务,我们应该创建一个包含构造函数、方法和所需特性的动态整数数组类。

让我们从类本身开始:

public final class DynamicArray 

这是部分,你已经完成了。现在后面的所有内容(字段、方法)都将成为该类的一部分。注意:我已经创建了 final 类以防止子类化,否则会引入一些其他问题。


私有 int 数组 [] 字段。您必须在内部将数据存储在常规的部分填充整数数组中。请不要使用 ArrayList。分配数组的大小就是它的容量,下面会讨论。

你也已经这样做了:

private int[] array;

请注意,我通常会将该字段标记为private。但由于其他一些需求过于扩大和缩小该数组,我们不能这样做。


私有 int 大小字段。此变量存储数组中“占用”元素的数量。在构造函数中设置为 0。

你也已经这样做了:

private int size;

“在构造函数中设置为 0”的要求是多余的,因为在 Java 中这些字段被初始化为其默认值(在本例中为 0)。这意味着,每当您创建 DynamicArray 类的实例时,size 字段的值将自动为 0。因此,我不会在以下构造函数中设置大小。


带参数的构造函数。该参数定义了初始数组的容量。分配给定容量的数组,将 size 字段设置为零。如果给构造函数的参数小于0,则抛出IllegalArgumentException。

虽然您已经向我们展示了构造函数,但您忘记了初始化数组。我们开始:

public DynamicArray(int capacity) 
    if (capacity < 0)
        throw new IllegalArgumentException("Capacity cannot be less than 0.");
    array = new int[capacity];

我认为即使容量为 0 也没有多大意义,但我们可以稍后处理(当数组增长时)。


无参数构造函数。分配大小为 10 的数组,将 size 字段设置为 0。

你已经展示了构造函数,但你应该使用构造函数链接,因为你已经有另一个构造函数来处理初始化。

public DynamicArray() 
    this(10);

最好有一个常数:

private static final int DEFAULT_CAPACITY = 10;

public DynamicArray() 
    this(DEFAULT_CAPACITY);


复制构造函数。构造函数将 DynamibcArray 类型的对象作为参数并将其复制到它创建的对象中。如果传递给 copy from 的对象为 null,则构造函数将抛出 IllegalArgumentException。

很遗憾,你弄错了。复制构造函数接受另一个 DynamicArray(不是 int[])。

public DynamicArray(DynamicArray source) 
    if (source == null)
        throw new IllegalArgumentException("Source array must not be null.");
    this.array = Arrays.copyOf(source.array, source.array.length);
    this.size = source.size;

这使用辅助类java.util.Arrays 来创建内部数组的副本。


int getSize() 返回大小——数组中被占用的元素数。

好的。这个很简单:

public int getSize() 
    return size;


int [] toArray() 访问器返回数组。确保您不返回私有数组字段。相反,为新数组分配内存,将数组字段复制到该新对象中,然后返回新数组。

虽然你有这个方法,但你忘了复制内部数组。你不能退回那个。而且您不应该返回它,因为任何调用者都可以更改您的内部数组。所以我们将使用与复制构造函数相同的技术:

public int[] toArray() 
    return Arrays.copyOf(array, array.length);


public void push(int num) 将一个新元素添加到数组的末尾并增加 size 字段。如果数组已满,则需要增加数组的容量。

这个方法有很大的作用。所以你应该使用一些辅助方法,例如用于不断增长的功能。

public void push(int num) 
    growIfNecessary();
    array[size] = num;
    size += 1;

一些解释:我现在推迟了不断增长的功能(所以我将其提取到另一种方法中,稍后将展示)。要将给定的数字推入内部数组,您必须知道确切的位置。幸运的是,size 字段准确地告诉了您这一点,但您还必须增加该 指针。你也可以这样做:

public void push(int num) 
    growIfNecessary();
    array[size++] = num;


public int pop() throws RuntimeException 删除数组的最后一个元素并返回它。减小大小字段。如果数组为空,则必须抛出带有消息“数组为空”的 RuntimeException。此时检查阵列的容量。如果容量是占用元素个数(大小)的 4 倍,就该收缩数组了。

public int pop() 
    if (size == 0)
        throw new NoSuchElementException();
    int result = array[--size];
    shrinkIfNecessary();
    return result;

提示:我抛出了另一个更适合这种情况的异常。弹出一个元素是推送一个相反的功能。我们首先递减指针,然后读取并返回值。此外,我将收缩功能提取到另一种方法中,稍后我也会展示。


int get(int index) throws IndexOutOfBoundsException 返回具有请求索引的数组元素。如果提供的索引太大或为负数,则会抛出 IndexOutOfBoundsException 并显示“非法索引”消息。

您完全正确:

public int get(int index) throws IndexOutOfBoundsException 
    if (index >= size || index < 0)
        throw new IndexOutOfBoundsException("Illegal index");
    return array[index];


int indexOf(int key) 返回给定数字第一次出现的索引。未找到数字时返回 -1。

使用此方法,您将在数组中搜索给定的数字。它需要一个循环!

public int indexOf(int key) 
    for (int i = 0; i < size; i++)
        if (array[i] == key)
            return i; // found
    return -1; // not found


void add(int index, int num) throws IndexOutOfBoundsException 将一个新元素(作为参数 num 传递)添加到由 index 参数指定的数组位置。如果索引大于数组大小或小于 0,则抛出 IndexOutOfBoundsException。将元素添加到数组中间时,您必须将所有元素向右移动以为新元素腾出空间。如果数组已满且没有空间容纳新元素,则数组的大小必须加倍。请按照 push() 方法说明中列出的步骤将数组的容量翻倍。

这个有点高级,因为它需要移动元素,而你在尝试中没有这样做。那我们走吧:

public void add(int index, int num) throws IndexOutOfBoundsException 
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException("Illegal index");
    growIfNecessary();
    System.arraycopy(array, index, array, index + 1, size - index);
    array[index] = num;
    size += 1;

说明:索引检查现在必须检查索引是否大于大小(不大于或等于),因为您还可以在数组末尾添加一个元素(在位置size 指向)。然后,如果需要,我们必须增加数组(就像在 push 方法中一样)。然后我使用 Java 内置类将所有元素从给定索引向右移动一个位置到末尾。最后一步是在给定索引处添加元素,我们不能忘记增加大小。


int remove (int index) throws IndexOutOfBoundsException 删除此数组中指定位置的元素。当从数组中间移除元素时,必须移动所有元素以关闭移除元素造成的间隙。如果传递给方法的索引值大于或等于大小或小于 0,则必须抛出 IndexOutOfBoundsException。此时检查阵列的容量。如果容量是占用元素个数(大小)的 4 倍,就该收缩数组了。

这是最难的方法。我将直接向您展示整个过程:

public int remove(int index) 
    if (index >= size || index < 0)
        throw new IndexOutOfBoundsException("Illegal Index");
    int result = array[index];
    System.arraycopy(array, index + 1, array, index, size - index - 1);
    size -= 1;
    shrinkIfNecessary();
    return result;

我们首先要检查索引,然后我们可以获取稍后将通过此方法返回的元素。现在是移位:我们将所有元素从位置index + 1 移到末尾向左一个位置。然后我们减小大小并在必要时缩小内部数组。就是这样。


现在我将向您展示最后两个辅助方法。

让我们从增长的方法开始:

一个。创建一个大小等于原始数组容量两倍的新数组。湾。将数组字段中的所有元素复制到新数组。 C。将新元素添加到新数组的末尾。 d。使用新数组作为数组字段。

private void growIfNecessary() 
    if (size == array.length) 
        if (array.length == 0)
            array = new int[1]; // Special case for capacity 0.
        else
            array = Arrays.copyOfRange(array, 0, array.length * 2);
    

如果大小等于容量,则数组已满。同样,我正在使用Arrays 助手类。 copyOfRange 方法可以创建更大的数组。

现在介绍收缩方法:

一个。创建一个大小等于原始数组一半容量的新数组。湾。将数组字段中的所有元素复制到新数组。 C。使用新数组作为数组字段。

private void shrinkIfNecessary() 
    if (size * 4 <= array.length)
        array = Arrays.copyOfRange(array, 0, array.length / 2);

在这里,我们没有任何特殊情况。如果容量是大小的 4 倍,则只需缩小阵列。就是这样。


仔细研究代码并尝试重现代码。库方法(System.arraycopyArrays.copyOf)的使用可以很容易地被使用临时数组的简单for 循环替换。试试看。

【讨论】:

【参考方案3】:

public int pop() throws RuntimeException 删除最后一个元素 数组并返回它。减小大小字段。如果数组是 必须清空带有消息“数组为空”的 RuntimeException 抛出。此时检查阵列的容量。如果容量 是占用元素个数(大小)的 4 倍,它是 是时候缩小数组了:

一个。创建一个大小等于容量一半的新数组 原来的。湾。将数组字段中的所有元素复制到 新数组。 C。使用新数组作为数组字段。

目前,您的 pop 命令只是递归地运行自己。这会崩溃。

编辑:我建议在 pop 之前部分实现 remove,因为它会使 pop 更容易。

让我们把它分解成一个要点列表并检查每个项目。 (我还建议针对您需要的每条具体建​​议提出一个新问题)

    public int pop() throws RuntimeException 删除数组的最后一个元素并返回它。 减小大小字段。 如果数组为空,则必须抛出带有“数组为空”消息的 RuntimeException。 此时检查阵列的容量。如果容量是占用元素个数(大小)的 4 倍,就该收缩数组了: 一个。创建一个大小等于原始数组一半容量的新数组。湾。将数组字段中的所有元素复制到新数组。 C。使用新数组作为数组字段。

现在我已将其分解为多个部分,首先我会忽略 3、4、5、6

并且只实现一个超级笨拙的 pop 方法,该方法只是从数组中删除最后一个元素。

一旦你完成了这项工作,就专注于边缘情况。

3. Decrements the size field 这将是整个班级的交叉问题。您只希望在代码的一小部分可重复使用的部分中执行此操作,因为每次添加或删除元素时都会弄乱大小字段。

4. If the array is empty a RuntimeException with the message "Array is empty" must be thrown. 您需要在 pop 方法的一开始就解决这个问题,因为它是保护无效使用的保护子句。

5.At this point check the capacity of the array. If the capacity is 4 times larger than the number of occupied elements (size), it is time to shrink the array: 同样,这可能会成为一个反复出现的横切关注点,因此将其定义为可重用的单独私有方法会很有帮助。

6. a. Create a new array with the size equal to half of the capacity of the original one. b. Copy all the elements from the array field to the new array. c. Use new array as an array field. 正在就如何最好地实施 5 提供指导。

您可以在 Arrays 静态帮助程序类的文档中找到有关数组复制和其他内容的更多信息。 https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html 以及 System https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#arraycopy(java.lang.Object,%20int,%20java.lang.Object,%20int,%20int) 中的 arrayCopy 方法

【讨论】:

以上是关于在 Java 中不使用 Arraylist 的情况下为类制作动态数组,并且没有正确教授如何去做的主要内容,如果未能解决你的问题,请参考以下文章

在java中不使用循环读取完整文件

如何在不指向 Java 源列表中的对象的情况下复制 ArrayList? [复制]

JAVA_ArrayList和HashSet

Java的ArrayList实现

Java集合--ArrayList源码

Java 中的 FileLock 在 Docker 挂载卷中不起作用