**本文將為大家詳細講解Java中的List集合,這是我們進行開發時經常用到的知識點,也是大家在學習Java中很重要的一個知識點,更是我們在面試時有可能會問到的問題。** **文章較長,乾貨滿滿,建議大家收藏慢慢學習。文末有本文重點總結,主頁有全系列文章分享。技術類問題,歡迎大家和我們一起交流討論! ...
本文將為大家詳細講解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介面的類關係,如下圖所示:
從這個類關係中我們可以看到,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異常。
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介面。其底層是一個可以動態修改的數組,該數組與普通數組的區別,在於它沒有固定的大小限制,我們可以對其動態地進行元素的添加或刪除
存儲在集合內的數據被稱為”元素“,我們可以利用索引來訪問集合中的每個元素。為了方便我們操作這些元素,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包中。它的底層是基於線性鏈表這種常見的數據結構,但並沒有按線性的順序存儲數據,而是在每個節點中都存儲了下一個節點的地址。
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```的效率更高。
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的效率更高。
以上就是本文的全部內容啦,大家有技術類問題,歡迎和我們一起交流討論~
更多技術類乾貨,關註我!
我是專門分享技術乾貨的分享君!