List接口概述
Collection接口有两个子接口:List(列表)、Set(集),本文我们先重点学习List(列表)接口。查阅API,查看List的介绍,我们可以发现以下这些话语:
有序的collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。与set不同,列表通常允许重复的元素。
看完API,我们总结一下:
- List接口是一个元素存取有序的集合。注意:有序指的是存入的顺序和取出的顺序一致。例如,存元素的顺序是11、22、33,那么集合中元素的存储就是按照11、22、33的顺序完成的;
- List接口是一个带有索引的集合。通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理);
- 集合中可以有重复的元素。通过元素的equals方法,来比较是否为重复的元素。
List接口中的常见方法
我们已学完Collection接口的大部分方法,List接口中同样有这些方法,所以就不必重复了。现在我们将重点放在List接口中的特有方法上,它的特有方法都是围绕索引定义的。List接口支持增删改查,如下:
现在我们演示List接口中的特有方法。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List list = new ArrayList();
methodDemo(list);
}
/*
* 演示List特有的方法。
*/
public static void methodDemo(List list) {
//1,常规添加元素
list.add("abc1");
list.add("abc2");
list.add("abc3");
//2,插入元素
// list.add(1, "hehe");
//3,删除
// list.remove(1);
// list.remove(1);
//4,获取
// System.out.println(list.get(1));
// System.out.println(list.indexOf("abc3"));
//5,修改
// list.set(1, "kk");
//取出集合中所有的元素
for (Iterator it = list.iterator(); it.hasNext();) {
System.out.println("iterator: " + it.next());
}
//List集合特有的取出方式
for (int i = 0; i < list.size(); i++) {
System.out.println("get: " + list.get(i));
}
// System.out.println(list);
}
}
List接口特有的迭代器——ListIterator
现在我们来思考这样一个问题:在迭代过程中,准备添加或者删除元素。例如,在List集合迭代元素中,对元素进行判断,一旦条件满足就添加一个新元素。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.List;
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
//希望在遍历的过程中,如何遍历到"abc2",添加一个元素"haha"
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
list.add("haha");//java.util.ConcurrentModificationException,
//迭代过程中,使用了集合对象同时对元素进行操作,导致了迭代的不确定性。引发了该异常。
//解决思想:在迭代过程中,想要执行一些操作,使用迭代器的方法就可以了。
}
System.out.println(obj);
}
}
}
运行上述代码发生了异常——java.util.ConcurrentModificationException[并发修改异常],这是什么原因呢?这是因为在使用迭代器或者增强for循环遍历集合的时候,再调用集合的方法修改集合的长度(添加和删除),就会发生并发修改异常。也可以这样说,在迭代过程中,使用了集合的方法对元素进行操作,会导致迭代器并不知道集合中的变化,容易引发数据的不确定性。注意:并发修改异常是由next()方法抛出的。
并发修改异常的源码分析
虽然我们知道其原因了,但是要想知道其更深层次的原因,还得看源码,下面跟着我一起去看看源码吧!
ArrayList集合中有一个成员变量modCount(该成员变量在其父接口AbstractList中),它用来记录集合长度修改的次数,每次修改集合的长度,这个变量就会增长。
迭代器有一个成员变量expectedModCount,它是预期被修改的值,它的初始值是被修改的值。
迭代器的next()方法源码截图:
点击进入checkForComodification()该方法里面去,查看其源码。
该方法比较预期修改的次数和修改的次数是不是相等,如果不相等,就抛出并发修改异常。所以,在迭代时,不可以通过集合对象的方法操作集合中的元素,因为会发生并发修改异常(ConcurrentModificationException)。
ListIterator接口的listIterator()方法
综上,在迭代时,只能用迭代器的方法操作元素,可是Iterator的方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他的操作如添加,修改等,就需要使用其子接口ListIterator(List集合特有的迭代器),该接口只能通过List集合的listIterator方法获取。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
/*
//希望在遍历的过程中,如何遍历到"abc2",添加一个元素"haha"
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
list.add("haha");//java.util.ConcurrentModificationException,
//迭代过程中,使用了集合对象同时对元素进行操作,导致了迭代的不确定性。引发了该异常。
//解决思想:在迭代过程中,想要执行一些操作,使用迭代器的方法就可以了。
}
System.out.println(obj);
}
*/
//使用List集合的特有迭代器ListIterator来实现,通过List集合的方法listIterator()方法获取该迭代器对象。
//ListIterator接口可以实现在迭代过程中的增删改查。
for (ListIterator it = list.listIterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
it.add("haha");
}
}
System.out.println(list);
}
}
List集合存储数据的结构
List接口下有很多个集合,它们存储元素所采用的结构方式是不同的,这样就导致了这些集合有它们各自的特点,供给我们在不同的环境下进行使用。数据存储的常用结构有:堆栈、队列、数组、链表。我们分别来了解一下:
堆栈
采用该结构的集合,对元素的存取有如下的特点:
队列
采用该结构的集合,对元素的存取有如下的特点:
数组
采用该结构的集合,对元素的存取有如下的特点:
链表
采用该结构的集合,对元素的存取有如下的特点:
List接口的具体子类
List集合的具体子类还蛮有几个,子类之所以区分,就是因为其内部的数据结构(存储数据的方式)不同。
ArrayList
ArrayList底层使用的是数组结构,所以它可以存储重复的元素。
ArrayList去除重复元素(集合中元素是字符串)方式一
例,定义功能,请去除ArrayList集合中的重复元素(集合中元素是字符串)。
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListTest2 {
public static void main(String[] args) {
/*
* 练习1,定义功能,请去除ArrayList集合中的重复元素。
*/
List list = new ArrayList();
list.add("abc1");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc2");
System.out.println(list);
singleElement(list);
System.out.println(list);
}
/**
* 定义功能。去除重复元素
*/
public static void singleElement(List list) {
//选择排序
for (int x = 0; x < list.size() - 1; x++) {
Object obj_x = list.get(x);
for (int y = x + 1; y < list.size(); y++) {
if (obj_x.equals(list.get(y))) {
list.remove(y);
y--;
}
}
}
}
}
这种解决方式其实有点类似于选择排序,不是吗?下面我们来看去除重复元素的第二种解决方式。
ArrayList去除重复元素(集合中元素是字符串)方式二
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListTest2 {
public static void main(String[] args) {
/*
* 练习1,定义功能,请去除ArrayList集合中的重复元素。
*/
List list = new ArrayList();
list.add("abc1");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc2");
System.out.println(list);
singleElement2(list);
System.out.println(list);
}
/**
* 去除重复元素方式二。
* 思路:
* 1,最后唯一性的元素,可以先定义一个容器用于存储这些唯一性的元素。
* 2,对原有的容器进行元素的获取,并到临时容器中去判断是否存在,容器本身就有这个功能,判断元素是否存在
* 3,存在就不存储,不存在就存储。
* 4,遍历完原容器后,临时容器中存储的就是唯一性的元素了。
*/
public static void singleElement2(List list) {
//1,定义一个临时容器
List temp = new ArrayList();
//2,遍历原容器
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = (Object) it.next();
//3,在临时容器中判断遍历到的元素是否存在
if (!temp.contains(obj)) {
//4,如果不存在,就存储到临时容器中
temp.add(obj);
}
}
//5,将原容器清空
list.clear();
//6,将临时容器中的元素都存储到原容器当中
list.addAll(temp);
}
}
ArrayList去除重复的自定义元素
例,将自定义对象作为元素存到ArrayList集合中,并去除重复元素。比如:存人对象。同姓名同年龄,视为同一个人,为重复元素。
思路:
- 对Person类进行描述,将数据封装到人对象中;
- 定义容器对象,将多个Person对象存储到集合中;
- 去除同姓名同年龄的Person对象(重复元素);
- 取出集合中的Person对象。
package cn.liayun.domain;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 建立Person类自己的判断对象是否相同的依据,必须要覆盖Object类中的equals方法。
*/
public boolean equals(Object obj) {
//为了提高效率,如果比较的对象是同一个,直接返回true
if (this == obj) {
return true;
}
// System.out.println(this + "......" + obj);
if (!(obj instanceof Person)) {
throw new ClassCastException("类型错误");
}
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
注意:在Person类中必须覆盖Object类中的equals方法,不久我们就会看到集合的contains()方法底层调用的就是equals()方法。
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import cn.liayun.domain.Person;
public class ArrayListTest {
public static void main(String[] args) {
/*
* 练习:1,往ArrayList集合中存储自定义对象。Person(name, age)
* 思路:
* 1,描述Person
* 2,定义容器对象
* 3,将多个Person对象存储到集合当中
* 4,取出Person对象
*/
//1,创建ArrayList集合对象
List list = new ArrayList();
//2,添加Person类型的对象
Person p1 = new Person("lisi1", 21);
Person p2 = new Person("lisi2", 22);
list.add(p1);
list.add(p1);//存储了一个地址相同的对象
list.add(p2);
list.add(new Person("lisi3", 23));
list.add(new Person("lisi1", 21));
list.add(new Person("lisi2", 22));
/*
//3,取出元素
for (Iterator it = list.iterator(); it.hasNext();) {
//it.next():取出的元素都是Object类型的。需要用到具体对象的内容时,需要向下转型。
Person p = (Person) it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
*/
System.out.println(list);
singleElement2(list);//去除重复元素
System.out.println(list);
}
/**
* 去除重复元素方式二。
* 思路:
* 1,最后唯一性的元素,可以先定义一个容器用于存储这些唯一性的元素。
* 2,对原有的容器进行元素的获取,并到临时容器中去判断是否存在,容器本身就有这个功能,判断元素是否存在
* 3,存在就不存储,不存在就存储。
* 4,遍历完原容器后,临时容器中存储的就是唯一性的元素了。
*/
public static void singleElement2(List list) {
//1,定义一个临时容器
List temp = new ArrayList();
//2,遍历原容器
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = (Object) it.next();
//3,在临时容器中判断遍历到的元素是否存在
if (!temp.contains(obj)) {
//4,如果不存在,就存储到临时容器中
temp.add(obj);
}
}
//5,将原容器清空
list.clear();
//6,将临时容器中的元素都存储到原容器当中
list.addAll(temp);
}
}
通过以上示例,说明contains()底层用的是equals()。
结论
List集合判断元素是否相同,依据的是元素的equals方法。
LinkedList
底层数据结构是链表,查询慢,增删快,线程不安全,效率高。
LinkedList中的特有方法
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法,如下:
方法声明 | 功能描述 |
---|---|
public void addFirst(E e) | 将指定元素插入此列表的开头 |
public void addLast(E e) | 将指定元素插入此列表的结尾 |
public E getFirst() | 获取集合的第一个元素 |
public E getLast() | 获取集合的最后一个元素 |
public E removeFirst() | 删除集合的第一个元素 |
public E removeLast() | 删除集合的最后一个元素 |
package cn.liayun.list.linkedlist;
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
//1,创建一个链表对象,演示xxxFirst、xxxLast方法
LinkedList link = new LinkedList();
//2,添加方法
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
//3,获取元素
// System.out.println(link.getFirst());
// System.out.println(link.getFirst());
//4,删除元素
// System.out.println(link.removeFirst());
// System.out.println(link.removeFirst());
//取出link中所有元素
while (!link.isEmpty()) {
System.out.println(link.removeLast());
}
}
}
面试题:LinkedList实现一个堆栈或者队列数据结构
package cn.liayun.list.linkedlist;
import java.util.LinkedList;
public class LinkedListTest {
public static void main(String[] args) {
/*
* 练习:请通过LinkedList实现一个堆栈或者队列数据结构。
* 堆栈:先进后出,First In Last Out(FILO)
* 队列:先进先出,Fisrt In First Out(FIFO)
*/
//1,创建自定义的队列对象
MyQueue queue = new MyQueue();
//2,添加元素
queue.myAdd("abc1");
queue.myAdd("abc3");
queue.myAdd("abc4");
queue.myAdd("abc5");
//3,获取所有元素
while (!queue.isNull()) {
System.out.println(queue.myGet());
}
}
}
/*
* 描述一个队列数据结构。内部使用的是LinkedList
*/
class MyQueue {
private LinkedList link;
MyQueue() {
link = new LinkedList();
}
/**
* 添加元素的方法
*/
public void myAdd(Object obj) {
//内部使用的是LinkedList的方法
link.addFirst(obj);
}
/**
* 获取队列元素的方法
*/
public Object myGet() {
return link.removeLast();
}
/**
* 集合中是否有元素的方法
*/
public boolean isNull() {
return link.isEmpty();
}
}