目录
一、ArrayList变量的创建
二、ArrayList的三种构造方法
2.1 ArrayList()
2.2 ArrayList(int)
2.3 ArrayList(Collection)
三、ArrayList常用方法介绍
3.1 boolean add(E e)
3.2 E remove(int index) 和 boolean remove(Object o)
3.3 List subList(int fromIndex, int toIndex)
3.4 常用的其他方法
四、ArrayList的遍历
4.1 for循环 + 下标
4.2 foreach循环
4.3 迭代器iterator
4.4 迭代器listIterator
4.5 迭代器listIterator倒着遍历
五、ArrayList的扩容机制
六、ArrayList的优缺点
一、ArrayList<E>变量的创建
二、ArrayList<E>的三种构造方法
2.1 ArrayList()
如果在实例化ArrayList<E>对象时调用的是不带参数的构造方法则,编译器会默认让顺序表指向一个大小为0的Object[]常量数组。
2.2 ArrayList(int)
如果在实例化ArrayList<E>对象时调用的是参数为int的构造方法则,如果initialCapacity的值大于0,则实例化一个大小为initialCapacity大小的Object[]类型的数组,如果initialCapacity的值等于0,则编译器会让顺序表指向一个大小为0的常量Object[]数组,如果initialCapacity的值小于0,则会抛出异常。
2.3 ArrayList(Collection<? extends E>)
1. 在实例化ArrayList对象时可以给构造方法传引用数据类型的对象,但是对象的类型必须实现了Collection接口,且接口的<>里得是E或E的子类,其中E表示ArrayList中<>的泛型。
2. 下图中我们将arrayList对象作为参数传递给了ArrayList<E>的构造方法,为什么不报错,而将arrayList3对象作为参数传递给了ArrayList<E>的构造方法,为什么报错?
答:不能将ArrayList<Integer>理解成Integer的子类,arrayList传入之所以符合是因为,它的类型ArrayList<Integer>,ArrayList实现了Collection接口,而<>中的Integer也是E的子类所以可以,如果把arrayList的类型修改成ArrayList<String>,String不是E的子类,在编译器中可以看到是不可以的。
三、ArrayList<E>常用方法介绍
3.1 boolean add(E e)
了解完ArrayLisr<E>的构造方法后,我们会发现在ArrayList<E>类中如果不明确指定顺序表的大小,则编译器在构造方法初始化顺序表时会让顺序表指向一个大小为0的Object[]类型的数组,如果我们直接在实例化ArrayList<E>对象后,使用对象的引用调用add方法,会发现编译器并没有报错,可是顺序表的大小不是0吗,我们也没看到扩容操作啊,为什么不报错呢?
答:如下图所示,我们观察到add方法内部的代码,发现在给顺序表有效数据后插入新的数据前,会先判断顺序表的大小和有效数组个数size的大小是否相等,如果相等,表示需要先对顺序表进行扩容操作,具体的扩容细节见下图所示,如果是因为顺序表本身就没开辟空间,则会将顺序表的大小设置成10,如果是因为顺序表已满,则将顺序表按照原本大小的1.5被扩容。总之,之所以没报错的原因是,add方法里存在扩容代码。
3.2 E remove(int index) 和 boolean remove(Object o)
E remove(int index) :删除 index 位置元素
boolean remove(Object o):删除遇到的第一个 o
【注意】:在给remove方法传参时,如果传递的是整型,编译器只会把它认为是下标,而不会认为是你想删除顺序表中第一次出现的元素10,如果想达到删除顺序表中第一次出现的元素10,得先将10装箱,示例代码:arrayList.remove(Integer.valueOf(10))
3.3 List subList(int fromIndex, int toIndex)
List<E> subList(int fromIndex, int toIndex):截取部分 list,仍然是左闭右开(注意一下该方法的返回值为List<E>,别写成其他的了)。
【注意】:使用subList截取的结果并不是重新new了一个数组存放的截取部分,而是返回了顺序表中下标为formLindex元素的地址。如果我们修改了截取部分数组中的元素的值,顺序表中的对应部分元素的值也会同时发生改变。
3.4 常用的其他方法
注意一下:remove(int)的返回值为被移除的元素。
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(100);//在表尾插入元素
arrayList.add(101);
arrayList.add(102);
System.out.println(arrayList);//[100, 101, 102]
arrayList.add(1,200);//在指定下标处插入元素
System.out.println(arrayList);//[100, 200, 101, 102]
arrayList.add(2,300);
System.out.println(arrayList);//[100, 200, 300, 101, 102]
ArrayList<Integer> cur = new ArrayList<>();
cur.add(999);
cur.add(888);
cur.add(999);
arrayList.addAll(cur);//在表尾插入另一个顺序表
System.out.println(arrayList);//[100, 200, 300, 101, 102, 999, 888, 999]
arrayList.remove(1);//删除指定下标的元素
System.out.println(arrayList);//[100, 300, 101, 102, 999, 888, 999]
arrayList.remove(2);
System.out.println(arrayList);//[100, 300, 102, 999, 888, 999]
arrayList.remove(Integer.valueOf(999));//删除第一次出现的999
System.out.println(arrayList);//[100, 300, 102, 888, 999]
Integer a = arrayList.get(1);//得到某下标的元素
arrayList.set(1,777);//把某下标的元素置为777
System.out.println(arrayList);//[100, 777, 102, 888, 999]
//arrayList.set(100,777);//越界异常
boolean b = arrayList.contains(777);//是否包含某元素
System.out.println(b);//true
arrayList.add(777);
System.out.println(arrayList);//[100, 777, 102, 888, 999, 777]
int c = arrayList.indexOf(777);//某元素第一次出现的下标
System.out.println(c);//1
int d = arrayList.lastIndexOf(777);//从后往前找,某元素第一次出现的位置
System.out.println(d);//5
List<Integer> e = arrayList.subList(1,3);//
System.out.println(e);//[777, 102]
e.set(0,33333);
System.out.println(arrayList);//[100, 33333, 102, 888, 999, 777]
System.out.println(e);//[33333, 102]
arrayList.clear();
System.out.println(arrayList);//[]
}
四、ArrayList<E>的遍历
下面将提供5种遍历方式,使用下标遍历的好处是可以通过下标去修改顺序表中元素的值,其他4中方式仅能得到每个元素的值,不能够修改。
4.1 for循环 + 下标
4.2 foreach循环
4.3 迭代器iterator
【书写格式】:Iterator<xxx> name = 顺序表.iterator(); 然后通过while循环,循环的条件是name.hasNext(),来判断是否有下一个元素,取元素的值时用name.next()取即可。
【解惑】:之所以arrayList对象可以调用iterator方法是因为,ArrayList<E>实现了Iterable<T>接口,Iterable<T>接口中有抽象方法iterator,而ArrayList<E>中重写了iterator方法。
下图是ArrayList<E>中重写的iterator方法,可以发现它的返回类型是Iterator<E>,调用该方法后,接收返回值的时候要注意一下,要写成Iterator<E>类型的:
下图是Iterable<T>接口,可以看到它有抽象方法iterator:
4.4 迭代器listIterator
【书写格式】:Iterator<xxx> name = 顺序表.listIterator();
或 ListIterator<xxx> name = 顺序表.listIterator();
然后通过while循环,循环的条件是name.hasNext(),来判断是否有下一个元素,取元素的值时用name.next()取即可。
【注意】ListIterator<E> 是 Iterator<E>的子类,所以可以写成书写格式一的样子。
之所以arrayList对象可以调用listIterator方法是因为ArrayList类里面实现了这个该方法了的,不过通过观察ListIterator接口的源码可以发现它里面并没有listIterator方法,所以初步断定ArrayList并不是重写了ListIterator接口中的listIterator方法,而是它自己的listIterator方法。
4.5 迭代器listIterator倒着遍历
【说明】:listIterator方法可以整型,而iterator方法是不可以传参的。如果给listIterator方法传整型则可以倒叙遍历顺序表,不过开始的书写格式必须写成, ListIterator<xxx> name = 顺序表.listIterator(); 不能写成Iterator<xxx> name = 顺序表.listIterator(); 因为Iterator<E>没有hasPrevious、previous方法。
五、ArrayList<E>的扩容机制
该扩容机制在上文的add方法中就已经讲解得差不多了,这里主要想强调的是:
① ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。
② 扩容时调用的时grow方法。
③ 使用Arrays.copyOf(数组,新的数组大小)进行具体的扩容。
④ ArrayList<E>的扩容方法被封装了,我们无法通过ArrayList<E>对象的引用调用对应的扩容方法,它底层的扩容逻辑是按原数组的1.5倍进行扩容的。
六、ArrayList的优缺点
1. 优点:ArrayList底层使用连续的空间,在访问某一具体的元素时所用的时间复杂度为O(1)
2. 缺点:
① ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)。
② 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。增容一般是呈2或1.5倍的增长,势必会有一定的空间浪费,例如当前容量为100,满了以后增容到200,我们再继 续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
3. 针对ArrayList的缺点于是引入了链表的概念,不过链表的优缺点 其实就是 顺序表的缺优点
本篇已完结 ......