目录
3.2 E remove(int index) 和 boolean remove(Object o)
3.3 List subList(int fromIndex, int toIndex)
在给定下标的情况下,需要经常性的访问/更新元素,采用顺序表来存储数据。
一、ArrayList<E>对象的创建
① ArrayList在java.util这个包里,使用时要导包:import java.util.ArrayList;
② ArrayList是一个泛型类,在创建对象时需要传类型。
③ ArrayList对象的创建有两种形式,如下图。
ArrayList实现了List接口,因此可以写成方式一的形式,不过list引用能够调用的方法一定少于arrayList引用中的方法,因为ArrayList类不仅实现了List接口,而且继承和实现了其他的类和接口。
二、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> c)
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方法的返回值时得用List<>类型的引用接收)。
【注意】:使用subList截取的结果并不是重新new了一个数组存放的截取部分,而是返回了顺序表中下标为formLindex元素的地址。如果我们修改了截取部分数组中的元素的值,顺序表中的对应部分元素的值也会同时发生改变。
3.4 常用的其他方法
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种遍历方式,使用下标遍历的好处是可以通过下标去修改顺序表中元素的值,其他3种方式(除sout)仅能得到每个元素的值,不能够修改。
4.1 sout直接输出
为什么可以用这种方法呢?因为当使用sout输出对象的引用时,打印的结果想要达到打印该对象的所有成员变量的值时,要么是ArrayList中重写了Object类的toString方法,要么它所继承类中重写了Obfect类中的toString方法,通过观察发现的确如此。
4.2 for循环 + 下标
4.3 foreach循环
4.4 迭代器iterator
【书写格式】:Iterator<xxx> name = 顺序表.iterator(); 然后通过while循环利用name引用遍历顺序表中每个元素,循环的条件是name.hasNext(),来判断是否有下一个元素,取元素的值时用name.next()取即可。
【解惑】:之所以arrayList对象可以调用iterator方法是因为,ArrayList<E>实现了Iterable<T>接口,Iterable<T>接口中有抽象方法iterator,而ArrayList<E>中重写了iterator方法。
下图是Iterable<T>接口,可以看到它有抽象方法iterator:
下图是ArrayList<E>中重写的iterator方法,可以发现它的返回类型是Iterator<E>,调用该方法后,接收返回值的时候要注意一下,要写成Iterator<E>类型。
4.5 迭代器listIterator
【书写格式】:Iterator<xxx> name = 顺序表.listIterator();
或 ListIterator<xxx> name = 顺序表.listIterator();
然后通过while循环,循环的条件是name.hasNext(),来判断是否有下一个元素,取元素的值时用name.next()取即可。
【注意】ListIterator<E> 是 Iterator<E>的子类,所以可以写成书写格式一的样子。
下图说明了,listIterator方法的返回值可以用ListIterator<>或Iterator<>类型的引用接收。
4.6 迭代器listIterator倒着遍历
【说明】:listIterator方法可以传整型,而iterator方法是不可以传参的。如果给listIterator方法传整型则可以倒叙遍历顺序表,不过开始的书写格式必须写成, ListIterator<xxx> name = 顺序表.listIterator(); 不能写成Iterator<xxx> name = 顺序表.listIterator(); 因为Iterator<E>类中没有hasPrevious、previous方法。
JAVA中ListIterator和Iterator详解与辨析 - Willie.wang - 博客园 (cnblogs.com)
五、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的缺点于是引入了链表的概念,不过链表的优缺点 其实就是 顺序表的缺优点
七、学习目标
1. 如何创建ArrayLisr对象,两种创建方法有什么区别?
2. ArrayList的三种构造方法分别做了什么事?
3. 再次提醒,泛型类的构造方法,方法名的后面不要写<>
4. 如果顺序表通过构造方法指向的是一个大小为0的常量数组,为什么add方法可以正常使用,对顺序表的扩容/修改顺序表指向的代码在哪里实现的?
5. add()尾插,add(,)在指定位置插入,addAll()尾插另一个xx表
6. 如果给remove传int类型的值表示删除顺序表中下标为i的元素,并返回被删除的值,如果顺序表中的元素的值存入时以int类型存入的(顺序表的类型是Integer,将int类型的数据存入顺序表中时其实发生了装箱的操作),想要删除某int类型的数据,则需要先将int类型的数据装箱再传,此时调用的是remove(Object o)方法,表示删除顺序表中元素o。
7. subList可以截取顺序表的指定部分,返回的结果需要用Lis<E>类型的引用a进行接收,需要注意的是,引用a指向的不是一个新的对象, 而是指向着原顺序表中下标为formLindex元素的地址。如果我们修改了截取部分数组中的元素的值,顺序表中的对应部分元素的值也会同时发生改变。
8. ArrayList中常用的方法要记得。
9 sout、for、foreach、iterator、listIterator五种遍历顺序表的方式分别如何使用?
10. 顺序表的优缺点?
本篇已完结 ......