Java中List集合有哪些特性?(内含视频教程)
  3i0nk3n0J0tF 2023年11月01日 78 0

本文将为大家详细讲解Java中的List集合,这是我们进行开发时经常用到的知识点,也是大家在学习Java中很重要的一个知识点,更是我们在面试时有可能会问到的问题。

文章较长,干货满满,建议大家收藏慢慢学习。文末有本文重点总结,主页有全系列文章分享。技术类问题,欢迎大家和我们一起交流讨论!

前言

在上一篇文章中给大家介绍了Java里的集合,我们了解了集合的由来、特点,以及一些接口API等,但这些内容都偏重于理论。 那么从今天这篇文章开始,我们会从实战的角度来进行List集合的学习。可以说,List集合是开发时用的最多的一种集合,尤其是ArrayList更是被经常使用。

所以对今天的内容,希望大家要好好阅读和练习

全文大约 【5800】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考...

一. List集合简介

1. 概述

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

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

2. 类关系

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

image.png

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

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异常。
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对象对集合总是具有最高的访问效率。
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中的新特性,后面我们会单独讲解。大家可以持续关注
import java.util.List;

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只是一个接口。
import java.util.Arrays;
import java.util.List;

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接口。其底层是一个可以动态修改的数组,该数组与普通数组的区别,在于它没有固定的大小限制,我们可以对其动态地进行元素的添加或删除

image.png

存储在集合内的数据被称为”元素“,我们可以利用索引来访问集合中的每个元素。为了方便我们操作这些元素,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()方法。

import java.util.ArrayList;

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()方法。

import java.util.ArrayList;

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()等方法。

import java.util.ArrayList;

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()方法对集合进行升序排列。

import java.util.ArrayList;
import java.util.Collections;

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. 配套视频

本节内容配套视频链接如下:戳链接即可查看

三. LinkedList集合

1. 简介

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

image.png

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

2. LinkedList类关系

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

image.png

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```的效率更高。

import java.util.LinkedList;

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()等方法获取到集合中的第一个、最后一个元素。

import java.util.LinkedList;

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. 配套视频

本节内容配套视频链接如下:戳链接直达视频教程


四. 结语

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

List是按索引顺序访问的、长度可变的有序列表;

一般开发时,ArrayList比LinkedList的使用更频繁;

List和Array可以相互转换;

集合遍历时有多种方式,增强for循环和Iterator迭代器的效率更高;

ArrayList与LinkedList都是List接口的实现类,都实现了List中所有未实现的方法,但实现的方式有所不同;

ArrayList底层的数据结构基于动态数组,访问元素速度快于LinkedList,在快速访问数据时ArrayList的执行效率比较高;

LinkedList底层的数据结构基于链表,占用的内存空间较大,但批量插入或删除数据时快于ArrayList。当频繁向集合中插入和删除元素时,使用LinkedList比ArrayList的效率更高。


以上就是本文的全部内容啦,大家有技术类问题,欢迎和我们一起交流讨论~

更多技术类干货,关注我!

我是专门分享技术干货的分享君!

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  2Vtxr3XfwhHq   2024年05月17日   53   0   0 Java
  Tnh5bgG19sRf   2024年05月20日   107   0   0 Java
  8s1LUHPryisj   2024年05月17日   46   0   0 Java
  aRSRdgycpgWt   2024年05月17日   47   0   0 Java
3i0nk3n0J0tF