List-有序集合
List本是豪门少爷。
在新婚前夜却发现未婚妻和兄弟Collection在喜床上翻滚。
她深夜买醉却撞上醉酒的List。
一夜痴缠List醒来后不见她的踪影。
只见床头压着一张纸:
今天是肯德基疯狂星期四吮指原味鸡+黄金脆皮鸡才九块九,我是真的不想错过!
目录
List集合概述和特点
List集合特有方法
并发修改异常
ListIterator
增强for循环
List集合子类特点
LinkedList集合的特有功能
List
List集合概述和特点
先到帮助文档中查看:
它在java.util包下,使用它需要导包。<E>是泛型,是List集合中的类型。往下看List是一个接口它继承自Collection接口,所以Collection接口中有的功能List都可以使用。
List被称为有序集合也被称为序列。
用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素(这句话带表List是有索引的)。
与Set集合不同,列表通常允许重复元素。
看完之后我们总结一下:
- 有序集合(也被称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素。
- 与Set集合不同,列表通常允许重复的元素。
List集合特点:
- 有序:存储和取出的元素顺序一致。
- 可重复:存储的元素可以重复。
程序演示特点:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
List集合特点:
有序:存储和取出的元素顺序一致。
可重复:存储的元素可以重复。
*/
public class ListDemo01 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("java");
list.add("hello");
//输出集合对象
// System.out.println(list);//[hello, java]
//遍历List结合,采用迭代器的方式遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
System.out.println(next);//hello java hello
}
}
}
List集合特有方法
List方法Collection是没有的,但是它的子类ArrayList是有的。这几个方法在学习集合基础的时候已经使用过了,再来看一遍。
程序演示:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
List集合特有方法:
void add(int index, E element) 将指定元素插入此集合中的指定位置.
E remove(int index) 删除此集合中指定位置的元素.
E get(int index) 返回此集合中指定位置的元素.
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素.
*/
public class ListDemo02 {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("java");
list.add("hello");
//void add(int index, E element) 将指定元素插入此集合中的指定位置.
list.add(2,"is");
list.add(3,"my");
list.add(4,"favourite");
System.out.println(list);//[hello, java, is, my, favourite, hello]
// list.add(11, "List");//IndexOutOfBoundsException(索引越界异常)
//上面这一句代码,因为集合中没有这么多个元素所以,使用这个索引是有问题的.try一下或者注释掉
System.out.println("---------------");
//E remove(int index) 删除此集合中指定位置的元素.
System.out.println(list.remove(5));//返回hello,表示索引为5的hello元素被删除掉了
System.out.println(list);
// System.out.println(list.remove(13));//IndexOutOfBoundsException索引越界
//上面一条输出语句报错,注释掉
System.out.println("---------------");
//E get(int index) 返回此集合中指定位置的元素.
System.out.println(list.get(3));//my
// System.out.println(list.get(11));//报错IndexOutOfBoundsException
//List是有索引的,注意带索引操作的时候索引不要越界
System.out.println("---------------");
//E set(int index,E element) 修改指定索引处的元素,返回被修改的元素.
System.out.println(list.set(1,"javaweb"));//java被修改为javaweb
//验证
System.out.println(list);//[hello, javaweb, is, my, favourite]
System.out.println("---------------");
//遍历集合
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
System.out.println("---------------");
//不使用迭代器遍历List集合,此处size会返回最大索长度.
for (int x = 0; x < list.size();x++){
String s = list.get(x);
System.out.println(s);
}//这两种方式都是可以的.
}
}
并发修改异常
先到程序中演示:
到帮助文档中查看这个异常类的详细信息:
这个异常继承自RunTimeException所以这个异常是一个运行时异常。
接着来看详细解释:
当不允许这样的修改时,检测到对象的并发修改的方法可能抛出此异常(这个就是我们说的并发修改异常)。
继续分析异常产生的原因:
问题出现在第二十五行代码,然后就因该是在这一行的next方法这里出现的问题,接着往上看,at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)这是java.util包下ArrayList这个集合里面的,Itr是一个内部类,它里面的next方法出现了问题,二next方法在里面又调用了这样一个方法:checkForComodification,最终就是在checkForComodification这里出现的问题,我们想把问题清楚的看到我们就得去看一下源码:
首先跟进List方法:
public interface List<E> extends Collection<E> {
Iterator<E> iterator();
boolean add(E e);
}
public abstract class AbstractList<E>{
protected int modCount = 0;//这个变量是protected修饰的,所以它的两个子类都可以访问到
}
public class ArrayList<E> extends AbstractList<E>implements List<E>{
/*10.get方法并没有做实际修改集合和预期修改集合的次数判断,他只是做了一个获取这个元素.
虽然add方法会对实际修改集合的次数做++但是get方法不会做这个判断所以不会出现并发修改异常
*/
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
public boolean add(E e) {//6.在add方法里面做了一个操作
modCount++;//7.对实际修改集合的次数做了一个++,但是预期修改集合的次数没有做++
add(e, elementData, size);
return true;
}
//实现接口就得实现接口中的方法
public Iterator<E> iterator() {
return new Itr();
}
//然后new了一个Itr,吧Itr也拿过来,(吧用不上的删掉了)
private class Itr implements Iterator<E> {
int expectedModCount = modCount;//1.一开始这两个值都是0
/*
modCount:实际修改集合的次数
expectedModCount:预期修改集合的次数
*/
@SuppressWarnings("unchecked")
public E next() {//2.这里list调用list方法
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {//3.每次调用next方法的时候
/*
8.然后到下一次调用checkForComodification()方法的时候,然后进入if判断,判定
这两发变量的值是否一样,那肯定是不一样的,不一样的话就会抛出并发修改异常.
*/
if (modCount != expectedModCount)//4.都会判定这了两个变量的值是否一致
throw new ConcurrentModificationException();
/*
5.
从正常来看这两个变量的值都是一致的,但是我们做了一个操作,一旦字符串s的值和world
相等,就会通过list调用add方法.
*/
}
}
}
/*
9.
所以说并发修改异常的原因就是通过迭代器遍历的过程中,我通过集合去添加了元素,那么就造成了
我这个迭代器在获取元素的时候,里面在进行判断预期修改集合的次数和实际修改集合的次数不一致的清空
这样就造成了并发修改异常.
*/
然后对代码进行修改:
/*
需求:
我有一个集合:List<String> list = new ArrayList<String>();
里面有三个元素:list.add("hello"); list.add("world"); list.add("java");
遍历集合,得到每一个元素,看看有没有"world"这个元素,如果由,我就添加一个"javaee"元素,请写代码实现
ConcurrentModificationException:
当不允许这样的修改时,检测到对象的并发修改的方法可能抛出此异常(这个就是我们说的并发修改异常)。
*/
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
//1.遍历集合
Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
// String next = iterator.next();
// if (next.equals("world")) {
// list.add("javaee");
// }
// }
//3.看完源码后解决问题(不使用迭代器),使用for循环遍历
for (int x =0;x < list.size();x++){
if (list.get(x).equals("world")){
list.add("javaee");
}
}
System.out.println(list);
/*
使用next方法他会使用判断,我们使用get方法不会使用判断吗?
跟进一下get方法
*/
//2.输出集合
System.out.println(list);//执行就报错:ConcurrentModificationException(并发修改异常)
}
}
并发修改异常:
- ConcurrentModificationException
产生原因:
- 迭代器遍历过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改次数和实际修改次数的值不一致。
解决方案:
使用for循环遍历,然后用集合对象做对应的操作即可。
ListIterator
先到帮助文档中查看:
它在java.util包下,所以使用它是需要导包的。它是一个接口,继承自Iterator接口。所以ListIterator可以直接使用hasnext方法和next方法的。因为Iterator里面有这两个方法的定义。
前面用Iterator它只能从前面往后面遍历,ListIterator是可以从任意方向遍历列表的迭代器(可以从后往前遍历),这就是ListIterator和Iterator的区别。ListIterator还可以在迭代期间修改列表(也就是增删元素);并获取列表中迭代器的当前位置。
再往下看:
ListIterator:列表迭代器
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器。
- 允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。
ListIterator中的常用方法:
- E next():返回迭代中的下一个元素。
- boolean hasNext():如果迭代具有更多元素,则返回true。
- E previous():返回列表中的上一个元素。
- boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true。
- void add(E e):将指定的元素插入列表
程序演示:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/*
ListIterator:列表迭代器
通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器。
允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。
ListIterator中的常用方法:
E next():返回迭代中的下一个元素。
boolean hasNext():如果迭代具有更多元素,则返回true。
E previous():返回列表中的上一个元素。
boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true。
void add(E e):将指定的元素插入列表
*/
public class ListIteratorDemo {
public static void main(String[] args) {
//1.创建集合对象
List<String> list = new ArrayList<String>();
//2.添加元素
list.add("hello");
list.add("world");
list.add("java");
/*5.1注释掉3和4的代码
//3.通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器。
ListIterator<String> stringListIterator = list.listIterator();
while(stringListIterator.hasNext()){
String next = stringListIterator.next();
System.out.println(next);//hello world java
}
System.out.println("--------------");
//4.反向遍历
while (stringListIterator.hasPrevious()){
String previous = stringListIterator.previous();
System.out.println(previous);// java world hello
}
*/
//5.注释掉3和4的代码,我们来重点演示add
//6.获取列表迭代器
ListIterator<String> stringListIterator = list.listIterator();
//7.遍历集合,如果集合中存在元素world则往结合中添加 "javaee" 元素
while(stringListIterator.hasNext()){
String next = stringListIterator.next();
if (next.equals("world")){
stringListIterator.add("javaee");//这一次不是通过集合添加,而是通过列表迭代器调add方法来添加的
}
}
/*
8.研究完底层代码只之后了解了
我们通过列表迭代器也可以实现往集合中添加元素,用的是列表迭代器的add方法,他不会产生并发修改异常,因为它的底层
最终会把实际修改次数的值赋值给预期修改次数变量.
*/
System.out.println(list);//[hello, world, javaee, java]
}
}
底层源码分析:
public interface List<E> extends Collection<E> {
Iterator<E> iterator();
//1.这里应该会有这个方法
ListIterator<E> listIterator();
}
public abstract class AbstractList<E>{
protected int modCount = 0;//这个变量是protected修饰的,所以它的两个子类都可以访问到
}
public class ArrayList<E> extends AbstractList<E>implements List<E>{
//2.同样在ArrayList里也应该有同样的方法
public ListIterator<E> listIterator() {
return new ListItr(0);//3.这里new了一个ListItr我们就要去找到它
/*
6.LIstIterator方法返回的是ListIterator,
new的是ListItr,然后来看ListItr类
*/
}
private class Itr implements Iterator<E> {
//5.这里Itr的实现被我省略掉了
}
//4.Iterator返回的是Iterator,new的是Itr.
public Iterator<E> iterator() {
return new Itr();
}
}
//7.继承自Itr实现了ListIterator接口
/*
8.所以说在我们代码中ListIterator<String> stringListIterator = list.listIterator();
得到的其实是ListIterator实现类对象,也就是我们这个ListItr的对象,因为ListItr实现了ListIterator接口
*/
private class ListItr extends Itr implements ListIterator<E> {
//9.其他方法删掉,就看一个方法就好了
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;//10.主要看着一句,会把实际修改次数,赋值给预期修改次数
//11.这样我们在调用next方法做判断的时候,这两个变量的值就是一致的,所以它就不会出现并发修改异常
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
增强for循环
我们先到帮助文档中再来查看一下Collection:
它继承自Iterable接口,接着来看Iterable接口:
所以Collection体系的集合都可以成为增强for语句的目标。
在Iterable里面有一个iterator方法,所以说增强for语句其底层就是包装一个迭代器。
概述:
增强for循环:简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象称为增强型for语句的目标
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器。
增强for的格式:
- 格式:
for(元素数据类型 变量名:数组或者Collection集合){
//在此处使用变量即可,该变量就是元素
}
格式:
int[] arr = {1,2,3,4,5};
for(int i : arr){
System.out.println(i);
}
程序演示
import java.util.ArrayList;
import java.util.List;
/*
增强for循环:简化数组和Collection集合的遍历
实现Iterable接口的类允许其对象称为增强型for语句的目标
它是JDK5之后出现的,其内部原理是一个Iterator迭代器。
增强for的格式:
for(元素数据类型 变量名:数组或者Collection集合){
//在此处使用变量即可,该变量就是元素
}
*/
public class ForDemo {
public static void main(String[] args) {
//定义一个int类型数组
int[] arr = {1,2,3,4,5};
for (int i : arr){
System.out.println(i);//1 2 3 4 5
}
System.out.println("-----------------");
String[] strArray = {"hello","world","java"};
for (String str : strArray){
System.out.println(str);//hello world java
}
System.out.println("-----------------");
//定义集合
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for (String str1 : list){
System.out.println(str1);
}
System.out.println("-----------------");
/*验证for增强循环内部是一个Iterator迭代器
如果for增强循环是一个迭代器,那么就相当于迭代器
遍历集合,然后判断里面有没有元素为world,如果有
通过集合调用add方法,往集合内添加一个元素,这个时候
就会抛出一个并发修改异常.如果这里抛出了并发修改异常,
那就说明它的内部就是一个迭代器.
*/
for (String s : list){
if (s.equals("world")){
list.add("javaee");
}
System.out.println(s);//ConcurrentModificationException
}
}
}
List集合子类特点
List集合常用子类:ArrayList,LinkedList。
先到帮助文档中查看ArrayList:
ArrayList实现了List接口,看下面描述,List接口可调整大小的数组实现,也就是说ArrayList集合底层数据结构是数组,数组的特点是查询快增删慢。
然后再来查看LinkedList:
LinkedList实现了List接口
它是链表实现了List接口,由此可见LinkedList的底层结构是链表,链表的结构是查询慢,增删快。
- ArrayList:底层数据结构是数组,查询快,增删慢。
- LinkedList:底层数据结构是链表,增删快,查询慢。
关于ArrayList它们的一些功能,不再学习,因为List有的它们都有;它们的用法和List是一模一样的,所以我们就不分别去学习它们的基本使用,但是我们以后在使用的时候要根据做查询还是做增删来选择集合。
我们来做一个ArrayList集合的案例:
需求:创建一个存储学生对象的集合,存储三个学生对象,实用程序实现在控制台遍历该集合。
思路:
- 定义学生类
- 创建集合
- 创建学生对象
- 将学生对象添加进集合
- 使用三种方式遍历集合
程序演示:
import java.util.ArrayList;
import java.util.Iterator;
public class Student {
String name;
int age;
public Student() {
}
public Student(String name, int age) {
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;
}
}
/*
需求:创建一个存储学生对象的集合,存储三个学生对象,实用程序实现在控制台遍历该集合。
思路:
① 定义学生类
② 创建集合
③ 创建学生对象
④ 将学生对象添加进集合
⑤ 使用三种方式遍历集合
*/
class ArrayListDemo {
public static void main(String[] args) {
//创建ArrayList集合
ArrayList<Student> arrayList = new ArrayList<>();
//创建学生对象
Student s1 = new Student("林青霞", 26);
Student s2 = new Student("张曼玉", 24);
Student s3 = new Student("王祖贤", 31);
//讲学生对象添加到集合中
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
//增强for循环
for (Student student : arrayList) {
System.out.println(student.getName() + "." + student.getAge());
}
System.out.println("----------");
//普通for循环
for (int x = 0; x < arrayList.size(); x++) {
Student student = arrayList.get(x);
System.out.println(student.getName() + "." + student.getAge());
}
System.out.println("----------");
//迭代器遍历
Iterator<Student> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
System.out.println(student.getName() + "." + student.getAge());
}
}
}
LinkedList集合的特有功能
程序演示:
import java.util.LinkedList;
/*
LinkedList集合的特有功能:
void addFirst(E e) 在此列表的开头插入指定的元素。
void addLast(E e) 将指定的元素追加到此列表的末尾。
E getFirst() 返回此列表中的第一个元素。
E getLast() 返回此列表中的最后一个元素。
E removeFirst() 从此列表中删除并返回第一个元素。
E removeLast() 从此列表中删除并返回最后一个元素。
*/
public class LInkedListDemo {
public static void main(String[] args) {
//1.创建LinkedList集合对象
LinkedList<String> linkedList = new LinkedList<>();
//2.添加元素
linkedList.add("hello");
linkedList.add("world");
linkedList.add("java");
//3.void addFirst(E e) 在此列表的开头插入指定的元素。
linkedList.addFirst("javaweb");
System.out.println(linkedList);//[javaweb, hello, world, java]
//4.E getLast() 返回此列表中的最后一个元素。
linkedList.addLast("favourite");
System.out.println(linkedList);//[javaweb, hello, world, java, favourite]
//5.E getFirst() 返回此列表中的第一个元素。
String first = linkedList.getFirst();
System.out.println(first);//javaweb
//6.E getLast() 返回此列表中的最后一个元素。
String last = linkedList.getLast();
System.out.println(last);
System.out.println("---------------------");
//7.E removeFirst() 从此列表中删除并返回第一个元素。
String s = linkedList.removeFirst();
System.out.println("删除了:"+ s);//删除了:javaweb
System.out.println(linkedList);//[hello, world, java, favourite]
System.out.println("---------------------");
//8.E removeLast() 从此列表中删除并返回最后一个元素。
String s1 = linkedList.removeLast();
System.out.println("删除了:"+ s1);//删除了:favourite
System.out.println(linkedList);//[hello, world, java]
}
}
--欢迎大家与我一起交流java知识
玩弄3: 简单明了
2301_80168059: ||这个写错了吧
乄bluefox: 分组是为了环境隔离吗
乄bluefox: 博主,那个集群优先访问,如果加上分组,那感觉可以看成按分组了,还有什么解决方法吗
赫本的猫€: 短路与那里写错啦