第三十一講 集合框架——List介面
List介面概述
Collection介面有兩個子介面:List(列表)、Set(集),本文我們先重點學習List(列表)介面。查閱API,檢視List的介紹,我們可以發現以下這些話語:
有序的collection(也稱為序列)。此介面的使用者可以對列表中每個元素的插入位置進行精確地控制。使用者可以根據元素的整數索引(在列表中的位置)訪問元素,並搜尋列表中的元素。與set不同,列表通常允許重複的元素。
看完API,我們總結一下:
- List介面是一個元素存取有序的集合。注意:有序指的是存入的順序和取出的順序一致。例如,存元素的順序是11、22、33,那麼集合中元素的儲存就是按照11、22、33的順序完成的;
- List介面是一個帶有索引的集合。通過索引就可以精確的操作集合中的元素(與陣列的索引是一個道理);
- 集合中可以有重複的元素。通過元素的equals方法,來比較是否為重複的元素。
List介面中的常見方法
我們已學完Collection介面的大部分方法,List介面中同樣有這些方法,所以就不必重複了。現在我們將重點放在List介面中的特有方法上,它的特有方法都是圍繞索引定義的。List介面支援增刪改查,如下:
現在我們演示List介面中的特有方法。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List list = new ArrayList();
methodDemo(list);
}
/*
* 演示List特有的方法。
*/
public static void methodDemo(List list) {
//1,常規新增元素
list.add("abc1");
list.add("abc2");
list. add("abc3");
//2,插入元素
// list.add(1, "hehe");
//3,刪除
// list.remove(1);
// list.remove(1);
//4,獲取
// System.out.println(list.get(1));
// System.out.println(list.indexOf("abc3"));
//5,修改
// list.set(1, "kk");
//取出集合中所有的元素
for (Iterator it = list.iterator(); it.hasNext();) {
System.out.println("iterator: " + it.next());
}
//List集合特有的取出方式
for (int i = 0; i < list.size(); i++) {
System.out.println("get: " + list.get(i));
}
// System.out.println(list);
}
}
List介面特有的迭代器——ListIterator
現在我們來思考這樣一個問題:在迭代過程中,準備新增或者刪除元素。例如,在List集合迭代元素中,對元素進行判斷,一旦條件滿足就新增一個新元素。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.List;
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
//希望在遍歷的過程中,如何遍歷到"abc2",新增一個元素"haha"
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
list.add("haha");//java.util.ConcurrentModificationException,
//迭代過程中,使用了集合物件同時對元素進行操作,導致了迭代的不確定性。引發了該異常。
//解決思想:在迭代過程中,想要執行一些操作,使用迭代器的方法就可以了。
}
System.out.println(obj);
}
}
}
執行上述程式碼發生了異常——java.util.ConcurrentModificationException[併發修改異常],這是什麼原因呢?這是因為在使用迭代器或者增強for迴圈遍歷集合的時候,再呼叫集合的方法修改集合的長度(新增和刪除),就會發生併發修改異常。也可以這樣說,在迭代過程中,使用了集合的方法對元素進行操作,會導致迭代器並不知道集合中的變化,容易引發資料的不確定性。注意:併發修改異常是由next()方法丟擲的。
併發修改異常的原始碼分析
雖然我們知道其原因了,但是要想知道其更深層次的原因,還得看原始碼,下面跟著我一起去看看原始碼吧!
ArrayList集合中有一個成員變數modCount(該成員變數在其父介面AbstractList中),它用來記錄集合長度修改的次數,每次修改集合的長度,這個變數就會增長。
迭代器有一個成員變數expectedModCount,它是預期被修改的值,它的初始值是被修改的值。
迭代器的next()方法原始碼截圖:
點選進入checkForComodification()該方法裡面去,檢視其原始碼。
該方法比較預期修改的次數和修改的次數是不是相等,如果不相等,就丟擲併發修改異常。所以,在迭代時,不可以通過集合物件的方法操作集合中的元素,因為會發生併發修改異常(ConcurrentModificationException)。
ListIterator介面的listIterator()方法
綜上,在迭代時,只能用迭代器的方法操作元素,可是Iterator的方法是有限的,只能對元素進行判斷,取出,刪除的操作,如果想要其他的操作如新增,修改等,就需要使用其子介面ListIterator(List集合特有的迭代器),該介面只能通過List集合的listIterator方法獲取。
package cn.liayun.list.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
/*
//希望在遍歷的過程中,如何遍歷到"abc2",新增一個元素"haha"
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
list.add("haha");//java.util.ConcurrentModificationException,
//迭代過程中,使用了集合物件同時對元素進行操作,導致了迭代的不確定性。引發了該異常。
//解決思想:在迭代過程中,想要執行一些操作,使用迭代器的方法就可以了。
}
System.out.println(obj);
}
*/
//使用List集合的特有迭代器ListIterator來實現,通過List集合的方法listIterator()方法獲取該迭代器物件。
//ListIterator介面可以實現在迭代過程中的增刪改查。
for (ListIterator it = list.listIterator(); it.hasNext();) {
Object obj = it.next();
if (obj.equals("abc2")) {
it.add("haha");
}
}
System.out.println(list);
}
}
List集合儲存資料的結構
List介面下有很多個集合,它們儲存元素所採用的結構方式是不同的,這樣就導致了這些集合有它們各自的特點,供給我們在不同的環境下進行使用。資料儲存的常用結構有:堆疊、佇列、陣列、連結串列。我們分別來了解一下:
堆疊
採用該結構的集合,對元素的存取有如下的特點:
佇列
採用該結構的集合,對元素的存取有如下的特點:
陣列
採用該結構的集合,對元素的存取有如下的特點:
連結串列
採用該結構的集合,對元素的存取有如下的特點:
List介面的具體子類
List集合的具體子類還蠻有幾個,子類之所以區分,就是因為其內部的資料結構(儲存資料的方式)不同。
ArrayList
ArrayList底層使用的是陣列結構,所以它可以儲存重複的元素。
ArrayList去除重複元素(集合中元素是字串)方式一
例,定義功能,請去除ArrayList集合中的重複元素(集合中元素是字串)。
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListTest2 {
public static void main(String[] args) {
/*
* 練習1,定義功能,請去除ArrayList集合中的重複元素。
*/
List list = new ArrayList();
list.add("abc1");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc2");
System.out.println(list);
singleElement(list);
System.out.println(list);
}
/**
* 定義功能。去除重複元素
*/
public static void singleElement(List list) {
//選擇排序
for (int x = 0; x < list.size() - 1; x++) {
Object obj_x = list.get(x);
for (int y = x + 1; y < list.size(); y++) {
if (obj_x.equals(list.get(y))) {
list.remove(y);
y--;
}
}
}
}
}
這種解決方式其實有點類似於選擇排序,不是嗎?下面我們來看去除重複元素的第二種解決方式。
ArrayList去除重複元素(集合中元素是字串)方式二
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ArrayListTest2 {
public static void main(String[] args) {
/*
* 練習1,定義功能,請去除ArrayList集合中的重複元素。
*/
List list = new ArrayList();
list.add("abc1");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc4");
list.add("abc2");
list.add("abc1");
list.add("abc4");
list.add("abc2");
System.out.println(list);
singleElement2(list);
System.out.println(list);
}
/**
* 去除重複元素方式二。
* 思路:
* 1,最後唯一性的元素,可以先定義一個容器用於儲存這些唯一性的元素。
* 2,對原有的容器進行元素的獲取,併到臨時容器中去判斷是否存在,容器本身就有這個功能,判斷元素是否存在
* 3,存在就不儲存,不存在就儲存。
* 4,遍歷完原容器後,臨時容器中儲存的就是唯一性的元素了。
*/
public static void singleElement2(List list) {
//1,定義一個臨時容器
List temp = new ArrayList();
//2,遍歷原容器
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = (Object) it.next();
//3,在臨時容器中判斷遍歷到的元素是否存在
if (!temp.contains(obj)) {
//4,如果不存在,就儲存到臨時容器中
temp.add(obj);
}
}
//5,將原容器清空
list.clear();
//6,將臨時容器中的元素都儲存到原容器當中
list.addAll(temp);
}
}
ArrayList去除重複的自定義元素
例,將自定義物件作為元素存到ArrayList集合中,並去除重複元素。比如:存人物件。同姓名同年齡,視為同一個人,為重複元素。
思路:
- 對Person類進行描述,將資料封裝到人物件中;
- 定義容器物件,將多個Person物件儲存到集合中;
- 去除同姓名同年齡的Person物件(重複元素);
- 取出集合中的Person物件。
package cn.liayun.domain;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
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;
}
/**
* 建立Person類自己的判斷物件是否相同的依據,必須要覆蓋Object類中的equals方法。
*/
public boolean equals(Object obj) {
//為了提高效率,如果比較的物件是同一個,直接返回true
if (this == obj) {
return true;
}
// System.out.println(this + "......" + obj);
if (!(obj instanceof Person)) {
throw new ClassCastException("型別錯誤");
}
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
注意:在Person類中必須覆蓋Object類中的equals方法,不久我們就會看到集合的contains()方法底層呼叫的就是equals()方法。
package cn.liayun.list.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import cn.liayun.domain.Person;
public class ArrayListTest {
public static void main(String[] args) {
/*
* 練習:1,往ArrayList集合中儲存自定義物件。Person(name, age)
* 思路:
* 1,描述Person
* 2,定義容器物件
* 3,將多個Person物件儲存到集合當中
* 4,取出Person物件
*/
//1,建立ArrayList集合物件
List list = new ArrayList();
//2,新增Person型別的物件
Person p1 = new Person("lisi1", 21);
Person p2 = new Person("lisi2", 22);
list.add(p1);
list.add(p1);//儲存了一個地址相同的物件
list.add(p2);
list.add(new Person("lisi3", 23));
list.add(new Person("lisi1", 21));
list.add(new Person("lisi2", 22));
/*
//3,取出元素
for (Iterator it = list.iterator(); it.hasNext();) {
//it.next():取出的元素都是Object型別的。需要用到具體物件的內容時,需要向下轉型。
Person p = (Person) it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
*/
System.out.println(list);
singleElement2(list);//去除重複元素
System.out.println(list);
}
/**
* 去除重複元素方式二。
* 思路:
* 1,最後唯一性的元素,可以先定義一個容器用於儲存這些唯一性的元素。
* 2,對原有的容器進行元素的獲取,併到臨時容器中去判斷是否存在,容器本身就有這個功能,判斷元素是否存在
* 3,存在就不儲存,不存在就儲存。
* 4,遍歷完原容器後,臨時容器中儲存的就是唯一性的元素了。
*/
public static void singleElement2(List list) {
//1,定義一個臨時容器
List temp = new ArrayList();
//2,遍歷原容器
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = (Object) it.next();
//3,在臨時容器中判斷遍歷到的元素是否存在
if (!temp.contains(obj)) {
//4,如果不存在,就儲存到臨時容器中
temp.add(obj);
}
}
//5,將原容器清空
list.clear();
//6,將臨時容器中的元素都儲存到原容器當中
list.addAll(temp);
}
}
通過以上示例,說明contains()底層用的是equals()。
結論
List集合判斷元素是否相同,依據的是元素的equals方法。
LinkedList
底層資料結構是連結串列,查詢慢,增刪快,執行緒不安全,效率高。
LinkedList中的特有方法
實際開發中對一個集合元素的新增與刪除經常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法,如下:
方法宣告 | 功能描述 |
---|---|
public void addFirst(E e) | 將指定元素插入此列表的開頭 |
public void addLast(E e) | 將指定元素插入此列表的結尾 |
public E getFirst() | 獲取集合的第一個元素 |
public E getLast() | 獲取集合的最後一個元素 |
public E removeFirst() | 刪除集合的第一個元素 |
public E removeLast() | 刪除集合的最後一個元素 |
package cn.liayun.list.linkedlist;
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
//1,建立一個連結串列物件,演示xxxFirst、xxxLast方法
LinkedList link = new LinkedList();
//2,新增方法
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
//3,獲取元素
// System.out.println(link.getFirst());
// System.out.println(link.getFirst());
//4,刪除元素
// System.out.println(link.removeFirst());
// System.out.println(link.removeFirst());
//取出link中所有元素
while (!link.isEmpty()) {
System.out.println(link.removeLast());
}
}
}
面試題:LinkedList實現一個堆疊或者佇列資料結構
package cn.liayun.list.linkedlist;
import java.util.LinkedList;
public class LinkedListTest {
public static void main(String[] args) {
/*
* 練習:請通過LinkedList實現一個堆疊或者佇列資料結構。
* 堆疊:先進後出,First In Last Out(FILO)
* 佇列:先進先出,Fisrt In First Out(FIFO)
*/
//1,建立自定義的佇列物件
MyQueue q