第三十一讲 集合框架——List接口

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/84039337

List接口概述

Collection接口有两个子接口:List(列表)、Set(集),本文我们先重点学习List(列表)接口。查阅API,查看List的介绍,我们可以发现以下这些话语:

有序的collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。与set不同,列表通常允许重复的元素。

看完API,我们总结一下:

  1. List接口是一个元素存取有序的集合。注意:有序指的是存入的顺序和取出的顺序一致。例如,存元素的顺序是11、22、33,那么集合中元素的存储就是按照11、22、33的顺序完成的;
  2. List接口是一个带有索引的集合。通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理);
  3. 集合中可以有重复的元素。通过元素的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集合中,并去除重复元素。比如:存人对象。同姓名同年龄,视为同一个人,为重复元素。

思路:

  1. 对Person类进行描述,将数据封装到人对象中;
  2. 定义容器对象,将多个Person对象存储到集合中;
  3. 去除同姓名同年龄的Person对象(重复元素);
  4. 取出集合中的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();
	}
	
}

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/84039337