@toc
1、List集合
Collection接口没有提供直接的实现类,而是提供了更加具体的子接口的实现类,其中一个最常用的子接口就是List接口。List集合中的元素是有序、可重复的。
List集合关心集合是否有序,而不关心元素是否重复。
1.1 List接口的方法
List除可以从Collection集合继承的方法,List集合中还添加了一些根据索引来操作集合的方法。之前我们说Collection接口中没有提供修改元素的方法,而List接口中提供了根据元素的下标索引位置来修改元素的方法set,下面列出了List接口新增的方法。
(1)添加元素
void add(int index,Object element)
:在[index]位置添加一个元素。boolean addAll(int index,Collection eles)
:在[index]位置添加多个元素。
(2)获取元素
Object get(int index)
:获取[index]位置的元素。List subList(int fromIndex,int toIndex)
:获取[fromIndex,toIndex)范围的元素。
(3)获取元素索引
int indexOf(Object obj)
:获取obj在List集合中首次出现的索引位置,如果不存在则返回-1.int lastIndexOf(Object obj)
:获取obj在List集合中最后出现的索引位置,如果不存在则返回-1.
(4)删除和替换元素
Object remove(int index)
:删除[index]位置的元素。Object set(int index,Object element)
:替换[index]位置的元素为element。
因为List接口是Collection接口的子接口,因此之前Collection接口的方法,List接口也同样适用,Collection集合的遍历方式也同样适用于List接口的集合。
1.2 案例:元素的增删改查
案例1:添加元素:
public class ListAddTest {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("张三");
list.add(0,"李四");//把“李四”添加到索引[0]位置
list.add(1,"王五");
for (Object o : list) {
System.out.println(o);
}
}
}
案例2:获取指定位置元素
public class ListGetTest {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
Object o = list.get(1);
System.out.println(o);
}
}
案例3:获取指定位置元素索引位置
public class ListGetTest {
public static void main(String[] args) {
listIndexOfTest();
}
public static void listIndexOfTest(){
ArrayList<Object> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("李四");
//从[0]开始查找,返回第一次出现的"李四“的索引
int index = list.indexOf("李四");
System.out.println("index="+index);
}
}
案例4:删除指定[0]位置的元素“张三”
import java.util.ArrayList;
public class ListRemoveTest {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.remove(0);//删除索引为[0]的元素。
list.forEach(System.out::println);
}
}
案例5:删除指定元素“2”
public class ListRemoveTest2 {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.remove(2);
list.forEach(System.out::println);
}
}
上面运行结果是1,2,4,而不是1,3,4,这是因为现在List接口中有两个remove方法,一个是remove(Object obj)
,另一个是remove(int index)
,而上面代码中list.remove(2)
匹配的是remove(int index)
,删除的是[2]位置的元素3.因为添加到集合中的1,2,3,4已经自动装箱为
Integer
的对象了,所以如果要删除元素2,那么可以通过list.remove(Integer.valueOf(2))
的方法实现或使用迭代器配合equals
判断是否是2再删除。
1.3 List接口的实现类
List接口的实现类都具备List接口有序且可以重复的特点,使用方式完全一样,仅仅是底层存储结构不同。这里列举了List接口中比较“著名”的几个实现类。
- ArrayList类:动态数组。
- LinkedList:双向链表,JDK1.6之后又实现了双端队列Deque接口。
- Vector类:动态数组。
- Stack类:堆栈/
1.3.1 ArrayList类
ArrayList类是使用最频繁的List集合类之一,它其实就是我们之前反复提到的动态数组的实现,因此它底层的物理结构是数组。
之前使用的数组是静态分配空间,一旦分配了空间大小,就不可再改变;而动态数组是动态分配空间,随着元素的不断插入,它会按照自身的一套机制不断扩充自身的容量。==动态数组扩容并不是在原有连续的内存空间后进行简单的叠加,而是重新申请一块更大的新内存,并把现有容器中的元素逐个赋值过去,然后销毁旧的内存。==
在构建ArrayList集合对象时,如果没有显示指定容量,那在JDK1.6及其之前版本的内部数组初始化容量默认为10,之后的版本初始化容量为长度为0的空数组,在添加第一个元素时再创建一个长度为10的数组。ArrayList延迟创建长度为10的数组的目的是节省内存空间,因为有时我们在创建ArrayList集合对象后,并没有添加元素,这点在方法的返回值类型是List类型时,极有可能存在。当然你也可以在创建ArrayList集合对象时,自己指定初始化容量。
ArrayList类在添加一个新元素时,如果现有的数组容量不够,则会将新数组长度扩容为原来的1.5倍之后再添加。如果调用addAll
方法一次添加多个元素,则会先判断原有数组是否够装,如果不够,则判断1.5倍容量是否够装,如果不够,就按实际需要来扩容数组。
1.3.2 LinkedList类
LinkedList类是典型的双向链表的实现类,除可以实现List接口的方法,还为在列表的开头及结尾get(获取)、remove(移除)和insert(插入)元素提供了统一的命名方法。这些操作允许将链表用作堆栈、队列或双端队列。
将LinkedList类作为普通列表形式使用的示例代码。
public class LinkedListTest1 {
public static void main(String[] args) {
LinkedList<Object> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
for (Object o : list) {
System.out.println(o);
}
}
}
JDK1.6之后LinkedList类实现了Deque接口。双端队列也可用作LIFO(后进先出)堆栈。如果要使用堆栈的集合,那么可以考虑使用LinkedList类,而不是Deque接口,如下表所示。
堆栈方法 | 等效Deque方法 |
---|---|
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
将LinkedList类作为堆栈使用的示例代码:
public class LinkedListTest2 {
public static void main(String[] args) {
LinkedList<Object> list = new LinkedList<>();
//入栈
list.addFirst(1);
list.addFirst(2);
list.addFirst(3);
//出栈:LIFO(后进先出)
System.out.println(list.removeFirst());
System.out.println(list.removeFirst());
System.out.println(list.removeFirst());
//栈空了,会报异常java.util.NoSuchElementException
System.out.println(list.removeFirst());
}
}
LinkedList类用作队列时,将得到FIFO(先进先出)行为,将元素添加到双端队列的末尾,从双端队列的开头移除元素,LinkedList类作为队列使用的方法如下表所示。
Queue方法 | 等效Deque方法 |
---|---|
add(e) | addLast(e) |
offer(e) | offerLast(e) |
remove() | removeFirst() |
poll() | pollFirst() |
element() | getFirst() |
peek() | peekFirst() |
将LinkedList类作为队列使用的示例代码:
import java.util.LinkedList;
public class LinkListTest3 {
public static void main(String[] args) {
LinkedList<Object> list = new LinkedList<>();
//入队
list.add(1);
list.add(2);
list.add(3);
//出队,FIFO(先进先出)
System.out.println(list.pollFirst());
System.out.println(list.pollFirst());
System.out.println(list.pollFirst());
//队列空了,返回Null
System.out.println(list.pollFirst());
}
}
每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式则会返回一个特殊值,null或false,具体形式取决于操作,LinkedList类作为双向链表使用的方法如下所示。
第一个元素(头部) | 最后一个元素(尾部) | |||
抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
检查 | getFirst() | peekFirst() | getLast() | peekLast() |
### 1.3.3 Vector类
Vector类是STL(标准模板库)中最常见的容器,也是动态数组数据结构的实现。关于Vector类和ArrayList类两种动态数组的对比,如下表所示。
类 | 底层结构 | 初始化容量 | 扩容机制 | 线程安全(同步) | 版本 | 效率 |
---|---|---|---|---|---|---|
Vector类 | 动态数组 | 如果没有显示指定容量,则创建对象时,初始化容量为0 | 2倍 | 安全(同步) | 较老 | 较低 |
ArrayList类 | 动态数组 | 如果没有显示指定容量,则在JDK6版本创建对象时,初始化容量为10,在更高版本创建对象时,初始化容量为0,第一次添加元素时,初始化容量为10. | 1.5倍 | 不安全(不同步) | 较新 | 较高 |
1.3.4 Stack类
Stack类是Vector的子类,用于表示后进后出(LIFO)的对象堆栈,通过5个操作对Vector类进行了扩展,下表列出了Stack类具有堆栈特点的操作。
方法 | 功能解释 |
---|---|
push(Object e) | 将对象插入Stack类的顶部 |
Object peek() | 返回位于Stack类顶部的对象但不将其移除 |
Object pop() | 移除并返回位于Stack类顶部的对象 |
boolean empty() | 堆栈是否为空 |
int search(Object o) | 对象到堆栈顶部的位置,以1为基数;返回值-1则表示此对象不在堆栈中 |
import java.util.EmptyStackException;
import java.util.Stack;
public class StackTest {
//添加新元素到栈,即把新元素压入栈,成为新的栈顶元素
static void showPush(Stack st,Object value){
st.push(value);
System.out.println("push("+value+")");
System.out.println("现在栈顶元素是:"+st.peek());//查看当前栈顶元素
System.out.println("现在栈中的元素有:"+st);
}
//弹出当前栈顶元素,下一个元素称为新的栈顶元素
static void showPop(Stack st){
System.out.println("pop->"+st.pop());
System.out.println("现在栈中元素有:"+st);
}
public static void main(String[] args) {
Stack<Object> st = new Stack<>();
showPush(st,42);
showPush(st,66);
showPush(st,99);
showPop(st);
showPop(st);
showPop(st);
try {
showPop(st);
} catch (EmptyStackException e) {
// e.printStackTrace();
System.out.println("empty stack");
}
}
}
1.4 List集合的遍历
因为List集合也属于Collection系列的集合,此前Collection集合支持的foreach遍历和Iterator遍历对于List集合来说仍然适用,这里就不再重复,下面介绍List集合的其他遍历方式。
1.4.1 普通for遍历循环遍历(效率不高)
public class ListForTest {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1,"张三"));
list.add(new Student(2,"李四"));
list.add(new Student(3,"王五"));
list.add(new Student(4,"赵六"));
list.add(new Student(5,"钱七"));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
1.4.2 ListIterator迭代器
List集合额外提供了一个listIterator()
方法,该方法返回ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法,如下所示。
void add()
:通过迭代器添加元素到对应集合。void set(Object obj)
:通过迭代器替换正在迭代的元素。void remove()
:通过迭代器删除刚才迭代的元素。boolean hasPrevious()
:如果逆向遍历列表,则判断往前是否还有元素。Object previous()
:返回列表中的前一个元素。int previousIndex()
:返回列表中的前一个元素的索引。boolean hasNext()
:判断有没有下一个元素。Object next()
:返回列表中的最后一个元素。int nextIndex()
:返回列表中后一个元素的索引。
public class ListIteratorTest {
public static void main(String[] args) {
ArrayList<Object> c = new ArrayList<>();
c.add(new Student(1,"张三"));
c.add(new Student(2,"李四"));
c.add(new Student(3,"王五"));
c.add(new Student(4,"赵六"));
c.add(new Student(5,"钱七"));
//从指定位置往前遍历
ListIterator<Object> listIterator = c.listIterator(c.size());
while (listIterator.hasPrevious()){
Object previous = listIterator.previous();
System.out.println(previous);
}
}
}
1.4.3 foreach循环遍历
import java.util.ArrayList;
public class ListForTest {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1,"张三"));
list.add(new Student(2,"李四"));
list.add(new Student(3,"王五"));
list.add(new Student(4,"赵六"));
list.add(new Student(5,"钱七"));
for (Student student : list) {
System.out.println(student);
}
}
}
1.4.4 Iterator迭代器遍历
public class ListForTest {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1,"张三"));
list.add(new Student(2,"李四"));
list.add(new Student(3,"王五"));
list.add(new Student(4,"赵六"));
list.add(new Student(5,"钱七"));
Iterator<Student> iterator = list.iterator();
while(iterator.hasNext()){
Student next = iterator.next();
System.out.println(next);
}
}
}