从零开始学Java之初学者必会的List集合怎么玩?

配套开源项目资料

Github: GitHub - SunLtd/LearnJava

Gitee: 一一哥/从零开始学Java

一. List集合简介

1. 概述

List本身是一个接口,该接口继承自Collection接口,它有两个常用的实现子类ArrayList和LinkedList。从功能特性上来看,List是有序、可重复的单列集合,集合中的每个元素都有对应的顺序索引,我们可以通过该索引来访问指定位置上的集合元素。默认情况下,List会按元素的添加顺序给元素设置索引,第一个添加到List集合中的元素索引为0,第二个为1,后面依此类推。所以List的行为和数组几乎完全相同,它们都是有序的存储结构。另外List集合中允许有重复的元素,甚至可以有多个null值

但是如果我们是使用数组来添加和删除元素,就会非常的不方便。比如从一个已有的数组{'A', 'B', 'C', 'D', 'E'}中删除索引为2的元素,这个“删除”操作实际上是把'C'后面的元素依次往前挪一个位置;而“添加”操作实际上是把指定位置以后的元素依次向后挪一个位置,腾出位置给新加入的元素。针对这两种操作,使用数组实现起来都会非常麻烦。所以在实际应用中,我们增删元素时,一般都是使用有序列表(如ArrayList),而不是使用数组。

2. 类关系

我们来看看List接口的类关系,如下图所示:

从这个类关系中我们可以看到,List接口继承了Collection接口,并且有ArrayList、LinkedList、Vector等子类,其中Vector现在已经不太常用了,所以我们重点掌握ArrayList和LinkedList就行。

3. 常用API方法

在List接口中定义了子类的一些通用方法,如下所示:

  • boolean add(E e) :在集合末尾添加一个数据元素;
  • boolean add(int index, E e) :在集合的指定索引出添加一个数据元素;
  • E remove(int index) :删除集合中指定索引的元素;
  • boolean remove(Object e) :删除集合中的某个元素;
  • E get(int index) :获取集合中指定索引出的元素;
  • int size() :获取集合的大小(包含元素的个数)。

以上这些方法,就是我们在开发时比较常用的几个方法,大家需要记住哦。

4. List对象创建方式

List作为一个接口,我们通常不能直接new List来创建其对象,在Java中给我们提供了如下两种创建List对象的方式:

  • 通过多态方式创建:new List的某个子类,比如new ArrayList()等;
  • 通过List.of()方法创建:of()方法可以根据给定的数据元素快速创建出List对象,但该方法不接受null值,如果传入null会抛出NullPointerException异常。
 
 

java

复制代码

import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class Demo01 { public static void main(String[] args) { //创建List对象方式一: List<String> list1=new ArrayList<>(); List<String> list2=new LinkedList<>(); //创建List对象方式二: List<Integer> list3 = List.of(1,3,5,7,9); //该方式不能传入null参数,否则会产生NullPointerException异常 //List<Integer> list4 = List.of(1,3,5,7,9,null); } }

5. List集合遍历方式

很多时候,我们都会对集合进行遍历操作,也就是要把集合中的每个元素挨个的取出来,以下是几种常用的集合遍历方式:

  • 普通for循环配合get(索引值)方法进行遍历: 这种遍历方式实现起来代码较为复杂,且get(int)取值方法只对ArrayList比较高效,但对LinkedList效率较低,索引越大时访问速度越慢。
  • 增强for循环进行遍历: 我们也可以使用增强for循环进行遍历,该方式比普通for循环实现起来更为简洁。
  • 使用Iterator迭代器进行集合遍历: 不同的List对象调用iterator()方法时,会返回不同实现的Iterator对象,该Iterator对象对集合总是具有最高的访问效率。
 
 

java

复制代码

import java.util.Iterator; import java.util.List; public class Demo02 { public static void main(String[] args) { //List遍历方式一,普通for循环: List<String> list = List.of("java", "大数据", "壹壹哥"); for(int i=0;i<list.size();i++) { System.out.println("遍历方式一,值="+list.get(i)); } //List遍历方式二,迭代器: Iterator<String> it = list.iterator(); while(it.hasNext()){ //取出下一个值 String value = it.next(); System.out.println("遍历方式二,值="+value); } //List遍历方式三,增强for循环:内部会自动使用Iterator for(String item:list) { System.out.println("遍历方式三,item="+item); } } }

上面提到的Iterator对象,有两个常用方法,如下所示:

  • boolean hasNext():该方法用于判断集合中是否还有下一个元素;
  • E next():该方法用于返回集合的下一个元素。

虽然使用Iterator遍历List集合的代码,看起来比使用索引较复杂,但Iterator遍历List集合的效率却是最高效的方式。

另外只要是实现了Iterable接口的集合类,我们都可以直接使用for each增强循环来遍历。在增强for循环中,Java编译器会自动把for each循环变成基于Iterator方式的遍历方式。

6. List与数组的转换方式

其实List与Array数组在很多地方都是比较相似的,比如都可以根据索引对数据元素进行遍历取值等操作。因为存在着这种相似之处,所以在List和数组之间是可以互相转换的,即List集合可以转成数组,数组也可以转成List集合。

6.1 List转数组

一般情况下,List转数组有如下几种方式:

  • toArray() 方法:该方法会返回一个Object[]数组,但该方法会丢失类型信息,在实际开发时较少使用;
  • toArray(T[])方法:传入一个与集合的数据元素类型相同的Array,List会自动把元素复制到传入的Array中;
  • T[] toArray(IntFunction<T[]> generator) 方法:函数式写法,这是Java中的新特性,后面壹哥会单独讲解。
 
 

java

复制代码

import java.util.List; /** * @author Sun */ public class Demo03 { public static void main(String[] args) { List<String> list = List.of("java", "大数据", "壹壹哥"); // List转数组方式一:返回一个Object[]数组 Object[] array = list.toArray(); for (Object val : array) { System.out.println("方式一,value="+val); } // List转数组方式二,给toArray(T[])传入与数组元素类型相同的Array,如果数组类型与集合数据元素类型不匹配则会产生如下异常: // java.lang.ArrayStoreException: arraycopy: element type mismatch: //can not cast one of the elements of java.lang.Object[] to the type of the destination array, java.lang.Integer String[] array2 = list.toArray(new String[list.size()]); for (String val : array2) { System.out.println("方式二,value="+val); } // List转数组方式三:返回一个String[]数组 String[] array3 = list.toArray(String[]::new); for (String val : array3) { System.out.println("方式二,value="+val); } } }

在本案例中,第一种实现方式是调用toArray()方法直接返回一个Object[]数组,但这种方法会丢失类型信息,所以开发是很少使用。

第二种方式要给toArray(T[])方法传入一个与集合数据元素类型相同的Array,List内部会自动把元素复制到传入的Array数组中。如果Array类型与集合的数据元素类型不匹配,就会产生”java.lang.ArrayStoreException: arraycopy: element type mismatch: can not cast one of the elements of java.lang.Object[] to the type of the destination array......“异常。

第三种方式是通过List接口定义的T[] toArray(IntFunction<T[]> generator)方法,这是一种函数式写法,壹哥以后再单独给大家讲解。

6.2 数组转List

反过来,数组也可以转为List集合,一般的方式如下:

  • List.of(T...)方法:该方法会返回一个只读的List集合,如果我们对只读List调用add()、remove()方法会抛出UnsupportedOperationException异常。其中的T是泛型参数,代表要转成List集合的数组;
  • Arrays.asList(T...) 方法:该方法也会返回一个List集合,但它返回的List不一定就是ArrayList或者LinkedList,因为List只是一个接口。
 
 

java

复制代码

import java.util.Arrays; import java.util.List; /** * @author Sun */ public class Demo04 { public static void main(String[] args) { // 数组转List的方式一:List.of()返回一个只读的集合,不能进行add/remove等修改操作。 List<Integer> values = List.of(1,8,222,10,5); for (Integer val : values) { System.out.println("方式一,value="+val); //该集合是一种只读的集合,不能在遍历时进行增删改等更新操作,只能进行读取操作, //否则会产生java.lang.UnsupportedOperationException异常 //values.remove(0); } // 数组转List的方式二:Arrays.asList()返回一个只读的集合,不能进行add/remove等修改操作。 List<String> items = Arrays.asList("java","壹壹哥","元宇宙"); for (String item : items) { System.out.println("方式二,value="+item); //不可以进行增删改操作 //items.add("sss"); //items.remove(0); } } }

在本案例中,无论我们是通过List.of()方法,还是通过Arrays.asList()方法,都只会返回一个只读的集合。这种集合在遍历时不能进行增删改等更新操作,只能进行读取操作,否则会产生java.lang.UnsupportedOperationException异常。

二. ArrayList集合

1. 简介

ArrayList是一个数组队列,位于java.util包中,它继承自AbstractList,并实现了List接口。其底层是一个可以动态修改的数组,该数组与普通数组的区别,在于它没有固定的大小限制,我们可以对其动态地进行元素的添加或删除。

存储在集合内的数据被称为”元素“,我们可以利用索引来访问集合中的每个元素。为了方便我们操作这些元素,ArrayList给我们提供了相关的添加、删除、修改、遍历等功能。

因为ArrayList的底层是一个动态数组,所以该集合适合对元素进行快速的随机访问(遍历查询),另外尾部成员的增加和删除操作速度也较快,但是其他位置上元素的插入与删除速度相对较慢。基于这种特性,所以ArrayList具有查询快,增删慢的特点

2. 常用方法

ArrayList给我们提供了如下这些方法,我们可以先来了解一下:

方法 描述
add() 将数据元素插入到ArrayList的指定位置上
addAll() 将一个新集合中的所有元素添加到ArrayList中
clear() 删除ArrayList中所有的元素
contains() 判断元素是否在ArrayList中
get() 通过索引值获取ArrayList中的元素
indexOf() 返回ArrayList中某个元素的索引值
removeAll() 删除ArrayList中指定集合的所有元素
remove() 删除ArrayList里的单个元素
size() 返回ArrayList的元素数量
isEmpty() 判断ArrayList是否为空
subList() 截取ArrayList的部分元素
set() 替换ArrayList中指定索引的元素
sort() 对ArrayList的数据元素进行排序
toArray() 将ArrayList转换为数组
toString() 将ArrayList转换为字符串
ensureCapacity() 设置指定容量大小的ArrayList
lastIndexOf() 返回指定元素在ArrayList中最后一次出现的位置
retainAll() 保留指定集合中的数据元素
containsAll() 查看ArrayList是否包含了指定集合的所有元素
trimToSize() 将ArrayList的容量调整为数组的元素个数
removeRange() 删除ArrayList中指定索引间存在的元素
replaceAll() 用给定的数据元素替换掉指定数组中每个元素
removeIf() 删除所有满足特定条件的ArrayList元素
forEach() 遍历ArrayList中每个元素并执行特定操作

接下来壹哥就挑选几个常用的方法,通过几个案例来给大家讲解一下ArrayList的用法。

3. 添加元素

ArrayList给我们提供了多个与添加相关的方法,比如add()和addAll()方法,可以将元素添加到集合中。另外如果我们要计算ArrayList中元素的数量,可以使用size()方法。

 
 

java

复制代码

import java.util.ArrayList; /** * @author 一一哥Sun */ public class Demo05 { public static void main(String[] args) { //创建ArrayList集合,<String>中的是泛型,后面我们会专门讲解泛型 ArrayList<String> names = new ArrayList<String>(); //一个一个地添加元素 names.add("一一哥"); names.add("java"); names.add("数学"); //遍历集合 for (String name : names) { System.out.println("name="+name+",size="+names.size()); } ArrayList<String> names2 = new ArrayList<String>(); names2.add("壹壹哥"); //在A集合中追加B集合 names2.addAll(names); //遍历集合 for (String name : names2) { System.out.println("name="+name); } } }

在上面的代码中,这部分是泛型,壹哥会在后面给大家专门讲解,敬请期待哦。

4. 遍历元素

我们对ArrayList中元素进行遍历的方式,其实与List的遍历是一样的,我们可以使用普通for循环、增强for循环、Iterator迭代器等方式对集合进行遍历,这里我们就不再单独展示其用法了。

5. 修改元素

我们使用add()方法将元素添加到集合中之后,如果想对集合中的元素进行修改,可以使用set()方法。

 
 

java

复制代码

import java.util.ArrayList; /** * @author 一一哥Sun */ public class Demo06 { public static void main(String[] args) { //创建ArrayList集合,<String>中的是泛型,后面我们会专门讲解泛型 ArrayList<String> names = new ArrayList<String>(); //一个一个地添加元素 names.add("一一哥"); names.add("java"); names.add("数学"); //修改集合中的元素:第一个参数是集合中的索引,第二个是要修改的值 names.set(1, "Android"); names.set(2, "iOS"); //遍历集合 for (String name : names) { System.out.println("name="+name); } } }

6. 删除元素

如果我们要删除ArrayList中的元素,可以使用remove()、removeAll()等方法。

 
 

java

复制代码

import java.util.ArrayList; /** * @author 一一哥Sun */ public class Demo07 { public static void main(String[] args) { //创建ArrayList集合,<String>中的是泛型,后面我们会专门讲解泛型 ArrayList<String> names = new ArrayList<String>(); //一个一个地添加元素 names.add("一一哥"); names.add("java"); names.add("数学"); //删除集合中指定位置上的某个元素 names.remove(0); //删除集合中的某个指定元素 names.remove("java"); //遍历集合 for (String name : names) { System.out.println("name="+name); } ArrayList<String> names2 = new ArrayList<String>(); names2.add("语文"); names2.add("英语"); names2.add("数学"); //删除本集合中的另一个集合 names2.removeAll(names); //遍历集合 for (String name : names2) { System.out.println("name2="+name); } } }

7. 集合排序

我们可以使用Collections.sort()方法对集合进行升序排列。

 
 

java

复制代码

import java.util.ArrayList; import java.util.Collections; /** * @author 一一哥Sun */ public class Demo08 { public static void main(String[] args) { //创建ArrayList集合 ArrayList<Integer> nums = new ArrayList<>(); //一个一个地添加元素 nums.add(100); nums.add(85); nums.add(120); nums.add(55); //对集合进行排序,默认是升序排列 Collections.sort(nums); //遍历集合 for (Integer num : nums) { System.out.println("num="+num); } } }

8. 配套视频

本节内容配套视频链接如下:

player.bilibili.com/player.html…

三. LinkedList集合

1. 简介

LinkedList采用链表结构来保存数据,所以是一种链表集合,类似于ArrayList,也是List的一个子类,位于java.util包中。它的底层是基于线性链表这种常见的数据结构,但并没有按线性的顺序存储数据,而是在每个节点中都存储了下一个节点的地址。

LinkedList的优点是便于向集合中插入或删除元素,尤其是需要频繁地向集合中插入和删除元素时,使用LinkedList类比ArrayList的效率更高。但LinkedList随机访问元素的速度则相对较慢,即检索集合中特定索引位置上的元素速度较慢。

2. LinkedList类关系

LinkedList直接继承自AbstractSequentialList,并实现了List、Deque、Cloneable、Serializable等多个接口。通过实现List接口,具备了列表操作的能力;通过实现Cloneable接口,具备了克隆的能力;通过实现Queue和Deque接口,可以作为队列使用;通过实现Serializable接口,可以具备序列化能力。LinkedList类结构关系如下图所示:

3. LinkedList与ArrayList对比

与ArrayList相比,LinkedList进行添加和删除的操作效率更高,但查找和修改的操作效率较低。基于这种特性,我们可以在以下情况中使用ArrayList:

  • 需要经常访问获取列表中的某个元素;
  • 只需要在列表的 末尾 进行添加和删除某个元素。

当遇到如下情况时,可以考虑使用LinkedList:

  • 需要经常通过 循环迭代来访问 列表中的某些元素;
  • 需要经常在列表的 开头、中间、末尾 等位置进行元素的添加和删除操作。

4. 常用方法

LinkedList中的很多方法其实都来自于List接口,所以它的很多方法与ArrayList是一样的。但由于其自身特点,也具有一些特有的常用方法,这里壹哥只列出LinkedList特有的常用方法,如下表所示:

方法 描述
public void addFirst(E e) 将元素添加到集合的头部。
public void addLast(E e) 将元素添加到集合的尾部。
public boolean offer(E e) 向链表的末尾添加元素,成功为true,失败为false。
public boolean offerFirst(E e) 在链表头部插入元素,成功为true,失败为false。
public boolean offerLast(E e) 在链表尾部插入元素,成功为true,失败为false。
public void clear() 清空链表。
public E removeFirst() 删除并返回链表的第一个元素。
public E removeLast() 删除并返回链表的最后一个元素。
public boolean remove(Object o) 删除某一元素,成功为true,失败为false。
public E remove(int index) 删除指定位置的元素。
public E poll() 删除并返回第一个元素。
public E remove() 删除并返回第一个元素。
public E getFirst() 返回第一个元素。
public E getLast() 返回最后一个元素。
public int lastIndexOf(Object o) 查找指定元素最后一次出现的索引。
public E peek() 返回第一个元素。
public E element() 返回第一个元素。
public E peekFirst() 返回头部元素。
public E peekLast() 返回尾部元素。
public Iterator descendingIterator() 返回倒序迭代器。
public ListIterator listIterator(int index) 返回从指定位置开始到末尾的迭代器。

对这些方法进行基本的了解之后,接下来我们选择几个核心方法来来看看具体该怎么使用。

5. 添加/删除元素

我们可以通过addFirst()和addLast()方法,分别在链表的开头和结尾添加一个元素。当我们要频繁地在一个列表的开头和结尾进行元素添加、删除时,使用LinkedList要比ArrayList的效率更高。

 
 

java

复制代码

import java.util.LinkedList; /** * @author 一一哥Sun */ public class Demo09 { public static void main(String[] args) { // 创建LinkedList集合 LinkedList<String> names = new LinkedList<String>(); // 一个一个地添加元素 names.add("一一哥"); names.add("java"); names.add("数学"); //在链表的开头添加元素 names.addFirst("壹壹哥"); //在链表的结尾添加元素 names.addLast("历史"); // 遍历集合 for (String name : names) { System.out.println("name=" + name); } //移除链表开头的元素 names.removeFirst(); //移除链表结尾的元素 names.removeLast(); } }

6. 迭代获取元素

我们可以通过getFirst()、getLast()等方法获取到集合中的第一个、最后一个元素。

 
 

java

复制代码

import java.util.LinkedList; /** * @author 一一哥Sun */ public class Demo10 { public static void main(String[] args) { // 创建LinkedList集合 LinkedList<String> names = new LinkedList<String>(); // 一个一个地添加元素 names.add("一一哥"); names.add("java"); names.add("数学"); System.out.println("first=" + names.getFirst()); System.out.println("last=" + names.getLast()); // 迭代遍历集合 for (String name : names) { System.out.println("name=" + name); } } }

7. 配套视频

本节内容配套视频链接如下:

player.bilibili.com/player.html…

------------------------------正片已结束,来根事后烟----------------------------

四. 结语

至此,壹哥就把List集合给大家讲解完毕了,最后我们再来看看本文的重点吧:

  • List 是按索引顺序访问的、长度可变的有序列表;
  • 一般开发时, ArrayList比LinkedList的使用更频繁
  • List和Array可以相互转换;
  • 集合遍历时有多种方式,增强for循环和Iterator迭代器的效率更高;
  • ArrayList与LinkedList都是List接口的实现类,都实现了List中所有未实现的方法,但实现的方式有所不同;
  • ArrayList底层的数据结构基于动态数组,访问元素速度快于LinkedList,在快速访问数据时ArrayList的执行效率比较高;
  • LinkedList底层的数据结构基于链表,占用的内存空间较大,但批量插入或删除数据时快于ArrayList。当频繁向集合中插入和删除元素时,使用LinkedList比ArrayList的效率更高。

猜你喜欢

转载自blog.csdn.net/BASK2312/article/details/131125593