Java入门--简单学会泛型动态数组的编写

目录

一、如何实现一个泛型动态数组

泛型动态数组的定义

二、泛型动态数组的基本操作

1.size()和isEmpty()

2、add(T e)和addAll(T[] array)

3、insert(int index, T e)

4、get(int index)和replace(int index, T e)

5、remove(int index)和removeRange(int start, int end)

三、toString方法的重写

四、对泛型动态数组的测试


一、如何实现一个泛型动态数组

动态数组是一种常用的数据结构,它可以根据需要自动调整大小,而不需要事先指定数组的长度。在Java中,我们可以使用ArrayList类来创建一个动态数组,但是它只能存储Object类型的元素,如果我们想要存储其他类型的元素,例如int,double,String等,我们就需要进行类型转换,这会增加代码的复杂度和出错的风险。为了解决这个问题,我们可以使用泛型来实现一个动态数组,泛型可以让我们在编译时指定元素的类型,从而避免类型转换和提高代码的安全性和可读性。

泛型动态数组的定义

要实现一个泛型动态数组,我们首先需要定义一个泛型类,使用尖括号(<T>)来表示泛型参数T,T可以是任意的引用类型。然后,我们需要定义一个内部的数组来存储元素,由于Java不支持直接创建泛型数组,我们需要使用Object类型的数组,并在合适的地方进行强制类型转换。此外,我们还需要定义两个变量来记录数组的长度和元素的个数,分别为length和size。最后,我们需要提供一个构造方法来初始化数组,并检查参数是否合法。

public class MYArray<T>{
    T[] array; //内部数组
    int size; //元素个数
    int length; //数组长度
    public MYArray(int initLength){ //构造方法
        if(initLength <= 1){ //检查参数是否合法
            throw new RuntimeException("数组长度必须大于1");
        }
        array = (T[]) new Object[initLength]; //创建Object类型的数组并强制转换为泛型类型
        length = initLength; //初始化数组长度
        size = 0; //初始化元素个数
    }
}

二、泛型动态数组的基本操作

接下来,我们需要实现一些基本的操作来对泛型动态数组进行增删改查。这些操作包括:

  • size(): 返回元素个数
  • isEmpty(): 判断是否为空
  • add(T e): 在末尾添加一个元素
  • addAll(T[] array): 在末尾添加一个数组
  • insert(int index, T e): 在指定位置插入一个元素
  • get(int index): 获取指定位置的元素
  • remove(int index): 删除指定位置的元素并返回
  • removeRange(int start, int end): 删除一段元素并返回删除个数
  • replace(int index, T e): 替换指定位置的元素并返回旧值

1.size()和isEmpty()

这两个方法比较简单,只需要返回size变量和判断size是否为0即可。

1.1 返回元素个数
public int size(){ //返回元素个数
    return size;
}

1.2 判断数组是否为空
public boolean isEmpty() { //判断是否为空
    return size == 0;
}

2、add(T e)和addAll(T[] array)

这两个方法用于在末尾添加元素或者数组。首先,我们需要检查内部数组是否已满,如果是,则需要进行扩容操作,即创建一个新的两倍大小的数组,并将旧数组的元素复制到新数组中。然后,我们将要添加的元素或者数组赋值给新数组的相应位置,并更新size变量。

//2.1:添加一个元素
public void add(T e ){ //在末尾添加一个元素
    if (size == array.length) { //检查是否需要扩容
        T[] newArr = (T[]) new Object[size * 2]; //创建新的两倍大小的数组
        for (int i = 0 ; i < size; i++){ //复制旧数组的元素到新数组中
            newArr[i] = array[i];
        }
        System.out.println("扩容完成了!");
        array = newArr; //将新数组赋值给内部数组
    }
    array[size] = e; //将元素赋值给内部数组的末尾位置
    size++; //更新元素个数
}
//2.2: 添加一个数组
public int addAll(T[] array){ //在末尾添加一个数组
    if (array == null) { //检查参数是否为空
        throw new NullPointerException("Argument cannot be null.");
    }
    int count = 0; //记录添加的个数
    for (T element : array) { //遍历要添加的数组
        add(element); //调用add方法添加每个元素
        count++; //更新添加的个数
    }
    return count; //返回添加的个数
}

3、insert(int index, T e)

这个方法用于在指定位置插入一个元素。首先,我们需要检查索引是否合法,即是否在0到size之间,如果不是,则抛出异常。然后,我们需要检查内部数组是否已满,如果是,则进行扩容操作,同上。接着,我们需要从后往前遍历内部数组,将索引位置及其后面的元素都向后移动一位,为插入的元素腾出空间。最后,我们将要插入的元素赋值给索引位置,并更新size变量。

// 3.1 按照索引插入元素
public void insert(int index,T e){ //在指定位置插入一个元素
    if (index < 0 || index > size) { //检查索引是否合法
        throw new IndexOutOfBoundsException("Index out of range.");
    }
    if (size == array.length) { //检查是否需要扩容
        T[] newArr = (T[]) new Object[size * 2]; //创建新的两倍大小的数组
        for (int i = 0; i < size; i++) { //复制旧数组的元素到新数组中
            newArr[i] = array[i];
        }
        array = newArr; //将新数组赋值给内部数组
    }
    for (int i = size - 1; i >= index; i--) { //从后往前遍历内部数组
        array[i + 1] = array[i]; //将索引位置及其后面的元素都向后移动一位
    }
    array[index] = e; //将元素赋值给索引位置
    size ++; //更新元素个数
}

4、get(int index)和replace(int index, T e)

这两个方法用于获取或者替换指定位置的元素。首先,我们需要检查索引是否合法,即是否在0到size-1之间,如果不是,则抛出异常。然后,我们分别返回或者赋值给内部数组的索引位置,并更新旧值。

//4.1获取元素
public T get(int index){ //获取指定位置的元素
    if (index < 0|| index >= size){ //检查索引是否合法
        throw new IndexOutOfBoundsException("Index out of range.");
    }
    return array[index]; //返回内部数组的索引位置的元素
}
//4.2修改元素
public T replace(int index ,T e) { //替换指定位置的元素并返回旧值
    if (index < 0 || index >= size) { //检查索引是否合法
        throw new IndexOutOfBoundsException("Index out of range.");
    }
    T old = array[index]; //记录旧值
    array[index] = e; //将新值赋值给内部数组的索引位置
    return old; //返回旧值
}

5、remove(int index)和removeRange(int start, int end)

这两个方法用于删除指定位置或者一段的元素。首先,我们需要检查索引或者范围是否合法,即是否在0到size-1之间,并且start不大于end,如果不是,则抛出异常。然后,我们分别记录要删除的元素或者个数,并从前往后遍历内部数组,将要删除的位置或者范围之后的元素都向前移动一位,覆盖掉要删除的元素。最后,我们将内部数组的末尾位置或者范围置为null,并更新size变量。

//5.删除元素
public T remove(int index){ //删除指定位置的元素并返回
    if (index < 0 || index >= size) { //检查索引是否合法
        throw new IndexOutOfBoundsException("Index out of range.");
    }
    T temp = array[index]; //记录要删除的元素
    for (int i = index + 1; i < size; i++) { //从前往后遍历内部数组
        array[i - 1] = array[i]; //将要删除的位置之后的元素都向前移动一位
    }
    size--; //更新元素个数
    array[size] = null; //将内部数组的末尾位置置为null
    return temp; //返回要删除的元素
}
//5.1 删除一段元素
public int removeRange(int start,int end){ //删除一段元素并返回删除个数
    if (start < 0 || start >= size || end < 0 || end >= size || start > end) { //检查范围是否合法
        throw new IndexOutOfBoundsException("Index out of range.");
    }
    int count = 0; //记录删除的个数
    for (int i = end; i < size; i++) { //从前往后遍历内部数组
        array[i - (end - start)] = array[i]; //将要删除的范围之后的元素都向前移动end-start位
    }
    for (int i = size - (end - start); i < size; i++) { //从后往前遍历内部数组
        array[i] = null; //将内部数组的末尾范围置为null
    }
    count = end - start; //计算删除的个数
    size -= count; //更新元素个数
    return count; //返回删除的个数
}

三、toString方法的重写

在泛型动态数组中,重写toString方法的目的是为了将数组中的元素以字符串的形式输出,方便查看和调试。一种常见的重写方式是使用StringBuilder类来拼接数组中的元素,然后返回StringBuilder对象的字符串表示。

@Override
public String toString() {
    StringBuilder res = new StringBuilder();
    res.append(String.format("Array: size = %d , capacity = %d\\n", size, data.length));
    res.append('[');
    for(int i = 0 ; i < size ; i ++) {
        res.append(data[i]);
        if(i != size - 1)
            res.append(", ");
    }
    res.append(']');
    return res.toString();
}

这个方法首先使用String.format方法来格式化数组的大小和容量,然后使用append方法来添加数组中的元素,注意在元素之间添加逗号和空格,最后在数组两端添加方括号。返回res.toString()就是数组的字符串表示。

四、对泛型动态数组的测试

最后,我们需要编写一个main方法来测试我们实现的泛型动态数组是否正确。我们可以创建一个长度为5的动态数组,并添加10个整数到数组中,然后打印出数组的大小和元素。我们还可以尝试其他操作,例如插入,获取,替换,删除等,并观察结果是否符合预期。

public static void main(String[] args) {
    //创建一个长度为5的动态数组
    MYArray<Integer> myArray = new MYArray<>(5);
    //添加10个整数到数组中
    for (int i = 0; i < 10; i++) {
        myArray.add(i + 1);
    }
    //打印数组的大小和元素
    System.out.println("The size of the array is: " + myArray.size());
    System.out.println("The elements of the array are: ");
    for (int i = 0; i < myArray.size(); i++) {
        System.out.print(myArray.get(i) + " ");
    }
}

运行结果如下:

扩容完成了!
The size of the array is: 10
The elements of the array are: 
1 2 3 4 5 6 7 8 9 10 

这样,我们就完成了一个泛型动态数组的实现和测试。希望这篇博客对你有所帮助。如果你有任何问题或者建议,请在评论区留言。谢谢你的阅读!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java泛型Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将类型参数化,实现代码的通用性。 一、泛型的基本语法 在声明类、接口、方法时可以使用泛型泛型的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类型参数,多个类型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个泛型类,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明中,`<T>`就是类型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型类的使用 在使用泛型类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个泛型类,`<String>`指定了具体的类型参数,即`data`字段的类型为`String`,`gc`对象被创建时没有指定类型参数,因为编译器可以根据上下文自动推断出类型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的类型参数,匿名内部类实现了该接口,并使用`String`作为类型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个泛型方法,`<T>`指定了类型参数,`T data`表示一个类型为`T`的参数,调用时可以传入任何类型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体类型,可以使用通配符`?`。通配符可以作为类型参数出现在方法的参数类型或返回类型中,但不能用于声明泛型类或泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类型为`List<?>`,表示可以接受任何类型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类型来遍历`List`中的元素。 四、泛型的继承 泛型类和泛型接口可以继承或实现其他泛型类或泛型接口,可以使用子类或实现类的类型参数来替换父类或接口的类型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类型参数`T`。 五、泛型的限定 有时候,我们需要对泛型的类型参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类型参数的上限,或使用`super`关键字来限定类型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类型参数`T`被限定为`Number`的子类,`GenericInterface`的类型参数`T`被限定为实现了`Comparable`接口的类。 六、泛型的擦除 在Java中,泛型信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取泛型的具体类型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类型为`List<String>`,但是在运行时,`getClass`返回的类型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型的类型推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类型推断等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值