1. 程式人生 > >第三十一講 集合框架——List介面

第三十一講 集合框架——List介面

List介面概述

Collection介面有兩個子介面:List(列表)、Set(集),本文我們先重點學習List(列表)介面。查閱API,檢視List的介紹,我們可以發現以下這些話語:

有序的collection(也稱為序列)。此介面的使用者可以對列表中每個元素的插入位置進行精確地控制。使用者可以根據元素的整數索引(在列表中的位置)訪問元素,並搜尋列表中的元素。與set不同,列表通常允許重複的元素。

看完API,我們總結一下:

  1. List介面是一個元素存取有序的集合。注意:有序指的是存入的順序和取出的順序一致。例如,存元素的順序是11、22、33,那麼集合中元素的儲存就是按照11、22、33的順序完成的;
  2. List介面是一個帶有索引的集合。通過索引就可以精確的操作集合中的元素(與陣列的索引是一個道理);
  3. 集合中可以有重複的元素。通過元素的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集合中,並去除重複元素。比如:存人物件。同姓名同年齡,視為同一個人,為重複元素。

思路:

  1. 對Person類進行描述,將資料封裝到人物件中;
  2. 定義容器物件,將多個Person物件儲存到集合中;
  3. 去除同姓名同年齡的Person物件(重複元素);
  4. 取出集合中的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