目录
引言
通过第一部分的学习,不知道大家对于集合的了解是否有了更加深刻的印象呢?如果还有一些不了解,可以尝试着通过动手敲代码,将自己代入在其中,感受到Java的无限魅力,接下来我们将要详细地讲解List集合。好了废话不多说了,让我们继续走向Java知识的海洋之中!
概念
List系列集合特点:
ArrayList、LinekdList:有序、有重复、有索引
有序:存储和取出的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
List集合特有方法:
List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都继承了。
LIst集合特有API
方法名称 | 说明 |
void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
E remove (int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
接下来通过一些简单的例子来加深这些方法的理解,大家不需要刻意的去记忆这些方法,只需要在使用的过程中浏览一下,在使用多了以后,这些方法将会烂熟于心了。
public static void main(String[] args) { List<String> l = new ArrayList<>(); //1、添加数据 l.add("石原里美"); l.add("工藤静香"); l.add("宫崎骏"); l.add("哆啦A梦"); System.out.println(l); //2、删除指定索引处的元素,返回被删除的元素 System.out.println(l.remove(2)); System.out.println(l); //3、修改指定索引处的元素,返回被修改的元素 System.out.println(l.set(1, "石原里美")); System.out.println(l); //4、返回指定索引处的元素 System.out.println(l.get(2)); System.out.println(l); }
输出结果:
List的实现类的底层原理:
ArrayList底层是基于数组实现的,查询元素快,增删相对慢
LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的
List集合遍历方式
在上一节的学习中,我们已经了解到Collection共有三种遍历方式,分别是迭代器、增强for循环、Lambda表达式,而List作为Collection的子类,当然是可以使用父类的三种遍历方式,另外List还有其独有的遍历方式:for循环(因为其具有索引)。在这就不对其他三种多做赘述了,仅在此列举第四种,详细介绍可以看第一篇的文章。
public static void main(String[] args) { List<String> l = new ArrayList<>(); l.add("石原里美"); l.add("工藤静香"); l.add("宫崎骏"); l.add("哆啦A梦"); for (int i = 0; i < l.size(); i++) { System.out.println(l.get(i)); } }
List集合底层原理
ArrayList
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组(在使用完这十个位置以后,会将原来的数据迁移过来,并且以原来的1.5倍进行扩容)
LinkedList
特点:
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
LinkedList特有功能:
方法名称 说明 public void addFirst(E e) 在该列表开头插入指定元素 public void addLast(E e) 在该列表末尾插入指定元素 public E getFirst() 返回此列表中的第一个元素 public E getLast() 返回此列表中的最后一个元素 public E removeFirst() 删除列表中的第一个元素,并返回 public E removeLast() 删除列表中的最后一个元素,并返回 接下来是一个简单的案列,帮助大家更好的理解这些方法在实际应用中是如何作用的:
public static void main(String[] args) { LinkedList<String> l = new LinkedList<>(); l.addFirst("东");//也可以写为l.push("东") l.addFirst("南"); l.addFirst("西"); l.addFirst("北"); System.out.println(l); l.removeFirst();//也可以写为l.pop(); l.removeFirst(); l.removeFirst(); System.out.println(l); LinkedList<String> L = new LinkedList<>(); L.addLast("黑"); L.addLast("红"); L.addLast("梅"); L.addLast("芳"); System.out.println(L); L.removeLast(); L.removeLast(); L.removeLast(); System.out.println(L); } //输出结果: [北, 西, 南, 东] [东] [黑, 红, 梅, 芳] [黑]
集合的并发修改异常问题
问题引出:
当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题
那么哪些遍历存在问题?
迭代器遍历
迭代器遍历集合且直接用集合删除元素的时候可能出现
public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add("工藤静香"); list.add("石原里美"); list.add("石原里美"); list.add("花泽香菜"); System.out.println(list); //迭代器遍历 Iterator<String> l = list.iterator(); while (l.hasNext()){ String s = l.next(); if (s.equals("石原里美")){ list.remove(s); } } }
报错情况:
大致一看,这篇文章的逻辑和语法都是没有问题的,但是当我们运行的时候却会出现如上的报错情况,这是为什么呢?
那么我们如果想了解错误出在哪,就应该首先要理解迭代器的工作原理:迭代器在遍历的时候会通过next方法依次向后跳一步,由此来对每一个数据进行访问。然而当我们使用集合删除的时候:当跳到“石原里美”这的时候,由于判断条件的符合而执行,会将其删除,然而在删除以后,第二个石原里美会落在第一个的位置,但是迭代器并不知道这件事的发生,它仍然会按照原来的方式,向后跳一格,由此将忽略了这个数据,从而无法达到目的。
解决方案:既然集合的删除无法让迭代器知道这件事,那么我们应该通过迭代器的删除方法对数据进行删除,使其不会发生跳过该数据的情况。(将list.remove()改为l.remove()即可)
增强for循环(Lambda表达式)
增强for循环遍历集合且直接用集合删除元素的时候可能出现
for (String s :list){ if(s.equals("石原里美")){ list.remove(s); } }
报错情况:
增强for循环在遍历删除的时候出现的情况与前面迭代器出现的情况一样,且工作原理也相同,因此在删除的时候应该选择迭代器去删除。而Lambda表达式在遍历方面与增强for循环基本类似,且报错情况也一样,在此就不再赘述了。
for循环
然而第四种for循环是可以运行的,并不会报错,但是其实验结果与我们理想的结果并不符合
for (int i = 0; i < list.size(); i++) { if(list.get(i).equals("石原里美")){ list.remove(list.get(i)); } } System.out.println(list);
输出结果:
出现这样的情况与前面的解释是一样的,它同样是会漏掉一些数据,但是不同之处在于它是不会出现报错的,它会一错再错地运行下去。不过是有两种方案去解决它的,一种是反着进行遍历删除,另一种则是在原来的基础上作i–行为,使其重新回到原来的位置。这样就可以避免漏掉数据的情况发生。
创作不易,给个三连