集合概述
概念:對(duì)象的容器,實(shí)現(xiàn)了對(duì)對(duì)象的操作。
-
和數(shù)組的區(qū)別
- 1.數(shù)組長度固定,集合長度不固定。
- 2.數(shù)組可以存儲(chǔ)基本類型和引用類型,集合只能存儲(chǔ)引用類型。
位置:
java.util.*;
Collection體系

Collection父接口
- 特點(diǎn):代表任意一組類型的對(duì)象,無序,無下標(biāo),不能重復(fù)。
-
方法:
- boolean add(Object obj) //添加一個(gè)對(duì)象。
- boolean addAll(Object obj) //將一個(gè)集合中的所有對(duì)象添加到此集合中。
- void clear() //清空此集合中的所有對(duì)象。
- boolean contains(Object o) //檢查此對(duì)象是否包含o對(duì)象。
- boolean equals(Object o) //比較此集合是否和指定對(duì)象相等。
- boolean isEmpty() //判斷此集合是否為空。
- boolean remove(Object o) //在此集合中移除o對(duì)象。
- int size() //返回此集合中的元素個(gè)數(shù)。
- Object[] toArray() //將此集合轉(zhuǎn)換成數(shù)組。
接口 Iterator<E>
對(duì)collection進(jìn)行迭代的迭代器。
- 迭代器允許調(diào)用者利用定義良好的語義在迭代期間從迭代器所指向的collection移除元素。
Iterator有三個(gè)方法:
- boolean hasNext()
如果仍有元素可以迭代,則返回 true。(換句話說,如果 next 返回了元素而不是拋出異常,則返回 true)。
返回:如果迭代器具有多個(gè)元素,則返回 true。- E next()
返回迭代的下一個(gè)元素。- void remove()
從迭代器指向的 collection 中移除迭代器返回的最后一個(gè)元素(可選操作)。每次調(diào)用 next 只能調(diào)用一次此方法。如果進(jìn)行迭代時(shí)用調(diào)用此方法之外的其他方式修改了該迭代器所指向的 collection,則迭代器的行為是不確定的。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Collection接口的使用。
* 1.添加元素
* 2.刪除元素
* 3.遍歷元素
* 4.判斷
*/
public class Demo1 {
public static void main(String[] args) {
//創(chuàng)建集合。
Collection collection = new ArrayList();
//1.添加元素。
collection.add("蘋果");
collection.add("西瓜");
collection.add("榴蓮");
System.out.println("元素個(gè)數(shù)"+collection.size());
System.out.println(collection);
//2.刪除元素。
collection.remove("榴蓮");
// collection.clear();清空集合中的對(duì)象。
System.out.println("刪除元素之后"+collection.size());
//3.遍歷元素。(重點(diǎn))
//3.1使用加強(qiáng)for。
//為什么不使用for,因?yàn)镃olletion是沒有下標(biāo)的。
for (Object object:collection
) {
System.out.println(object);
}
//3.2使用迭代器(迭代器專門用來遍歷集合的一種方式)
//hasNext();有沒有下一個(gè)元素。
//next();獲取下一個(gè)元素。
//remove();刪除當(dāng)前元素。
Iterator it = collection.iterator();
while (it.hasNext()){
String s = (String) it.next();
System.out.println(s);
//刪除方法:
//collection.remove(s);會(huì)出現(xiàn)并發(fā)異常。在迭代器中不能使用colection對(duì)對(duì)象進(jìn)行修改。
//it.remove();應(yīng)使用迭代器的方法。
}
//4.判斷。
System.out.println(collection.contains("西瓜"));
System.out.println(collection.isEmpty());
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
*Collection的使用:保存學(xué)生信息
*/
public class Demo2 {
public static void main(String[] args) {
//新建Collection對(duì)象。
Collection collection = new ArrayList();
Student s1 = new Student("張三",20);
Student s2 = new Student("張無忌",18);
Student s3 = new Student("王二",22);
//1.添加數(shù)據(jù)
collection.add(s1);
collection.add(s2);
collection.add(s3);
System.out.println("元素個(gè)數(shù)"+collection.size());
System.out.println(collection.toString());
//2。刪除。
collection.remove(s1);
//collection.clear(); 清除只是把三個(gè)對(duì)象從集合里刪除,刪除的是對(duì)象在集合的地址,三個(gè)對(duì)象沒有消失。三個(gè)對(duì)象還在堆里面。
System.out.println("刪除之后"+collection.size());
//3.遍歷
//3.1使用增強(qiáng)for遍歷
for (Object object:
collection) {
Student s = (Student) object;
System.out.println(s.toString());
}
//3.2使用迭代器:hasNext(); next(); remove(); 迭代過程中不能使用collection的刪除方法。
Iterator it = collection.iterator();
while (it.hasNext()){
Student s = (Student) it.next();
System.out.println(s.toString());
}
//4.判斷
collection.contains(s2);
collection.isEmpty();
System.out.println(collection.contains(s2));
System.out.println(collection.isEmpty());
}
}
/**
* 學(xué)生類
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Collection子接口
List集合
- 特點(diǎn):有序,有下標(biāo),元素可以重復(fù)。
-
方法:
void add(int index,Object o); //在index位置中插入對(duì)象o。boolean addAll(int index,Collection collection); //將一個(gè)集合中的元素添加到此集合中的index位置。Object get(int index); //返回集合中指定元素的位置。List subList(int formIndex,int toIndex); //返回formIndex和toIndex之間的集合元素。
接口 ListIterator<E>
系列表迭代器,允許程序員按任一方向遍歷列表、迭代期間修改列表,并獲得迭代器在列表中的當(dāng)前位置。ListIterator 沒有當(dāng)前元素;它的光標(biāo)位置 始終位于調(diào)用 previous() 所返回的元素和調(diào)用 next() 所返回的元素之間。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/**
* List子接口的使用(一)
* 特點(diǎn):有序,有下標(biāo),元素可以重復(fù)。
*/
public class Deom3 {
public static void main(String[] args) {
//創(chuàng)建集合對(duì)象。
List list = new ArrayList<>();
//1.添加元素。
list.add("蘋果");
list.add("小米");
list.add(0,"華為");
System.out.println(list.size());
System.out.println(list.toString());
//2.刪除元素。
//list.remove("蘋果"); //使用元素進(jìn)行刪除。
//list.remove(0);//使用下標(biāo)進(jìn)行刪除。
System.out.println(list.size());
System.out.println(list.toString());
//3.遍歷。
//3.1使用for遍歷。
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//3.2使用增強(qiáng)for。
for (Object object:
list) {
System.out.println(object);
}
//3.3使用迭代器
Iterator it = list.iterator();
while (it.hasNext()){
Object s = it.next();
System.out.println(s);
}
//3.4使用列表迭代器,listIterator可以雙向遍歷,添加、刪除及修改元素。
ListIterator lit = list.listIterator();
System.out.println("---------3.4使用列表迭代器從前往后---------------");
while (lit.hasNext()){
System.out.println(lit.nextIndex()+":"+lit.next());
}
System.out.println("---------3.4使用列表迭代器從后往前---------------");
//從后往前(此時(shí)“遍歷指針”已經(jīng)指向末尾)
while (lit.hasPrevious()){
System.out.println(lit.previousIndex()+":"+lit.previous());
}
//4.判斷
System.out.println(list.contains("蘋果"));
System.out.println(list.isEmpty());
//5.獲取位置
System.out.println(list.indexOf("華為"));
}
}
import java.util.ArrayList;
import java.util.List;
/**
* List子接口的使用(二)
*/
public class Deom4 {
public static void main(String[] args) {
List list = new ArrayList<>();
//1.添加數(shù)字?jǐn)?shù)據(jù)(自動(dòng)裝箱)
list.add(20);
list.add(30);
list.add(40);
list.add(50);
System.out.println("元素個(gè)數(shù):"+list.size());
System.out.println(list.toString());
//2.刪除元素。
list.remove(0);
//list.remove(20);很明顯數(shù)組下標(biāo)越界,改成如下
//list.remove((Object) 20);
//list.remove(new Integer(20));
System.out.println("元素個(gè)數(shù):"+list.size());
System.out.println(list.toString());
//3-5不在演示,與之前類似
//6.補(bǔ)充方法sublist,返回子集合,含頭不含尾。
List list1 = list.subList(1,3);
System.out.println(list1.size());
System.out.println(list1.toString());
}
}
List實(shí)現(xiàn)類
ArrayList【重點(diǎn)】
- 數(shù)組結(jié)構(gòu)實(shí)現(xiàn),查詢快,增刪慢。
- JDK1.2版本,運(yùn)行效率快,線程不安全。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**
* ArrayList的使用。
* 存儲(chǔ)結(jié)構(gòu):數(shù)組,查詢遍歷快,增刪慢。
*/
public class Deom5 {
public static void main(String[] args) {
//創(chuàng)建一個(gè)集合
ArrayList arrayList = new ArrayList();
//1.添加元素
Student s1 = new Student("劉德華",22);
Student s2 = new Student("郭富城",20);
Student s3 = new Student("梁朝偉",18);
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
System.out.println(arrayList.size());
System.out.println(arrayList);
//2.刪除元素。
arrayList.remove(s1);
//arrayList.remove("劉德華",22);
//注:這樣可以刪除元素嗎?(不可以)顯然這是兩個(gè)不同的對(duì)象。
//假如兩個(gè)對(duì)象屬性相同便認(rèn)為是其同一對(duì)象,那么該如何修改代碼? 我們?cè)赟tudent重寫equals方法。
System.out.println(arrayList.size());
System.out.println(arrayList);
//3.遍歷元素。
//3.1使用迭代器。
Iterator it = arrayList.iterator();
while (it.hasNext()){
Student s = (Student) it.next(); //這里不用轉(zhuǎn)化也可以,直接打印it.next()
System.out.println(s.toString());
}
//3.2使用列表迭代器。
ListIterator lit = arrayList.listIterator();
while (lit.hasNext()){
Student slit = (Student) lit.next();
System.out.println(slit.toString());
}
while (lit.hasPrevious()){
Student slit = (Student) lit.previous();
System.out.println(slit.toString());
}
//4.判斷
System.out.println(arrayList.contains(s1));
System.out.println(arrayList.isEmpty());
//5.查找
System.out.println(arrayList.indexOf(s1));
}
}
注:Object里的equals(this==obj)用地址和當(dāng)前對(duì)象比較,如果想實(shí)現(xiàn)代碼中的問題,可以在學(xué)生類中重寫equals方法:
@Override
public boolean equals(Object obj) {
//1.判斷是不是同一個(gè)對(duì)象。
if (this==obj){
return true;
}
//2.判斷對(duì)象是否為空。
if (obj==null){
return false;
}
//3.判斷對(duì)象是不是Student類型
if (obj instanceof Student){
Student s = (Student) obj;
//4.比較屬性。
if (this.name.equals(s.getName()) && this.age==s.getAge()){
return true;
}
}
//5.不滿足條件返回false。
return false;
}
ArrayList源碼分析
- 默認(rèn)容量大小:
private static final int DEFAULT_CAPACITY = 10; - 存放元素的數(shù)組:
transient Object[] elementData; - 實(shí)際元素個(gè)數(shù):
private int size; - 創(chuàng)建對(duì)象時(shí)調(diào)用無參構(gòu)造函數(shù):
//這是一個(gè)空的數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
這段源碼說明當(dāng)你沒有向集合中添加任何元素時(shí),集合容量為0。那么默認(rèn)的10個(gè)容量怎么來的呢?
這就得看看add()方法的源碼了:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
假設(shè)你new了一個(gè)數(shù)組,當(dāng)前容量為0,size當(dāng)然也為0。這時(shí)調(diào)用add方法進(jìn)入到ensureCapacityInternal(size + 1);該方法源碼如下:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
該方法中的參數(shù)minCapacity傳入的值為size+1也就是 1,接著我們?cè)龠M(jìn)入到calculateCapacity(elementData, minCapacity)里面:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
上文說過,elementData就是存放元素的數(shù)組,當(dāng)前容量為0,if條件成立,返回默認(rèn)容量DEFAULT_CAPACITY也就是10。這個(gè)值作為參數(shù)又傳入ensureExplicitCapacity()方法中,進(jìn)入該方法查看源碼:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
我們先不要管modCount這個(gè)變量。
因?yàn)閑lementData數(shù)組長度為0,所以if條件成立,調(diào)用grow方法,重要的部分來了,我們?cè)俅芜M(jìn)入到grow方法的源碼中:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
這個(gè)方法先聲明了一個(gè)oldCapacity變量將數(shù)組長度賦給它,其值為0;又聲明了一個(gè)newCapacity變量其值為oldCapacity+一個(gè)增量,可以發(fā)現(xiàn)這個(gè)增量是和原數(shù)組長度有關(guān)的量,當(dāng)然在這里也為0。第一個(gè)if條件滿足,newCapacity的值為10(這就是默認(rèn)的容量,不理解的話再看看前面)。第二個(gè)if條件不成立,也可以不用注意,因?yàn)镸AX_ARRAY_SIZE的定義如下:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
這個(gè)值太大了以至于第二個(gè)if條件沒有了解的必要。
最后一句話就是為elementData數(shù)組賦予了新的長度,Arrays.copyOf()方法返回的數(shù)組是新的數(shù)組對(duì)象,原數(shù)組對(duì)象不會(huì)改變,該拷貝不會(huì)影響原來的數(shù)組。copyOf()的第二個(gè)自變量指定要建立的新數(shù)組長度,如果新數(shù)組的長度超過原數(shù)組的長度,則保留數(shù)組默認(rèn)值。
這時(shí)候再回到add的方法中,接著就向下執(zhí)行elementData[size++] = e;到這里為止關(guān)于ArrayList就講解得差不多了,當(dāng)數(shù)組長度為10的時(shí)候你們可以試著過一下源碼,查一下每次的增量是多少(答案是每次擴(kuò)容為原來的1.5倍)。
Vector:
- 數(shù)組結(jié)構(gòu)實(shí)現(xiàn),查詢快,增刪慢。
- JDK1.0版本,運(yùn)行效率慢,線程安全。
import java.util.Vector;
/**
* Vector的演示使用
*/
public class Deon6 {
public static void main(String[] args) {
//創(chuàng)建Vector對(duì)象
Vector vector = new Vector<>();
//1.添加數(shù)據(jù)。
vector.add("石榴");
vector.add("藍(lán)莓");
vector.add("草莓");
System.out.println("元素個(gè)數(shù):"+vector.size());
System.out.println(vector.toString());
//2.刪除數(shù)據(jù)
// vector.remove(0);
// vector.remove("草莓");
//3.遍歷
//使用枚舉器(類似與迭代器)
Enumeration enumeration = vector.elements();
while (enumeration.hasMoreElements()){
String s=(String) enumeration.nextElement();
System.out.println(s);
}
//4.判斷
System.out.println(vector.isEmpty());
System.out.println(vector.contains("草莓"));
//5.Vertor其他方法
vector.firstElement(); //返回此向量的第一個(gè)組件(位于索引 0) 處的項(xiàng))。
vector.lastElement(); //返回此向量的最后一個(gè)組件。
vector.elementAt(0); //返回指定索引處的組件。
}
}
LinkedList
- 鏈表結(jié)構(gòu)實(shí)現(xiàn),增刪快,查詢慢。
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
/**
* LinkedList的用法
* 存儲(chǔ)結(jié)構(gòu):雙向鏈表
*/
public class Demo7 {
public static void main(String[] args) {
LinkedList linkedList=new LinkedList<>();
Student s1 = new Student("劉德華",22);
Student s2 = new Student("郭富城",20);
Student s3 = new Student("梁朝偉",18);
//1.添加元素
linkedList.add(s1);
linkedList.add(s2);
linkedList.add(s3);
// linkedList.add(s3);
System.out.println("元素個(gè)數(shù):"+linkedList.size());
System.out.println(linkedList.toString());
//2.刪除元素
/*
* Student s1 = new Student("劉德華",22);
* System.out.println(linkedList.toString());
*/
//3.遍歷
//3.1 使用for
for(int i=0;i<linkedList.size();++i) {
System.out.println(linkedList.get(i));
}
//3.2 使用增強(qiáng)for
for(Object object:linkedList) {
Student student=(Student) object;
System.out.println(student.toString());
}
//3.3 使用迭代器
Iterator iterator =linkedList.iterator();
while (iterator.hasNext()) {
Student student = (Student) iterator.next();
System.out.println(student.toString());
}
//3.4 使用列表迭代器
ListIterator lit = linkedList.listIterator();
while (lit.hasNext()){
Student student = (Student) lit.next();
System.out.println(student.toString());
}
while (lit.hasPrevious()){
Student student = (Student)lit.previous();
System.out.println(student.toString());
}
//4. 判斷
System.out.println(linkedList.contains(s1));
System.out.println(linkedList.isEmpty());
System.out.println(linkedList.indexOf(s3));
}
}
LinkedList源碼分析
LinkedList首先有三個(gè)屬性:
- 鏈表大小:
transient int size = 0; - (指向)第一個(gè)結(jié)點(diǎn)/頭結(jié)點(diǎn):
transient Node<E> first; - (指向)最后一個(gè)結(jié)點(diǎn)/尾結(jié)點(diǎn):
transient Node<E> last;
關(guān)于Node類型我們?cè)龠M(jìn)入到類里看看:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
首先item存放的是實(shí)際數(shù)據(jù);next指向下一個(gè)結(jié)點(diǎn)而prev指向上一個(gè)結(jié)點(diǎn)。
Node帶參構(gòu)造方法的三個(gè)參數(shù)分別是前一個(gè)結(jié)點(diǎn)、存儲(chǔ)的數(shù)據(jù)、后一個(gè)結(jié)點(diǎn),調(diào)用這個(gè)構(gòu)造方法時(shí)將它們賦值給當(dāng)前對(duì)象。
LinkedList是如何添加元素的呢?先看看add方法:
public boolean add(E e) {
linkLast(e);
return true;
}
進(jìn)入到linkLast方法:
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
假設(shè)剛開始new了一個(gè)LinkedList對(duì)象,first和last屬性都為空,調(diào)用add進(jìn)入到linkLast方法。
首先創(chuàng)建一個(gè)Node變量 l 將last(此時(shí)為空)賦給它,然后new一個(gè)newNode變量存儲(chǔ)數(shù)據(jù),并且它的前驅(qū)指向l,后繼指向null;再把last指向newNode。如下圖所示:

如果滿足if條件,說明這是添加的第一個(gè)結(jié)點(diǎn),將first指向newNode:

至此,LinkedList對(duì)象的第一個(gè)數(shù)據(jù)添加完畢。假設(shè)需要再添加一個(gè)數(shù)據(jù),我們可以再來走一遍,過程同上不再贅述,圖示如下:

ArrayList和LinkedList區(qū)別
- ArrayList:必須開辟連續(xù)空間,查詢快,增刪慢。
-
LinkedList:無需開辟連續(xù)空間,查詢慢,增刪快。
image.png
泛型概述
- Java泛型是JDK1.5中引入的一個(gè)新特性,其本質(zhì)是參數(shù)化類型,把類型作為參數(shù)傳遞。
- 常見形式有泛型類、泛型接口、泛型方法。
- 語法:
- <T,…> T稱為類型占位符,表示一種引用類型。
- 好處:
- 提高代碼的重用性。
- 防止類型轉(zhuǎn)換異常,提高代碼的安全性。
泛型類
/**
* 泛型類
* 語法:類名<T>
* T是類型占位符,表示一種引用類型,編寫多個(gè)使用逗號(hào)隔開。
*/
public class myGeneric<T> {
//1.創(chuàng)建泛型變量
//不能使用new來創(chuàng)建,因?yàn)榉盒褪遣淮_定的類型,也可能擁有私密的構(gòu)造方法。
T t;
//2.泛型作為方法的參數(shù)
public void show(T t){
System.out.println(t);
}
//3.泛型作為方法的返回值
public T getT(){
return t ;
}
}
/**
* 注意:
* 1.泛型只能使用引用類型
* 2.不同泛型類型的對(duì)象不能相互賦值
*/
public class TestGeneric {
public static void main(String[] args) {
//使用泛型類創(chuàng)建對(duì)象。
myGeneric<String> myGeneric1 = new myGeneric<String>();
myGeneric1.t ="劉德華";
myGeneric1.show("劉德華唱歌");
myGeneric1.getT();
myGeneric<Integer> myGeneric2 = new myGeneric<>();
myGeneric2.t = 10 ;
myGeneric2.show(20);
Integer integer = myGeneric2.getT();
}
}
泛型接口
/**
* 泛型接口
* 語法:接口名<T>
* 注意:不能創(chuàng)建泛型靜態(tài)常量
*/
public interface MyInterface<T> {
//創(chuàng)建常量
String nameString="tang";
T server(T t);
}
/**
* 實(shí)現(xiàn)接口時(shí)確定泛型類
*/
public class MyInterfaceImpl implements MyInterface<String>{
@Override
public String server(String t) {
System.out.println(t);
return t;
}
}
//測試
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
//xxx
/**
* 實(shí)現(xiàn)接口時(shí)不確定泛型類
*/
public class MyInterfaceImpl2<T> implements MyInterface<T>{
@Override
public T server(T t) {
System.out.println(t);
return t;
}
}
//測試
MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
myInterfaceImpl2.server(2000);
//2000
泛型方法
/**
* 泛型方法
* 語法:<T> 返回類型
*/
public class MyGenericMethod {
public <T> void show(T t) {
System.out.println("泛型方法"+t);
}
}
//測試
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");
myGenericMethod.show(200);
myGenericMethod.show(3.14);
泛型集合
- 概念:參數(shù)化類型,類型安全的集合,強(qiáng)制集合元素的類型必須一致。
-
特點(diǎn):
- 編譯時(shí)即可檢查,而非運(yùn)行時(shí)拋出異常。
- 訪問時(shí),不必類型轉(zhuǎn)換(拆箱)。
- 不同泛型指尖引用不能相互賦值,泛型不存在多態(tài)。
之前我們?cè)趧?chuàng)建LinkedList類型對(duì)象的時(shí)候并沒有使用泛型,但是進(jìn)到它的源碼中會(huì)發(fā)現(xiàn):
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{//略}
它是一個(gè)泛型類,而我之前使用的時(shí)候并沒有傳遞,說明java語法是允許的,這個(gè)時(shí)候傳遞的類型是Object類,雖然它是所有類的父類,可以存儲(chǔ)任意的類型,但是在遍歷、獲取元素時(shí)需要原來的類型就要進(jìn)行強(qiáng)制轉(zhuǎn)換。這個(gè)時(shí)候就會(huì)出現(xiàn)一些問題,假如往鏈表里存儲(chǔ)了許多不同類型的數(shù)據(jù),在強(qiáng)轉(zhuǎn)的時(shí)候就要判斷每一個(gè)原來的類型,這樣就很容易出現(xiàn)錯(cuò)誤。
集合概述
Set子接口
- 特點(diǎn):無序,無下標(biāo),元素不能重復(fù)
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 測試Set接口的使用。
* 特點(diǎn):(1)無序,沒有下標(biāo) (2)元素不能重復(fù)。
* 方法:全部繼承Collection的全部方法。
public class Demo1 {
public static void main(String[] args) {
//創(chuàng)建集合。
Set<String> set = new HashSet<String>();
//1.添加元素。
set.add("蘋果");
set.add("香蕉");
set.add("西瓜");
System.out.println("元素個(gè)數(shù)"+set.size());
System.out.println(set.toString());
//2.刪除元素
// set.remove("西瓜");
// System.out.println(set.toString());
//3.遍歷(重點(diǎn))
//使用增強(qiáng)for
for (String string:set
) {
System.out.println(string);
}
//使用迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
//4.判斷
System.out.println(set.contains("蘋果"));
System.out.println(set.isEmpty());
}
}
Set實(shí)現(xiàn)類
HashSet【重點(diǎn)】
- 基于HashCode計(jì)算元素存放位置。
- 當(dāng)存放元素的哈希碼相同時(shí),會(huì)調(diào)用equals進(jìn)行確認(rèn),若結(jié)果為true,則拒絕后者存入。
import java.util.HashSet;
import java.util.Iterator;
/**
*HashSet的使用(一)
*存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
*/
public class Demo1 {
public static void main(String[] args) {
//創(chuàng)建集合。
HashSet<String> hashSet = new HashSet();
//1.添加元素。
hashSet.add("劉德華");
hashSet.add("周潤發(fā)");
hashSet.add("林志玲");
hashSet.add("梁朝偉");
System.out.println("元素個(gè)數(shù)"+hashSet.size());
System.out.println(hashSet.toString());
//2.刪除元素
// hashSet.remove("周潤發(fā)");
// System.out.println("元素個(gè)數(shù)"+hashSet.size());
//3.遍歷
//3.1增強(qiáng)for
for (String string: hashSet
) {
System.out.println(string);
}
//3.2使用迭代器
Iterator<String> it = hashSet.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//4.判斷
System.out.println(hashSet.contains("黃飛鴻"));
System.out.println(hashSet.isEmpty());
}
}
/**
* 人類
*/
public class Person {
private String name;
private int age;
public Person(String name,int age) {
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;
}
@Override
public String toString() {
return "Peerson [name=" + name + ", age=" + age + "]";
}
}
/**
* HashSet集合的使用
* 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
* 1.添加元素
* 2.刪除元素
* 3.遍歷
* 4.判斷
*/
public class Demo3 {
public static void main(String[] args) {
HashSet<Person> hashSet=new HashSet<>();
Person p1=new Person("tang",21);
Person p2=new Person("he", 22);
Person p3=new Person("yu", 21);
//1.添加元素
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
//重復(fù),添加失敗
hashSet.add(p3);
//直接new一個(gè)相同屬性的對(duì)象,依然會(huì)被添加,不難理解。
//假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,怎么修改?
hashSet.add(new Person("yu", 21));
System.out.println(hashSet.toString());
//2.刪除元素
hashSet.remove(p2);
//3.遍歷
//3.1 增強(qiáng)for
for (Person person : hashSet) {
System.out.println(person);
}
//3.2 迭代器
Iterator<Person> iterator=hashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//4.判斷
System.out.println(hashSet.isEmpty());
//直接new一個(gè)相同屬性的對(duì)象結(jié)果輸出是false,不難理解。
//注:假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,該怎么做?
System.out.println(hashSet.contains(new Person("tang", 21)));
}
}
注:hashSet存儲(chǔ)過程:
- 根據(jù)hashCode計(jì)算保存的位置,如果位置為空,則直接保存,否則執(zhí)行第二步。
- 執(zhí)行equals方法,如果方法返回true,則認(rèn)為是重復(fù),拒絕存儲(chǔ),否則形成鏈表。
存儲(chǔ)過程實(shí)際上就是重復(fù)依據(jù),要實(shí)現(xiàn)“注”里的問題,可以重寫hashCode和equals代碼:
/**
* 人類
* 重新hashCode和equals方法。
*/
public class Person {
private String name;
private int age;
public Person(String name,int age) {
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;
}
@Override
public String toString() {
return "Peerson [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
hashCode方法里為什么要使用31這個(gè)數(shù)字大概有兩個(gè)原因:
- 31是一個(gè)質(zhì)數(shù),這樣的數(shù)字在計(jì)算時(shí)可以盡量減少散列沖突。
- 可以提高執(zhí)行效率,因?yàn)?1*i=(i<<5)-i,31乘以一個(gè)數(shù)可以轉(zhuǎn)換成移位操作,這樣能快一點(diǎn);但是也有網(wǎng)上一些人對(duì)這兩點(diǎn)提出質(zhì)疑。
TreeSet
- 基于排序序列不重復(fù)。
- 實(shí)現(xiàn)SortedSet接口,對(duì)集合元素進(jìn)行自動(dòng)排序。
- 元素對(duì)象必須實(shí)現(xiàn)Comparable接口,進(jìn)行指定規(guī)則排序。
- 通過CompareTo方法確定是否為重復(fù)元素。
import java.util.TreeSet;
/**
*使用TreeSet保存數(shù)據(jù)。
*存儲(chǔ)結(jié)構(gòu):紅黑樹
* 要求:元素必須實(shí)現(xiàn)Comparable接口,compareTo方法返回值為0,則認(rèn)為元素重復(fù)。
*/
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建集合。
TreeSet<Person> treeSet = new TreeSet<>();
//1.添加元素。
Person p1=new Person("tang",21);
Person p2=new Person("he", 22);
Person p3=new Person("yu", 21);
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
//注:直接添加會(huì)報(bào)類型轉(zhuǎn)換錯(cuò)誤,需要實(shí)現(xiàn)Comparable接口
System.out.println("元素個(gè)數(shù):"+treeSet.size());
System.out.println(treeSet.toString());
//2.刪除元素。
treeSet.remove(p2);
treeSet.remove(new Person("tang",21));
System.out.println(treeSet.toString());
//3.遍歷(略)
//4.判斷
System.out.println(treeSet.contains(p1));
System.out.println(treeSet.isEmpty());
}
}
查看Comparable接口的源碼,發(fā)現(xiàn)只有一個(gè)compareTo抽象方法,在人類中實(shí)現(xiàn)它:
public class Person implements Comparable<Person>{
@Override
//1.先按姓名比
//2.再按年齡比
public int compareTo(Person o) {
int n1=this.getName().compareTo(o.getName());
int n2=this.age-o.getAge();
return n1==0?n2:n1;
}
}
注:
- ? : 是一個(gè)三目運(yùn)算符,問號(hào)前面的表達(dá)式為真則返回'?'和':'中間那個(gè)值即,問號(hào)前面的表達(dá)式為假則返回':'后面那個(gè)值
- 當(dāng)先按姓名比時(shí),先判斷n1是否為0,若為0,則在比較n2是否為0,若不為0,則比較n1.
除了實(shí)現(xiàn)Comparable接口里的比較方法,TreeSet也提供了一個(gè)帶比較器Comparator的構(gòu)造方法,使用匿名內(nèi)部類來實(shí)現(xiàn)它:
import java.util.Comparator;
import java.util.TreeSet;
/**
* TreeSet集合的使用。
* Comparator:實(shí)制定比較()比較器
* Comparable:可比較的。
*/
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建集合,并制定比較器
TreeSet<Person> treeSet1 = new TreeSet<Person>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int n1 =o1.getAge()-o2.getAge();
int n2 =o1.getName().compareTo(o2.getName());
return n1==0?n2:n1;
}
});
Person p1 = new Person("xyz",22);
Person p2 = new Person("hello",20);
Person p3 = new Person("abc",25);
Person p4 = new Person("lisi",22);
treeSet1.add(p1);
treeSet1.add(p2);
treeSet1.add(p3);
treeSet1.add(p4);
System.out.println(treeSet1.toString());
}
}
接下來我們來做一個(gè)小案例:
import java.util.Comparator;
import java.util.TreeSet;
/**
* 需求:使用TreeSet集合實(shí)現(xiàn)字符串按照長度進(jìn)行排序.
* hellowold zhang lisi wangwu beijing xian nanjing
* Comparator實(shí)現(xiàn)接口制定比較。
*/
public class Demo2 {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1 = o1.length() - o2.length();
int n2 = o1.compareTo(o2);
return n1==0?n2:n1;
}
});
treeSet.add("helloworld");
treeSet.add("zhang");
treeSet.add("lisi");
treeSet.add("wangwu");
treeSet.add("beijing");
treeSet.add("xian");
treeSet.add("nanjing");
System.out.println(treeSet.toString());
}
}
Map體系集合
- Map接口的特點(diǎn):
- 用于儲(chǔ)存任意鍵值對(duì)(Key - Value)。
- 鍵:無序,無下標(biāo),不允許重復(fù)(唯一)。
-
值:無序,無下標(biāo),允許重復(fù)。
image.png
接口 Map<K,V>
- 類型參數(shù):
- K - 此映射所維護(hù)的鍵的類型
- V - 映射值的類型
public interface Map<K,V>
將鍵映射到值的對(duì)象。一個(gè)映射不能包含重復(fù)的鍵;每個(gè)鍵最多只能映射到一個(gè)值。
方法摘要:
-
V put(K key,V value)//將指定的值與此映射中的指定鍵關(guān)聯(lián)(可選操作)。 -
Object get(Object key)//返回指定鍵所映射的值;如果此映射不包含該鍵的映射關(guān)系,則返回 null。 -
Set<Map.Entry<K,V>> entrySet()//返回此映射中包含的映射關(guān)系的Set視圖。 -
Collection<V> values()//返回包含所有值的Collection集合。 返回此映射中包含的映射關(guān)系的
接口 Map.Entry<K,V>
public static interface Map.Entry<K,V>
映射項(xiàng)(鍵-值對(duì))。Map.entrySet 方法返回映射的 collection 視圖,其中的元素屬于此類。獲得映射項(xiàng)引用的唯一 方法是通過此 collection 視圖的迭代器來實(shí)現(xiàn)。這些 Map.Entry 對(duì)象僅 在迭代期間有效;更確切地講,如果在迭代器返回項(xiàng)之后修改了底層映射,則某些映射項(xiàng)的行為是不確定的,除了通過 setValue 在映射項(xiàng)上執(zhí)行操作之外。

import java.util.HashMap;
import java.util.Map;
/**
*Map接口的使用
* 特點(diǎn):(1)存儲(chǔ)鍵值對(duì)(2)鍵不能重復(fù),值可以重復(fù)。(3)無序
*/
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建集合
Map<String,String> map = new HashMap<>();
//1.添加元素。
map.put("cn","中國");
map.put("uk","英國");
map.put("usa","美國");
// map.put("cn","zhongguo");//zhongguo 把前面的中國替換掉了
System.out.println("元素個(gè)數(shù):"+map.size());
System.out.println(map.toString());
//2.刪除
// map.remove("cn");
// System.out.println(map.toString());
//3.遍歷
//3.1使用keySet()。
for (String str1 : map.keySet()) {
System.out.println(str1+map.get(str1));
}
//3.2使用entrySet方法。entry是一個(gè)映射項(xiàng)。效率較高.
for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
System.out.println(stringStringEntry.getKey()+stringStringEntry.getValue());
}
//4.判斷
System.out.println(map.containsKey("cn"));
System.out.println(map.containsValue("泰國"));
}
}
Map集合的實(shí)現(xiàn)類
HashMap【重點(diǎn)】
- JDK1.2版本,線程不安全,運(yùn)行效率快;允許用null作為key或是value。
import java.util.HashMap;
import java.util.Map;
/**
*HashMap集合的使用
* 存儲(chǔ)結(jié)構(gòu):哈希表(數(shù)組+鏈表+紅黑樹)
*/
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建集合
HashMap<Student,String> hashMap = new HashMap<>();
//1.添加元素
Student s1 = new Student("huang",1001);
Student s2 =new Student("tang",1002);
Student s3 = new Student("wang",10030);
hashMap.put(s1,"北京");
hashMap.put(s2,"上海");
hashMap.put(s3,"南京");
//添加失敗,但會(huì)更新值
hashMap.put(s1,"南京");
//注:假如相同屬性便認(rèn)為是同一個(gè)對(duì)象,怎么修改?
hashMap.put(new Student("huang",1001),"南京");//此時(shí)元素個(gè)數(shù)是四個(gè),如果不想加進(jìn)來,可以在Student類重新hashcode和equlas方法。
System.out.println(hashMap.size());
System.out.println(hashMap.toString());
//2.刪除元素
// hashMap.remove(s1);
// System.out.println(hashMap.size());
//3.遍歷
//3.1使用keySet遍歷
for (Student student : hashMap.keySet()) {
System.out.println(student+hashMap.get(student));
}
//3.2使用entrySet遍歷
for (Map.Entry<Student, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+entry.getValue());
}
//4.判斷
System.out.println(hashMap.containsKey(s1));
System.out.println(hashMap.containsValue("泰國"));
}
}
public class Student {
private String name;
private int stuNo;
public Student() {
}
public Student(String name, int stuNo) {
this.name = name;
this.stuNo = stuNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", stuNo=" + stuNo +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (stuNo != student.stuNo) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + stuNo;
return result;
}
}
HashMap源碼分析
- 默認(rèn)初始化容量:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 - 數(shù)組最大容量:
static final int MAXIMUM_CAPACITY = 1 << 30; - 默認(rèn)加載因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f; - 鏈表調(diào)整為紅黑樹的鏈表長度閾值(JDK1.8):
static final int TREEIFY_THRESHOLD = 8; - 紅黑樹調(diào)整為鏈表的鏈表長度閾值(JDK1.8):
static final int UNTREEIFY_THRESHOLD = 6;
*鏈表調(diào)整為紅黑樹的數(shù)組最小閾值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64; - HashMap存儲(chǔ)的數(shù)組:
transient Node<K,V>[] table; - HashMap存儲(chǔ)的元素個(gè)數(shù):
transient int size;
默認(rèn)加載因子是什么?
- 就是判斷數(shù)組是否擴(kuò)容的一個(gè)因子。假如數(shù)組容量為100,如果HashMap的存儲(chǔ)元素個(gè)數(shù)超過了100*0.75=75,那么就會(huì)進(jìn)行擴(kuò)容。
鏈表調(diào)整為紅黑樹的鏈表長度閾值是什么?
- 假設(shè)在數(shù)組中下標(biāo)為3的位置已經(jīng)存儲(chǔ)了數(shù)據(jù),當(dāng)新增數(shù)據(jù)時(shí)通過哈希碼得到的存儲(chǔ)位置又是3,那么就會(huì)在該位置形成一個(gè)鏈表,當(dāng)鏈表過長時(shí)就會(huì)轉(zhuǎn)換成紅黑樹以提高執(zhí)行效率,這個(gè)閾值就是鏈表轉(zhuǎn)換成紅黑樹的最短鏈表長度;
紅黑樹調(diào)整為鏈表的鏈表長度閾值是什么?
- 當(dāng)紅黑樹的元素個(gè)數(shù)小于該閾值時(shí)就會(huì)轉(zhuǎn)換成鏈表。
鏈表調(diào)整為紅黑樹的數(shù)組最小閾值是什么?
- 并不是只要鏈表長度大于8就可以轉(zhuǎn)換成紅黑樹,在前者條件成立的情況下,數(shù)組的容量必須大于等于64才會(huì)進(jìn)行轉(zhuǎn)換。
HashMap的數(shù)組table存儲(chǔ)的就是一個(gè)個(gè)的Node<K,V>類型,很清晰地看到有一對(duì)鍵值,還有一個(gè)指向next的指針(以下只截取了部分源碼):
static class Node<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Node<K,V> next;
}
之前的代碼中在new對(duì)象時(shí)調(diào)用的是HashMap的無參構(gòu)造方法,進(jìn)入到該構(gòu)造方法的源碼查看一下:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
發(fā)現(xiàn)沒什么內(nèi)容,只是賦值了一個(gè)默認(rèn)加載因子;而在上文我們觀察到源碼中table和size都沒有賦予初始值,說明剛創(chuàng)建的HashMap對(duì)象沒有分配容量,并不擁有默認(rèn)的16個(gè)空間大小,這樣做的目的是為了節(jié)約空間,此時(shí)table為null,size為0。
當(dāng)我們往對(duì)象里添加元素時(shí)調(diào)用put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法把key和value傳給了putVal,同時(shí)還傳入了一個(gè)hash(Key)所返回的值,這是一個(gè)產(chǎn)生哈希值的方法,再進(jìn)入到putVal方法(部分源碼):
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else{
//略
}
}
這里面創(chuàng)建了一個(gè)tab數(shù)組和一個(gè)Node變量p,第一個(gè)if實(shí)際是判斷table是否為空,而我們現(xiàn)在只關(guān)注剛創(chuàng)建HashMap對(duì)象時(shí)的狀態(tài),此時(shí)tab和table都為空,滿足條件,執(zhí)行內(nèi)部代碼,這條代碼其實(shí)就是把resize()所返回的結(jié)果賦給tab,n就是tab的長度,resize顧名思義就是重新調(diào)整大小。查看resize()源碼(部分):
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
if (oldCap > 0);
else if (oldThr > 0);
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
return newTab;
}
該方法首先把table及其長度賦值給oldTab和oldCap;threshold是閾值的意思,此時(shí)為0,所以前兩個(gè)if先不管,最后else里newCap的值為默認(rèn)初始化容量16;往下創(chuàng)建了一個(gè)newCap大小的數(shù)組并將其賦給了table,剛創(chuàng)建的HashMap對(duì)象就在這里獲得了初始容量。然后我們?cè)倩氐絧utVal方法,第二個(gè)if就是根據(jù)哈希碼得到的tab中的一個(gè)位置是否為空,為空便直接添加元素,此時(shí)數(shù)組中無元素所以直接添加。至此HashMap對(duì)象就完成了第一個(gè)元素的添加。當(dāng)添加的元素超過16*0.75=12時(shí),就會(huì)進(jìn)行擴(kuò)容:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
if (++size > threshold)
resize();
}
擴(kuò)容的代碼如下(部分):
final Node<K,V>[] resize() {
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int newCap;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {//略}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
}
}
核心部分是else if里的移位操作,也就是說每次擴(kuò)容都是原來大小的兩倍。
注:額外說明的一點(diǎn)是在JDK1.8以前鏈表是頭插入,JDK1.8以后鏈表是尾插入。
HashSet源碼分析
了解完HashMap之后,再回過頭來看之前的HashSet源碼,為什么放在后面寫你們看一下源碼就知道了(部分):
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
}
可以看見HashSet的存儲(chǔ)結(jié)構(gòu)就是HashMap,那它的存儲(chǔ)方式是怎樣的呢?可以看一下add方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
很明了地發(fā)現(xiàn)它的add方法調(diào)用的就是map的put方法,把元素作為map的key傳進(jìn)去的。。
Hashtable
- JDK1.0版本,線程安全,運(yùn)行效率慢;不允許null作為key或是value。
- 初始容量11,加載因子0.75。
這個(gè)集合在開發(fā)過程中已經(jīng)不用了,稍微了解即可。
Properties
- Hashtable的子類,要求key和value都是String。通常用于配置文件的讀取。
它繼承了Hashtable的方法,與流關(guān)系密切,此處不詳解。
TreeMap
- 實(shí)現(xiàn)了SortedMap接口(是Map的子接口),可以對(duì)key自動(dòng)排序。
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
* TreeMap的使用
* 存儲(chǔ)結(jié)構(gòu):紅黑樹,
*/
public class Demo2 {
public static void main(String[] args) {
//創(chuàng)建集合
TreeMap<Student,Integer> treeMap = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int n1 = o1.getName().compareTo(o2.getName());
int n2 = o1.getStuNo()- o2.getStuNo();
return n1==0?n2:n1;
}
});
//1.創(chuàng)建元素
Student s1=new Student("tang", 36);
Student s2=new Student("yu", 101);
Student s3=new Student("he", 10);
treeMap.put(s1, 21);
treeMap.put(s2, 22);
treeMap.put(s3, 21);
System.out.println(treeMap.toString());
//2.刪除
// treeMap.remove(s1);
// treeMap.remove(s2,22);
// System.out.println(treeMap.toString());
//3.遍歷
//使用keySet()遍歷
for (Student student : treeMap.keySet()) {
System.out.println(student +"-------"+ treeMap.get(student));//
}
//使用entrySet()遍歷
for (Map.Entry<Student, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey()+""+entry.getValue());
}
//4.判斷
System.out.println(treeMap.containsKey(new Student("tang", 36)));
}
TreeSet源碼
和HashSet類似,放在TreeMap之后講便一目了然(部分):
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
}
TreeSet的存儲(chǔ)結(jié)構(gòu)實(shí)際上就是TreeMap,再來看其存儲(chǔ)方式:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
它的add方法調(diào)用的就是TreeMap的put方法,將元素作為key傳入到存儲(chǔ)結(jié)構(gòu)中。
Collections工具類
- 概念:集合工具類,定義了除了存取以外的集合常用方法。
- 方法:
-
public static void reverse(List<?> list)//反轉(zhuǎn)集合中元素的順序 -
public static void shuffle(List<?> list)//隨機(jī)重置集合元素的順序 -
public static void sort(List<T> list)//升序排序(元素類型必須實(shí)現(xiàn)Comparable接口)
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 實(shí)現(xiàn)Collections工具類的使用。
*
*/
public class Demo2 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(20);
list.add(5);
list.add(12);
list.add(30);
list.add(6);
//sort排序
System.out.println("排序之前:"+list.toString());
Collections.sort(list);
System.out.println("排序之后:"+list.toString());
//binarySearch(二分查找) 二分查找前提:要查找的序列必須是有序的。
int i = Collections.binarySearch(list,12);
System.out.println(i); //如果查找成功,i是>=0,如果查找失敗,i返回負(fù)數(shù)。
//copy復(fù)制
List<Integer> list2 = new ArrayList<>();//該方法要求目標(biāo)元素容量大于等于源目標(biāo),不然會(huì)報(bào)錯(cuò)。
for (int j = 0; j < list.size(); j++) {
list2.add(j);
}
Collections.copy(list2,list);
System.out.println(list2);
//reverse(反轉(zhuǎn))
Collections.reverse(list);
System.out.println(list);
//shuffle(打亂)
Collections.shuffle(list);
System.out.println(list);
//補(bǔ)充:list轉(zhuǎn)成數(shù)組
Integer[] arr=list.toArray(new Integer[0]);
System.out.println(arr.length);
System.out.println(Arrays.toString(arr));
//補(bǔ)充:數(shù)組轉(zhuǎn)成集合
String[] nameStrings= {"tang","he","yu"};
//受限集合,不能添加和刪除
List<String> list3= Arrays.asList(nameStrings);
System.out.println(list3);
//注:基本類型轉(zhuǎn)成集合時(shí)需要修改為包裝類
}
}

