03-ArrayList(CustomDynamicArray)
数据结构是什么?
.
.
动态数组
.
.
.
Person.java
package com.rnny;
/**
* @author 软柠柠吖(Runny)
* @date 2023-01-03
*/
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected void finalize() throws Throwable {
System.out.println("Person - finalize");
}
}
Assert.java
package com.rnny;
/**
* @author 软柠柠吖(Runny)
* @date 2023-01-01
*/
public class Assert {
public static void test(boolean value) {
try {
// if (value) {} 与 if (!value) {}❓
// 答:if (value):表示 value 为 true 时,执行 if 语句
// => 简单理解为:当 value 是真的时,执行 if 语句(额……有点多余)
// if (!value):表示 value 为 false 时,执行 if 语句
// => 简单理解为:当 value 为假的时,执行 if 语句。
// 总结:value => true ==> if (value) {}
// value => false ==> if (!value) {}
if (!value) {
throw new Exception("测试未通过❗");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ArrayList.java
ArrayList<E>
.
package com.rnny;
/**
* @author 软柠柠吖(Runny)
* @date 2023-01-01
*/
@SuppressWarnings("unchecked")
public class ArrayList<E> {
}
成员变量
/**
* 元素的数量
*/
private int size;
/**
* 所有的元素
*/
private E[] elements;
/**
* 默认容量 <br>
* static:保证变量的内存空间仅此一份
*/
private static final int DEFAULT_CAPACITY = 16;
/**
* 元素未发现
*/
private static final int ELEMENT_NOT_FOUND = -1;
构造函数
.
public ArrayList() {
// this(参数):调用有参构造
this(DEFAULT_CAPACITY);
}
public ArrayList(int capacity) {
// > VS <(大于号与小于号如何区分❓)
// 答:从左向右看(正常看的顺序),如果为大头,则为大于号 '>'
// 如果为小头,则为小于号 '<'
// 等价于:capacity = Math.max(capacity, DEFAULT_CAPACITY);
capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
this.size = 0;
// 对象数组中每个位置存储的是什么❓
// 答:地址❗对象数组中存储的是对象的地址,而不是具体的对象。
// 如果存储的是具体的对象,那 Object 就没法用了,这样设计也不好
// 因为具体对象的大小不确定,无法用 Object[] 接收。
// 存储地址就能完美的解决这个问题🐲
this.elements = (E[]) new Object[capacity];
}
clear()
.
/**
* 清除所有元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
size()
/**
* 元素的数量
*
* @return
*/
public int size() {
return size;
}
isEmpty()
/**
* 是否为空
*
* @return
*/
public boolean isEmpty() {
// 这里为什么要写 "size == 0" ?
// 答:自己不应该纠结 JAVA 的判断语句怎么写,而是要按照具体的逻辑写代码。
// 说的有点抽象❓ 一言以蔽之:用具体的代码来实现方法名所表达的含义❗
// 例如,方法名 isEmpty:是空的 ==> 则具体的代码就写判断为空即可 ==> 即代码为:"size == 0"
return size == 0;
}
contains(E element)
/**
* 是否包含某个元素
*
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
add(E element)
.
/**
* 添加元素到最后面
*
* @param element
*/
public void add(E element) {
// 代码重用
add(size, element);
//if (size >= this.elements.length) {
// resize();
//}
//this.elements[size++] = element;
}
add(int index, E element)
.
/**
* 往 index 位置添加元素
*
* @param index
* @param element
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
//if (size >= this.elements.length) {
// resize();
//}
//for (int i = size - 1; i >= index; i--) {
// elements[i + 1] = elements[i];
//}
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
this.elements[index] = element;
size++;
}
rangeCheckForAdd(int index)
/**
* 添加元素时检查下表是否越界
* @param index
*/
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
resize()
最终结果没有使用此方法
private void resize() {
E[] newArray = (E[]) new Object[elements.length * 2];
System.arraycopy(elements, 0, newArray, 0, elements.length);
elements = newArray;
}
ensureCapacity(int desiredCapacity)
.
/**
* 保证有 desiredCapacity 的容量
*
* @param desiredCapacity
*/
private void ensureCapacity(int desiredCapacity) {
// “微观” VS “宏观”❓
// 答:微观上指具体的数组元素(索引下标),即水桶中水的位置(刻度线)
// 宏观上指数组的容量,即水桶的大小(容积)
// ensureCapacity:保证数组有地方存储增加的元素,因此这里是 “宏观” 问题,即判断容量是否足够。
int oldCapacity = elements.length;
if (oldCapacity >= desiredCapacity) {
return;
}
// 新容量为旧容量的 1.5 倍
// 注意:位移运算符 >> 要和 () 一同使用,既提高了优先级,又不出错,容易阅读
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + " 扩容为 " + newCapacity);
}
get(int index)
/**
* 返回 index 位置对应的元素
*
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elements[index];
}
rangeCheck(int index)
/**
* 检查下标是否越界(查询或删除元素)
* @param index
*/
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
set(int index, E element)
/**
* 设置 index 位置的元素
*
* @param index
* @param element
* @return
*/
public E set(int index, E element) {
E oldValue = get(index);
this.elements[index] = element;
return oldValue;
}
outOfBounds(int index)
private void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
remove(int index)
.
.
/**
* 删除 index 位置对应的元素
*
* @param index
* @return
*/
public E remove(int index) {
E oldValue = get(index);
for (int i = index + 1; i < size; i++) {
this.elements[i - 1] = this.elements[i];
}
this.elements[--size] = null;
return oldValue;
}
remove(E element)
/**
* 删除与 element 相等的元素
* @param element
* @return
*/
public E remove(E element) {
return remove(indexOf(element));
}
indexOf(E element)
.
/**
* 查看元素的位置
*
* @param element
* @return
*/
public int indexOf(E element) {
// for 循环的 "i < size" 判断条件如何理解❓(注意:忽略 for 循环花括号里的语句)
// 答:① 按照 “指针” 思想来理解,将 i 看作指向每个数组元素的 “索引指针”❗
// 即,“索引指针” 从 0 开始(第一个元素)遍历,直到 size - 1(最后一个元素)结束。
// ② 按照 “执行次数 ” 来理解,这里符合 “等 0 小数”(i = 0, i < 执行次数)的规则,
// 那么 size 即为执行次数❗
// 即,for 循环执行 size 次,数组中每个元素都有被遍历到的机会。
if (element == null) {
for (int i = 0; i < size; i++) {
if (this.elements[i] == null) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(this.elements[i])) {
return i;
}
}
}
//for (int i = 0; i < size; i++) {
// if (this.elements[i] == null) {
// if (element == null) {
// return i;
// }
// } else if (this.elements[i].equals(element)) {
// return i;
// }
//}
return ELEMENT_NOT_FOUND;
}
toString()
.
@Override
public String toString() {
// 格式:size=3, [55, 22, 2]
// 字符串拼接推荐使用:StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
// 不需要减法运算,【推荐】
if (i != 0) {
sb.append(", ").append(elements[i]);
} else {
sb.append(elements[i]);
}
// 不是很好❓
// 原因:判断的时候需要 size - 1,进行减法运算,效率不高
//if (i == size - 1) {
// sb.append(elements[i]).append("]");
//} else {
// sb.append(elements[i]).append(", ");
//}
}
sb.append("]");
return sb.toString();
}
完整版
.
package com.rnny;
/**
* @author 软柠柠吖(Runny)
* @date 2023-01-01
*/
@SuppressWarnings("unchecked")
public class ArrayList<E> {
/**
* 元素的数量
*/
private int size;
/**
* 所有的元素
*/
private E[] elements;
/**
* 默认容量 <br>
* static:保证变量的内存空间仅此一份
*/
private static final int DEFAULT_CAPACITY = 16;
/**
* 元素未发现
*/
private static final int ELEMENT_NOT_FOUND = -1;
public ArrayList() {
// this(参数):调用有参构造
this(DEFAULT_CAPACITY);
}
public ArrayList(int capacity) {
// > VS <(大于号与小于号如何区分❓)
// 答:从左向右看(正常看的顺序),如果为大头,则为大于号 '>'
// 如果为小头,则为小于号 '<'
// 等价于:capacity = Math.max(capacity, DEFAULT_CAPACITY);
capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
this.size = 0;
// 对象数组中每个位置存储的是什么❓
// 答:地址❗对象数组中存储的是对象的地址,而不是具体的对象。
// 如果存储的是具体的对象,那 Object 就没法用了,这样设计也不好
// 因为具体对象的大小不确定,无法用 Object[] 接收。
// 存储地址就能完美的解决这个问题🐲
this.elements = (E[]) new Object[capacity];
}
/**
* 清除所有元素
*/
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
/**
* 元素的数量
*
* @return
*/
public int size() {
return size;
}
/**
* 是否为空
*
* @return
*/
public boolean isEmpty() {
// 这里为什么要写 "size == 0" ?
// 答:自己不应该纠结 JAVA 的判断语句怎么写,而是要按照具体的逻辑写代码。
// 说的有点抽象❓ 一言以蔽之:用具体的代码来实现方法名所表达的含义❗
// 例如,方法名 isEmpty:是空的 ==> 则具体的代码就写判断为空即可 ==> 即代码为:"size == 0"
return size == 0;
}
/**
* 是否包含某个元素
*
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
/**
* 添加元素到最后面
*
* @param element
*/
public void add(E element) {
add(size, element);
//if (size >= this.elements.length) {
// resize();
//}
//this.elements[size++] = element;
}
private void resize() {
E[] newArray = (E[]) new Object[elements.length * 2];
System.arraycopy(elements, 0, newArray, 0, elements.length);
elements = newArray;
}
/**
* 保证有 desiredCapacity 的容量
*
* @param desiredCapacity
*/
private void ensureCapacity(int desiredCapacity) {
// “微观” VS “宏观”❓
// 答:微观上指具体的数组元素(索引下标),即水桶中水的位置(刻度线)
// 宏观上指数组的容量,即水桶的大小(容积)
// ensureCapacity:保证数组有地方存储增加的元素,因此这里是 “宏观” 问题,即判断容量是否足够。
int oldCapacity = elements.length;
if (oldCapacity >= desiredCapacity) {
return;
}
// 新容量为旧容量的 1.5 倍
// 注意:位移运算符 >> 要和 () 一同使用,既提高了优先级,又不出错,容易阅读
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + " 扩容为 " + newCapacity);
}
/**
* 返回 index 位置对应的元素
*
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elements[index];
}
/**
* 检查下标是否越界(查询或删除元素)
* @param index
*/
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
/**
* 设置 index 位置的元素
*
* @param index
* @param element
* @return
*/
public E set(int index, E element) {
E oldValue = get(index);
this.elements[index] = element;
return oldValue;
}
/**
* 往 index 位置添加元素
*
* @param index
* @param element
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
//if (size >= this.elements.length) {
// resize();
//}
//for (int i = size - 1; i >= index; i--) {
// elements[i + 1] = elements[i];
//}
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
this.elements[index] = element;
size++;
}
/**
* 添加元素时检查下表是否越界
* @param index
*/
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
private void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
/**
* 删除 index 位置对应的元素
*
* @param index
* @return
*/
public E remove(int index) {
E oldValue = get(index);
for (int i = index + 1; i < size; i++) {
this.elements[i - 1] = this.elements[i];
}
this.elements[--size] = null;
return oldValue;
}
/**
* 删除与 element 相等的元素
* @param element
* @return
*/
public E remove(E element) {
return remove(indexOf(element));
}
/**
* 查看元素的位置
*
* @param element
* @return
*/
public int indexOf(E element) {
// for 循环的 "i < size" 判断条件如何理解❓(注意:忽略 for 循环花括号里的语句)
// 答:① 按照 “指针” 思想来理解,将 i 看作指向每个数组元素的 “索引指针”❗
// 即,“索引指针” 从 0 开始(第一个元素)遍历,直到 size - 1(最后一个元素)结束。
// ② 按照 “执行次数 ” 来理解,这里符合 “等 0 小数”(i = 0, i < 执行次数)的规则,
// 那么 size 即为执行次数❗
// 即,for 循环执行 size 次,数组中每个元素都有被遍历到的机会。
if (element == null) {
for (int i = 0; i < size; i++) {
if (this.elements[i] == null) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(this.elements[i])) {
return i;
}
}
}
//for (int i = 0; i < size; i++) {
// if (this.elements[i] == null) {
// if (element == null) {
// return i;
// }
// } else if (this.elements[i].equals(element)) {
// return i;
// }
//}
return ELEMENT_NOT_FOUND;
}
@Override
public String toString() {
// 格式:size=3, [55, 22, 2]
// 字符串拼接推荐使用:StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
// 不需要减法运算,【推荐】
if (i != 0) {
sb.append(", ").append(elements[i]);
} else {
sb.append(elements[i]);
}
// 不是很好❓
// 原因:判断的时候需要 size - 1,进行减法运算,效率不高
//if (i == size - 1) {
// sb.append(elements[i]).append("]");
//} else {
// sb.append(elements[i]).append(", ");
//}
}
sb.append("]");
return sb.toString();
}
}
Main.java
package com.rnny;
/**
* @author 软柠柠吖(Runny)
* @date 2023-01-01
*/
public class Main {
public static void main(String[] args) {
// 所有的类,最终都继承 java.lang.Object
// new:向堆空间申请内存
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//list.add(list.size() - 1, 100);
//list.set(0, 30);
//list.remove(2);
//System.out.println(list);
//Assert.test(list.get(list.size() - 2) == 100);
ArrayList<Person> personArrayList = new ArrayList<>();
personArrayList.add(new Person("jack", 12));
personArrayList.add(new Person("tom", 14));
personArrayList.add(new Person("scott", 16));
personArrayList.clear();
// 提醒 JVM 进行垃圾回收
System.gc();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!