List 接口
在整个集合中 List 是 Collection 的子接口,有序集合(也称为序列 )。 该接口的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。
与集合不同,列表通常允许重复元素。 更正式地,列表通常允许成对元素e1和e2 ,使得e1.equals(e2) ,并且如果它们允许空元素,则它们通常允许多个空元素。
List 子接口的定义:
public interface List<E> extends Collection<E>
此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:
No. | 方法名称 | 描述 |
---|---|---|
1 | public void add(int index,E element) | 在指定位置处增加元素 |
2 | boolean addAll(int index,Collection<? extends E> c) | 在指定位置处增加一组元素 |
3 | public E get(int index) | 根据索引位置取出每一个元素 |
4 | public int indexOf(Object o) | 根据对象查找指定的位置,找不到返回-1 |
5 | public int lastIndexOf(Object o) | 从后面向前查找位置,找不到返回-1 |
6 | public ListIterator< E > listIterator() | 返回 ListIterator 接口的实例 |
7 | public ListIterator< E > listIterator(int index) | 返回从指定位置的 ListIterator 接口的实例 |
8 | public E remove(int index) | 删除指定位置的内容 |
9 | public E set(int index,E element) | 修改指定位置的内容 |
10 | List< E > subList(int fromIndex,int toIndex) | 返回子集合 |
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。所以,证明,List 接口拥有比 Collection 接口更多的操作方法。
了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个:
ArrayList、Vector、LinkedList
1、ArrayList
此类定义:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
范例:增加操作add(),删除操作remove() ,foreach(增强for,用于遍历)
构造器
构造器 | 描述 |
---|---|
ArrayList() | 构造一个初始容量为10的空列表。 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表。 |
ArrayList(Collection<?extends E> c) | 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。 |
注意:其实其初始容量为0,只有进行add()操作(add()方法必定返回true),容量扩容为10,所以API文档中描述ArrayList()无参构造器是构造一个初始容量为10的空列表(具体在newCapacity方法中)
add方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;//只要有添加操作,必定返回true,一定不会返回false
}
newCapacity():初始容量为10的原由(源码)
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容1.5倍, >>1 将oldCapacity的二进制数右移一位,即其数值的1/2倍,(类似十进制数100.0右移一位成10.00,其数值的1/10倍)
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);//private static final int DEFAULT_CAPACITY = 10;
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
示例:
注意:add(int index,E element)是添加,区别于一般的add(E e),这个就是有个位置的概念,特殊位置之后的数据,索引值依次加一。
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
//ArrayList : 使用的是数组结构,对于增删慢,查找快
//<>里面只能放引用数据类型,例如,int就报错
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(100);
//在指定位置增加的操作是 List 接口单独定义的。
arrayList.add(1,200);//在指定位置增加元素
//打印集合长度
System.out.println(arrayList.size());
//直接打印集合
System.out.println(arrayList);//[100, 200]
System.out.println("--------------");
arrayList.remove(1);//根据下标删除
for (Integer i:arrayList) {//foreach循环,类型 变量名:数组或集合名
System.out.println(i);
}
System.out.println("--------------");
arrayList.remove("100");//根据内容删除
arrayList.clear();//清空
}
}
以上的操作向集合中增加了两个元素,其中在指定索引位置增加的操作是 List 接口单独定义的。随后进行输出的时候,实际上调用的是 toString()方法完成输出的。
可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。
使用 remove()方法删除元素,可以根据索引删除也可以根据内容删除。
根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义(可进源码验证)。Map没有,Set算是阉割版的Map
for(元素数据类型 变量名:数组/集合对象名){循环体}
forEach加强for循环内部原理是一个Iterator迭代器,所以遍历时不能修改集合(可通过迭代器修改)
Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:
public class Vector< E > extends AbstractList< E >
implements List, RandomAccess, Cloneable, Serializable
此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。
import java.util.List;
import java.util.Vector;
public class VectorDemo {
public static void main(String[] args) {
List<String> list = new Vector<String>(); // 实例化List对象,并指定泛型类型
list.add("HELLO"); // 增加内容,此方法从Collection接口继承而来
list.add(0, "abc");// 增加内容,此方法是List接口单独定义的
list.add("world");
list.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
list.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < list.size(); x++) { // size()方法从Collection接口继承而来
System.out.print(list.get(x) + "、"); // 此方法是List接口单独定义的
}
}
}
以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。
但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。
从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。 如果不需要线程安全实现,建议使用ArrayList代替Vector 。
Vector 类和 ArrayList 类的区别(重点)
这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,具体如下:
No. | 区别点 | ArrayList | Vector |
---|---|---|---|
1 | 出现时间不一 | 是在 JDK 1.2 之后推出的 | 是旧的类是在 JDK 1.0 的时候就定义的 |
2 | 性能 | 性能较高,采用异步处理 | 性能较低,是采用了同步处理 |
3 | 输出 | 支持 Iterator、ListIterator 输出 | 除了支持 Iterator、ListIterator 输出,还支持Enumeration 输出 |
4 | 线程安全性 | 线程不安全 | 线程安全 |
5 | 扩容 | ArrayList以1.5倍的方式在扩容 | Vector 当扩容容量增量大于0时、新数组长度为原数组长度+扩容容量增量、否则新数组长度为原数组长度的2倍(详细看源码) |
6 | 空值 | 可重复,也允许多个null值 | 可重复,也允许多个null值 |
LinkedList
此类的定义如下:
public class LinkedList< E > extends AbstractSequentialList
implements List< E >, Deque< E >, Cloneable, Serializable
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
No. | 方法名称 | 描述 |
---|---|---|
1 | public boolean add(E e) | 增加元素,如果有容量限制,并且已满,则抛出异常 |
2 | public E element() | 取得,但是不删除当前元素,如果对列为空则抛出异常 |
3 | boolean offer(E e) | 添加,如果有容量限制,并且已满,只是无法添加,但不抛出异常,返回 false |
4 | E peek() | 取得头元素,但是不删除,如果队列为空,则返回null |
5 | E poll() | 取得头元素,但是删除, 如果队列为空,则返回 null |
6 | E remove() | 删除当前元素, 如果对列为空则抛出异常 |
例子:
package com.java.collection;
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
//LinkedList : 使用的是双向链表结构,对于增加删除快,查找慢
LinkedList<Integer> list = new LinkedList<>();
// list.addFirst(10);
// list.addFirst(20);//加在前面,最后加的在最前面
// Integer i = list.removeFirst();
// System.out.println(i);//200
//压栈
list.push(11);
list.push(12);
//弹栈
Integer pop = list.pop();
System.out.println(pop);//12
}
}
List集合一览表
No. | 特点 | arraylist | Linkedlist |
---|---|---|---|
1 | 底层实现 | 动态数组 | 链表 |
2 | 容量 | 无参构造器生成的容量为0的空列表(说10的也没毛病,如果你感到疑惑一定没仔细看上面的内容),容量不固定,随着容量的增加而动态扩容(阈值基本不会达到)。 | 没有初始化大小,也不需要,直接增加就行。 |
3 | 存储 | 在内存中开辟连续的内存空间。 | 不一定连续 |
4 | 有序 | 有序集合(插入的顺序==输出的顺序) | 有序集合(先进后出,输出顺序和增加顺序相反) |
5 | 可重复 | 可重复,也允许多个null值 | 可重复,也允许多个null值 |
6 | 效率 | 查找效率更高(相对于LinkedList来说) | 增删效率更高(相对于ArrayList来说) |
7 | 线程安全性 | 线程不安全 | 线程不安全 |
8 | 迭代 | 快速失败 | 快速失败 |
LinkedList底层通过链表来实现,随着元素的增加不断向链表的后端增加节点。
LinkedList是一个双向链表,每一个节点都拥有指向前后节点的引用。相比于ArrayList来说,LinkedList的随机访问效率更低。
Iterator迭代器
定义:public interface Iterator< E >
集合上的迭代器。 Iterator取代了Java Collections Framework中的Enumeration 。 迭代器在两个方面与枚举不同:
迭代器允许调用者在迭代期间使用明确定义的语义从底层集合中删除元素。
方法名称已得到改进。
此接口是Java Collections Framework的成员。
API Note:
一个Enumeration可以被转换成一个Iterator通过使用Enumeration.asIterator()方法。
从1.2版本开始:
变量和类型 | 方法 | 描述 |
---|---|---|
default void | forEachRemaining(Consumer<? super E> action) | 对每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。 |
boolean | hasNext() | 如果迭代具有更多元素,则返回 true 。 |
E | next() | 返回迭代中的下一个元素。 |
default void | remove() | 从底层集合中移除此迭代器返回的最后一个元素(可选操作)。 |
例子:
public static void main(String[] args) {
//Iterator
//ListIterator
ArrayList<Integer> data = new ArrayList<>();
data.add(9);
data.add(8);
data.add(7);
data.add(6);
Iterator<Integer> iterator = data.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
System.out.println(i);
}
//iterator.remove();
}
注意:
remove()从底层集合中移除此迭代器返回的最后一个元素(可选操作)。 每次调用next()时,只能调用此方法一次。(如下图,不能直接remove())
必须要先让指针指向下一个元素,否则会有异常。
先调用next()方法后,指针往下走,如下图
ListIterator迭代器
软件包 java.util
public interface ListIterator< E >extends Iterator< E >列表的迭代器,允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。
请注意, remove()和set(Object)方法根据光标位置定义; 它们被定义为对next()或previous()调用返回的最后一个元素进行操作。
该迭代器还可以从后往前遍历,hasPrevious() 如果此列表迭代器在反向遍历列表时具有更多元素,则返回 true 。 这个方法的意思为如果指针在最下面就能从后往前遍历,也就是得先从前往后遍历让指针到底才能反向迭代(可以结合上面画了指针的图理解,指针还是在最上面,只有一根针,同一根,这就是闲的蛋疼)。
forEach循环
//forEach : 增强For循环,最早出现在c#中
//用于迭代数组 或 集合(collection)
//语法:for(数据类型 变量名 : 集合或数组名){}
//内部使用迭代器
public static void main(String[] args) {
ArrayList<String> data = new ArrayList<>();
data.add("砖业手艺人");
data.add("春晓");
data.add("孟浩然");
data.add("春眠不觉晓");
data.add("处处闻啼鸟");
data.add("夜来风雨声");
data.add("花落知多少");
for (String str : data) {
System.out.println(str);
}
}
foreach循环是获取集合删除之前的数据,若是删除一个数据,则集合长度减一,会出现异常,所以foreach不能在遍历的时候删除集合。
而Iterator在每一次迭代时都会调用hasNext()方法判断是否有下一个,是允许集合中数据增加和减少的,所以使用iterator迭代删除没问题的。
所以使用forEach删除时,会报错ConcurrentModificationException异常,在使用forEach遍历时,是不允许集合元素进行删除和增加。