自定義泛型
1.1、泛型的定義介紹
在集合中,不管是接口還是類,它們在定義的時(shí)候類或接口名的后面都使用<標(biāo)識符>,當(dāng)我們在使用的時(shí)候,可以指定其中的類型。如果當(dāng)前的類或接口中并沒有<標(biāo)識符>,我們在使用的時(shí)候也不能去指定類型。
舉例:比如我們之前所學(xué)習(xí)的集合ArrayList類:

說明:在ArrayList類上有一個(gè)泛型的參數(shù):E
設(shè)計(jì)API的時(shí)候,設(shè)計(jì)者并不知道我們要用ArrayList存儲哪種數(shù)據(jù)類型,所以他定義了一個(gè)泛型。
然后當(dāng)我們使用ArrayList的時(shí)候,我們知道要存儲的類型,比如要存儲String類型:

當(dāng)我們new對象的時(shí)候,就把泛型E替換成了String,于是JVM就知道我們要存儲的其實(shí)是String。
泛型:如果我們不確定數(shù)據(jù)的類型,可以用一個(gè)標(biāo)識符來代替。
如果我們在使用的時(shí)候已經(jīng)確定要使用的數(shù)據(jù)類型了,我們在創(chuàng)建對象的時(shí)候可以指定使用的數(shù)據(jù)類型。
泛型自定義格式:
<標(biāo)識符>
這里的標(biāo)識符可以是任意的字母、數(shù)字、下劃線和 $ 。但是這里一般規(guī)范使用單個(gè)大寫字母。
注意:自定義泛型也屬于標(biāo)識符,滿足標(biāo)識符的命名規(guī)則。1)數(shù)字不能開始;2)關(guān)鍵字不能作為標(biāo)識符;
根據(jù)以上分析我們可以思考一個(gè)問題:既然我們學(xué)習(xí)過的集合類可以定義泛型,那么我們自己在描述類或接口的時(shí)候,是否也可以在自己的類或接口上定義泛型,然后別人使用的時(shí)候也可以指定這個(gè)類型呢?
答案當(dāng)然是可以的。
1.2、自定義泛型類(掌握)
泛型類:
在定義類的時(shí)候,在類名的后面書寫泛型。
格式:
class 類名<泛型參數(shù)>
{
}
泛型參數(shù)其實(shí)就是標(biāo)識符。
分析和步驟:
1)創(chuàng)建測試類GenericDemo1 ,在這個(gè)類中定義一個(gè)main函數(shù);
2)定義一個(gè)泛型類Demo<ABC>;
3)在這個(gè)類中定義一個(gè)成員變量name,類型是泛型ABC類型;
4)在定義一個(gè)非靜態(tài)成員函數(shù)show,接收參數(shù)給name屬性賦值,局部變量name的類型是ABC類型;
5)在main函數(shù)中創(chuàng)建Demo類的對象,并指定泛型分別是String 和Integer類型;
package cn.xuexi.generic.demo1;
/*
* 自定義泛型類演示
* 在類上定義的泛型,當(dāng)外界創(chuàng)建這個(gè)對象的時(shí)候,由創(chuàng)建者指定泛型的類型
* 在類上定義的泛型,類中的成員變量和函數(shù)都可以使用
*/
class Demo<ABC>
{
ABC name;
public void show(ABC name)
{
this.name=name;
}
}
public class GenericDemo1 {
public static void main(String[] args) {
//創(chuàng)建Demo類的對象
/*
* 注意如果這里創(chuàng)建Demo類的對象時(shí)沒有指定泛型類的類型時(shí),這里ABC默認(rèn)是Object類型
* 如下代碼所示創(chuàng)建Demo類的對象的時(shí)候,指定的泛型類類型是String類型,
* 那么Demo類中的泛型類ABC就是String類
* 同理,如果指定的數(shù)據(jù)類型是Integer,那么ABC就是Integer類
*/
//Demo<String> d=new Demo<String>();
Demo<Integer> d=new Demo<Integer>();
//d.show("哈哈");
d.show(123);
System.out.println(d.name);
}
}
說明:
1)在類上定義的泛型,當(dāng)外界在創(chuàng)建這個(gè)類的對象的時(shí)候,需要?jiǎng)?chuàng)建者自己來明確當(dāng)前泛型的具體類型;
2)在類上定義的泛型,在類中的方法上和成員變量是可以使用的;
3)上述代碼中如果創(chuàng)建Demo類的對象時(shí)沒有指定泛型類的類型時(shí),那么ABC默認(rèn)是Object類型;
4)上述代碼中如果創(chuàng)建Demo類的對象時(shí)指定的泛型類類型是String類型,那么ABC默認(rèn)是String類型;
自定義泛型類的練習(xí):
需求:使用自定義泛型模擬隊(duì)列數(shù)據(jù)結(jié)構(gòu)。
分析:
由于隊(duì)列是集合中的一種,可以存儲任意類型的數(shù)據(jù),而對于隊(duì)列類我們不確定具體存儲什么數(shù)據(jù)類型,所以可以在隊(duì)列類QueueDemo中指定泛型,根據(jù)創(chuàng)建者來決定什么數(shù)據(jù)類型。
步驟:
1)自定義一個(gè)類QueueDemo<E>,在這個(gè)類中創(chuàng)建集合類LinkedList<E>的對象list;
2)在QueueDemo類中定義一個(gè)添加數(shù)據(jù)的函數(shù)addElement(E e),在這個(gè)函數(shù)中使用對象list調(diào)用LinkedList集合類中addLast(e)函數(shù)向隊(duì)列中添加數(shù)據(jù);
3)定義一個(gè)函數(shù)getElement用來獲得數(shù)據(jù),返回值類型是E,并使用list對象調(diào)用LinkedList集合類中的removeFirst()函數(shù);
4)在定義一個(gè)函數(shù)isNull()判斷隊(duì)列中是否還含有數(shù)據(jù),返回值類型是布爾類型;
5)創(chuàng)建一個(gè)測試類GenericDemo,在這個(gè)類中創(chuàng)建隊(duì)列類QueueDemo的對象qd,并在泛型位置指定數(shù)據(jù)類型String;
6)使用對象qd調(diào)用添加數(shù)據(jù)的函數(shù)addElement(),向隊(duì)列中添加幾個(gè)數(shù)據(jù);
7)使用while循環(huán)遍歷集合并取出;
自定義的隊(duì)列類:
package cn.xuexi.generic.demo1;
import java.util.LinkedList;
/*
* 模擬隊(duì)列
* 由于隊(duì)列是集合中的一種,可以存儲任意類型的數(shù)據(jù),而對于隊(duì)列類我們不確定具體存儲什么
* 數(shù)據(jù)類型,所以可以在隊(duì)列類QueueDemo中指定泛型,根據(jù)創(chuàng)建者來決定什么類型
*/
public class QueueDemo<E> {
//創(chuàng)建LinkedList集合的對象
LinkedList<E> list=new LinkedList<E>();
//自定義函數(shù)向隊(duì)列中添加數(shù)據(jù)
public void addElement(E e)
{
list.addLast(e);
}
//自定義函數(shù)取出隊(duì)列中的數(shù)據(jù)
public E getElement()
{
return list.removeFirst();
}
//自定義函數(shù)判斷隊(duì)列中是否還含有數(shù)據(jù)
public boolean isNull()
{
/*
* list.isEmpty() 表示集合中沒有數(shù)據(jù)返回true 有數(shù)據(jù)返回false
* 我們這里這么書寫:!list.isEmpty() 表示集合中有數(shù)據(jù)返回true 沒有數(shù)據(jù)返回false
*/
return !list.isEmpty();
}
}
定義測試類:
package cn.xuexi.generic.demo1;
/*
* 測試模擬隊(duì)列的類
*/
public class GenericDemo2 {
public static void main(String[] args) {
//創(chuàng)建隊(duì)列類的對象,并指定數(shù)據(jù)類型
QueueDemo<String> qd = new QueueDemo<String>();
//使用對象調(diào)用函數(shù)向隊(duì)列中添加數(shù)據(jù)
qd.addElement("abc");
qd.addElement("hello");
qd.addElement("java");
//取出數(shù)據(jù)
while(qd.isNull())
{
//表示有數(shù)據(jù)
String s = qd.getElement();
System.out.println(s);
}
}
}

注意:對于自定義泛型類只有在創(chuàng)建這個(gè)類的對象的時(shí)候才可以指定泛型的類型。
1.3、在函數(shù)上使用泛型(掌握)
我們不僅可以在自己定義的類或接口上使用泛型,還可以在自定義的函數(shù)上使用泛型。
雖然可以在類上定義泛型,但是有時(shí)類中的方法需要接收的數(shù)據(jù)類型和類上外界指定的類型不一致。也就是說對于某個(gè)函數(shù)而言參數(shù)的數(shù)據(jù)類型和所屬類的泛型類型不一致了,這時(shí)我們可以在這個(gè)類中的這個(gè)方法上單獨(dú)給這個(gè)方法設(shè)定泛型。
在函數(shù)上使用泛型的格式:
函數(shù)修飾符 <泛型名> 函數(shù)返回值類型 方法名( 泛型名 變量名 )
{
函數(shù)體;
}
說明:函數(shù)返回值類型前面的<泛型名>相當(dāng)于定義了方法參數(shù)列表中泛型的類型。
代碼演示如下圖所示:

說明:
1)類上的泛型是在創(chuàng)建這個(gè)類的對象的時(shí)候明確具體是什么類型;
2)方法上的泛型是在真正調(diào)用這個(gè)方法時(shí),傳遞的是什么類型,泛型就會自動(dòng)變成什么類型;
舉例:上述代碼中當(dāng)調(diào)用者傳遞的是2,那么這個(gè)Q就代表Integer類型。
如果調(diào)用者傳遞的是new Student(“班長”,19),那么這個(gè)Q類型就是Student類型。
3)上述method函數(shù)中<Q>表示定義的泛型,而參數(shù)列表中的Q q是在使用泛型類型,而這里的Q類型具體由調(diào)用者來指定;
自定義泛型的練習(xí):
需求:自定義泛型方法,定義一個(gè)方法,可以打印任意一個(gè)數(shù)組的內(nèi)容。
打印String數(shù)組
打印Integer數(shù)組
分析和步驟:
1)分別定義一個(gè)String類型和Integer類型的數(shù)組,并分別存入數(shù)據(jù);
2)定義兩個(gè)函數(shù)都是printArray,但是參數(shù)列表不同,一個(gè)接收String類型的數(shù)組,另一個(gè)接收是Integer類型的數(shù)組;
3)分別遍歷兩個(gè)數(shù)組;
4)雖然上述做法可以,但是只要遍歷不同類型的數(shù)組就得定義一個(gè)重載的函數(shù),麻煩,我們可以使用在函數(shù)上自定義泛型<T>,函數(shù)的參數(shù)也是T[]類型的數(shù)組;
package cn.xuexi.generic.demo1;
/*
* 需求:自定義泛型方法,定義一個(gè)方法,可以打印任意一個(gè)數(shù)組的內(nèi)容。
打印String數(shù)組
打印Integer數(shù)組
*/
public class GenericDemo4 {
public static void main(String[] args) {
//定義數(shù)組
String[] arr={"hello","world","java"};
Integer[] arr2={10,20,30};
//調(diào)用自定義函數(shù)分別打印數(shù)組
/*printArray(arr);
printArray(arr2);*/
printArray2(arr);//傳遞String[]類型的數(shù)組的時(shí)候,T就變成了String類型
printArray2(arr2);//傳遞Integer[]類型的數(shù)組的時(shí)候,T就變成了Integer類型
/*
* 上述做法可以定義兩個(gè)函數(shù)來遍歷不同數(shù)據(jù)類型的數(shù)組,這兩個(gè)函數(shù)以函數(shù)重載的形式存在,但是相對
* 來說比較麻煩,如果定義不同類型的數(shù)組,都會定義一個(gè)相對應(yīng)的函數(shù),太麻煩,這里我們可以借助
* 泛型方法來簡化代碼開發(fā)
*/
}
/*
* 由于要打印不同類型的數(shù)組,所以這里參數(shù)列表的類型不確定了,既然參數(shù)列表類型不確定了,我們可以使用
* 泛型 在函數(shù)返回值類型前面還得定義相應(yīng)的泛型
*/
public static <T> void printArray2(T[] arr)
{
/*//使用for循環(huán)遍歷數(shù)組
for (int i = 0; i < arr.length; i++)
{
System.out.print(arr[i]+" ");
}*/
for (T t : arr) {
System.out.print(t+" ");
}
//換行
System.out.println("");
}
/*//遍歷整數(shù)
public static void printArray(Integer[] arr2) {
//使用for循環(huán)遍歷數(shù)組
for (int i = 0; i < arr2.length; i++)
{
System.out.print(arr2[i]+" ");
}
}
//遍歷字符串
public static void printArray(String[] arr) {
//使用for循環(huán)遍歷數(shù)組
for (int i = 0; i < arr.length; i++)
{
System.out.print(arr[i]+" ");
}
//換行
System.out.println("");
}*/
}
說明:因?yàn)閰?shù)中的數(shù)組類型不確定,遍歷時(shí)候的數(shù)據(jù)類型也不確定,所以都用泛型T來代替。
總結(jié):
1)自定義泛型方法格式:
** 修飾符 <泛型> 返回值 函數(shù)名(參數(shù)列表 ){}**
2)自定義泛型方法,泛型必須在函數(shù)返回值之前和函數(shù)修飾之后定義,否則報(bào)錯(cuò);
3)泛型方法中泛型的確定:在方法被調(diào)用的時(shí)候,傳遞參數(shù)的時(shí)候才確定;
注意事項(xiàng):
靜態(tài)函數(shù)上的泛型:
靜態(tài)函數(shù)不能使用類上定義的泛型。
因?yàn)殪o態(tài)方法的運(yùn)行是不需要對象的,而類上的泛型必須在創(chuàng)建這個(gè)類對象的時(shí)候才能明確具體是什么類型。
而靜態(tài)函數(shù)運(yùn)行的時(shí)候是沒有對象的,也就是說類上的泛型在靜態(tài)函數(shù)運(yùn)行的時(shí)候還不知道是什么類型。
如果一個(gè)靜態(tài)方法,也需要泛型,請使用自定義泛型方法。
靜態(tài)函數(shù)的使用注意事項(xiàng)代碼演示如下:

1.4、泛型接口和泛型傳遞(掌握)
通過查閱API得知,類支持泛型,那么接口也可以支持泛型,比如集合中的接口:
接口Collection<E>
接口List<E>
那么既然API中的接口支持泛型,自己定義的接口同樣也可以使用泛型。
泛型接口的格式:
修飾符 interface 接口名<泛型>{}
問題:泛型類的泛型,在創(chuàng)建類的對象時(shí)確定。
那么接口又無法創(chuàng)建對象,什么時(shí)候確定泛型的類型呢?有兩種方式可以實(shí)現(xiàn)。
方式1:類實(shí)現(xiàn)接口的時(shí)候,直接明確泛型類型。

源代碼中的String類的的實(shí)現(xiàn):

方式2:類實(shí)現(xiàn)接口的時(shí)候,還不確定數(shù)據(jù)類型,這時(shí)可以在實(shí)現(xiàn)類上隨便先定義個(gè)泛型,當(dāng)這個(gè)類被創(chuàng)建對象的時(shí)候,
就明確了類上的泛型,于是接口上的泛型也明確了。
我們管方式2這種方式叫做泛型的傳遞。
代碼實(shí)現(xiàn)如下:

舉例,API中集合的泛型使用情況解釋如下所示:
比如:
interface Collection<E>{
}
interface List<E> extends Collection<E>{
}
class ArrayList<E> implements List<E>{
}
ArrayList<String> list = new ArrayList<String>();
結(jié)論:通過以上操作,上述集合接口中的泛型類型是String類型。
1.5、泛型通配符(掌握泛型中的通配符的使用)
需求:定義功能,打印集合中的對象。
分析和步驟:
1)定義一個(gè)類GenericDemo6,在這個(gè)類中的主函數(shù)中創(chuàng)建ArrayList<String>集合的對象list;
2)使用對象list調(diào)用add()函數(shù)向集合中添加幾個(gè)字符串?dāng)?shù)據(jù);
3)再創(chuàng)建一個(gè)HashSet<Integer>集合類的對象set;
4)使用對象set調(diào)用add()函數(shù)向集合中添加幾個(gè)整數(shù)數(shù)據(jù);
5)分別定義兩個(gè)函數(shù)來對上述的集合進(jìn)行遍歷;

上述定義了2個(gè)打印集合的方法,方法體中都是在使用迭代器遍歷集合。區(qū)別是方法上接收的集合類型不同,還有集合中保存的元素的數(shù)據(jù)類型不同。這樣使用迭代器遍歷的時(shí)候,迭代器的泛型需要根據(jù)集合中保存元素的具體的數(shù)據(jù)類型才能確定,其他的代碼書寫都相同。這樣會導(dǎo)致上述代碼重復(fù)冗余,可以對上述代碼進(jìn)行進(jìn)一步的優(yōu)化。
換句話說:通過觀察發(fā)現(xiàn)上述的兩個(gè)函數(shù)以函數(shù)重載的形式存在,幾乎一模一樣,還有泛型的數(shù)據(jù)類型不一致,還有集合的類型不一致,一個(gè)是ArrayList,另一個(gè)是HashSet,所以代碼冗余,我們可以簡化代碼的開發(fā),我們可以只定義一個(gè)函數(shù)來遍歷集合。既然兩個(gè)集合不一樣,我們這里可以書寫集合的父類或者父接口Collection。由于集合中保存的數(shù)據(jù)類型也不一致,這里也沒法寫,寫String 不行 Integer 也不可以。
說明:這里書寫Object不可以,因?yàn)榉盒透袷揭髢蓚?cè)必須類型一致,這里不一致了,會報(bào)錯(cuò)
Collection<Object> coll=new ArrayList<String>()
所以這里我們只能借助泛型中通配符可以實(shí)現(xiàn) : ?
? 表示泛型的通配符,表示集合中的任意數(shù)據(jù)類型,傳遞過來的表示什么數(shù)據(jù)類型,?就表示什么數(shù)據(jù)類型
分析:
現(xiàn)在需要定義一個(gè)方法既可以接收ArrayList,又可以接收HashSet集合容器對象,只能使用它們的父類或接口類型來接收,這樣就可以使用Collection接口類型接收。
在寫方法的時(shí)候,如果要接收別人的數(shù)據(jù),但是使用泛型不知道具體的數(shù)據(jù)類型,可以使用泛型的通配符 ? 來表示。
? :表示的是任意的數(shù)據(jù)類型。當(dāng)真正傳遞過來的集合中的數(shù)據(jù)是什么類型,這個(gè)?就會自動(dòng)的變成什么類型。

完整代碼如下所示:
package cn.xuexi.generic.demo1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/*
* 需求:定義功能,打印集合中的對象。
*/
public class GenericDemo6 {
public static void main(String[] args) {
//創(chuàng)建ArrayList集合對象
ArrayList<String> list=new ArrayList<String>();
//向集合中添加數(shù)據(jù)
list.add("aaa");
list.add("ccc");
list.add("bbb");
list.add("ghfas");
//創(chuàng)建HashSet集合
HashSet<Integer> set=new HashSet<Integer>();
//向集合中添加數(shù)據(jù)
set.add(123);
set.add(567);
set.add(7843);
set.add(45);
//調(diào)用自定義函數(shù)分別遍歷上述兩個(gè)集合
printCollection(list);
printCollection(set);
}
/*
* 通過觀察發(fā)現(xiàn)以下的兩個(gè)函數(shù)以函數(shù)重載的形式存在,幾乎一模一樣,還有泛型的數(shù)據(jù)類型不一致,
* 還有集合的類型不一致,一個(gè)是ArrayList,另一個(gè)是HashSet,
* 所以代碼冗余,我們可以簡化代碼的開發(fā),我們可以只定義一個(gè)函數(shù)來遍歷集合
* 既然兩個(gè)集合不一樣,我們這里可以書寫集合的父類或者父接口Collection
* 由于集合中保存的數(shù)據(jù)類型也不一致,這里也沒法寫,寫String 不行 Integer 也不可以
* 說明:這里書寫Object不可以,因?yàn)榉盒透袷揭髢蓚?cè)必須類型一致,這里不一致了,會報(bào)錯(cuò)
* Collection<Object> coll=new ArrayList<String>()
* 所以這里我們只能借助泛型中通配符可以實(shí)現(xiàn) : ?
* ? 表示泛型的通配符,表示集合中的任意數(shù)據(jù)類型,傳遞過來的表示什么數(shù)據(jù)類型,?就表示什么數(shù)據(jù)類型
*/
public static void printCollection(Collection<?> coll)
{
//迭代集合
for (Iterator<?> it = coll.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
/*//自定義函數(shù)遍歷ArrayList函數(shù)
public static void printCollection(ArrayList<String> list) {
//迭代集合
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String str = it.next();
System.out.println(str);
}
}
//自定義函數(shù)遍歷HashSet函數(shù)
public static void printCollection(HashSet<Integer> set) {
for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
Integer i= it.next();
System.out.println(i);
}
}*/
}
注意:泛型的通配符雖然可以簡化代碼的開發(fā),但是在開發(fā)中要慎用,因?yàn)槿绻褂猛ㄅ浞敲春瘮?shù)就可以接收任意的數(shù)據(jù)類型,這樣會導(dǎo)致代碼的不安全。
有的人會有疑問,我就是一個(gè)迭代遍歷有什么不安全的,注意啦,我們在開發(fā)中可不是只有遍歷這么簡單,對接收的數(shù)據(jù)有可能進(jìn)行其他的相關(guān)操作(增刪改),結(jié)果就會導(dǎo)致對任何數(shù)據(jù)都可以操作,這樣才會導(dǎo)致不安全。所以我們?nèi)绻胧褂梅盒偷耐ㄅ浞敲幢仨毜檬褂猛ㄅ浞南薅?。目的是考慮數(shù)據(jù)的安全性。
1.6、泛型限定(了解,能看懂即可)
泛型的限定:包括上限限定和下限限定。
上限限定:在定義泛型的通配符的時(shí)候,如果需要對這個(gè)?號接收的類型進(jìn)行限定,并且只知道具體的父類是什么,而所有的子類都需要接收,這時(shí)就使用泛型的上限限定。
上限限定的格式:
? extends 父類類型或父接口類型
例如:? extends Person :?代表的是一種類型,當(dāng)前這個(gè)類型可以是Person本身,也可以是Person的子類。
下限限定:在定義泛型的通配符的時(shí)候,如果需要對這個(gè)?號接收的類型進(jìn)行限定,并且只知道當(dāng)前的某個(gè)子類,
而泛型需要接收的是當(dāng)前的這個(gè)子類,或者子類的父類類型??梢允褂梅盒偷南孪尴薅?br>
格式:? super 子類類型或?qū)崿F(xiàn)類類型
例如: ? super Student :?代表當(dāng)前的類型可以是Student類型,也可以是Student的父類類型。
但不能是Student 的子類,或者Student的兄弟。
2、Map集合
2.1、Map集合介紹
2.1.1 為什么要學(xué)習(xí)Map集合
Java中集合的關(guān)系圖如下圖所示:

說明:Collection接口下的所有集合中保存的對象都是孤立的。對象和對象之間并沒有任何關(guān)系存在。
在生活中對象和對象之間必然會一定的聯(lián)系存在。而我們學(xué)習(xí)的Collection接口下的所有子接口或者實(shí)現(xiàn)類(集合)它們中的確可以保存對象,但是沒有辦法維護(hù)這些對象之間的關(guān)系。
而學(xué)習(xí)的Map集合,它也是存放對象的,但是可以對其中的對象進(jìn)行關(guān)系的維護(hù)。
Map引入的圖解:
需求:定義一個(gè)集合存儲班級學(xué)生的學(xué)生姓名。

補(bǔ)充:把Collection集合稱為單列集合。Map被稱為雙列集合。
2.1.2 Map集合的特點(diǎn)

特點(diǎn):
1)Map集合可以一次性存儲兩個(gè)對象;
2)在Map集合中保存的key和value這樣的具備一定對應(yīng)關(guān)系的一組(一對)數(shù)據(jù),Map集合中存儲的是兩個(gè)對象的對應(yīng)關(guān)系(映射關(guān)系)。[ key-value映射關(guān)系 ];
3)Map集合中的key必須保證唯一(存儲的key元素不能重復(fù),value可以重復(fù),但是一個(gè)key只能對應(yīng)一個(gè)value);
4)Map集合中的key和value屬于一一對應(yīng)關(guān)系(一個(gè)key只能對應(yīng)一個(gè)value)

2.1.3 Map和Collection的區(qū)別
區(qū)別:
1)Map中鍵唯一,值沒有要求。Collection中只有Set體系要求元素唯一;
2)Map的數(shù)據(jù)結(jié)構(gòu)針對鍵而言,Collection的數(shù)據(jù)結(jié)構(gòu)針對元素而言;
3)Map是雙列集合頂級接口,Collection是單列集合頂級接口,他們兩個(gè)分別是兩種集合的頂級接口,之間沒有必然的聯(lián)系;

說明:
Collection集合中只能存儲一個(gè)對象元素,稱為單列集合。
Map集合中其實(shí)存儲的是兩個(gè)單列集合,稱為雙列集合。
2.2、Map集合中的方法(掌握)
1)增加元素:

在使用put存儲一對元素(key-value)對象時(shí),會先拿key去判斷Map集合中是否已經(jīng)存在。
如果Map集合中沒有相同的key存在:就把key-value存儲到Map集合中,并返回null值。
如果Map集合中有相同的key存在:會把之前存儲的value對象覆蓋。并返回之前的value對象(舊value對象)。
這里可以理解為修改value,但是不能修改key。
注意:由于Map是接口,不能創(chuàng)建對象,只能使用Map下面的子類HashMap或者TreeMap創(chuàng)建對象。
分析和步驟:
1)創(chuàng)建集合HashMap<String,String>集合對象map,返回值類型是Map<String,String>;
2)使用map對象調(diào)用put函數(shù)向集合中添加學(xué)生的學(xué)號作為key,姓名作為value;
3)輸出對象map;
package cn.xueximap.function.demo;
import java.util.HashMap;
import java.util.Map;
/*
* Map中的put函數(shù)的演示
* V put(K key, V value)
將指定的值與此映射中的指定鍵關(guān)聯(lián)(可選操作)。
*/
public class MapPutDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//給集合中添加數(shù)據(jù)
map.put("001","張三");
map.put("002","李四");
map.put("003","王二");
map.put("004","麻子");
map.put("005","王五");
map.put("006","麻子");
String name = map.put("006","老麻子");
//輸出集合中的數(shù)據(jù)
System.out.println(map);
System.out.println(name);
}
}
2)刪除元素:

清空集合。

remove是根據(jù)key刪除當(dāng)前key對應(yīng)的value這一組數(shù)據(jù):
如果Map集合中有與指定的key相同的元素存在:則刪除一對key-value對象,而不是只刪除value,并返回刪除這組的value值;
如果Map集合中沒有與指定的key相同的元素存在:沒有刪除操作,返回null;
問題:為什么不可以通過value來刪除一組數(shù)據(jù)?
因?yàn)閗ey是唯一的,而value可以有多個(gè),所以不能通過value來刪除一組數(shù)據(jù),只能通過key值進(jìn)行刪除。
分析和步驟:
1)創(chuàng)建集合HashMap<String,String>集合對象map,返回值類型是Map<String,String>;
2)使用map對象調(diào)用put函數(shù)向集合中添加學(xué)生的學(xué)號作為key,姓名作為value;
3)使用map對象調(diào)用remove函數(shù)根據(jù)鍵key=004進(jìn)行刪除鍵值對,并使用String類型接收返回值value
4)輸出對象map和刪除的返回值value;
5)使用對象map調(diào)用clear()函數(shù)清除Map集合中的元素?cái)?shù)據(jù);
package cn.xuexi.map.function.demo;
import java.util.HashMap;
import java.util.Map;
/*
* Map中的remove函數(shù)的演示
* V remove(Object key)
如果存在一個(gè)鍵的映射關(guān)系,則將其從此映射中移除(可選操作)。
*/
public class MapRemoveDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//給集合中添加數(shù)據(jù)
map.put("001","張三");
map.put("002","李四");
map.put("003","王二");
map.put("004","麻子");
map.put("005","王五");
map.put("006","麻子");
//調(diào)用remove函數(shù)刪除key為004的鍵值對組合
String value = map.remove("004");//返回刪除key為004的value值麻子
//輸出集合中的數(shù)據(jù)
System.out.println(map);
System.out.println(value);
//調(diào)用clear()函數(shù)清空map集合
//注意清空指的是將map集合中的所有元素清空,集合還在,還可以使用集合對象向集合中添加數(shù)據(jù)
map.clear();
System.out.println(map);
map.put("009", "黑旋風(fēng)");
System.out.println(map);
}
}
3)改變集合中的元素:使用put函數(shù)就可以根據(jù)集合的key改變集合中的value值。
4)獲取集合中的元素:

根據(jù)指定的key元素對象,去 Map集合獲取相應(yīng)的value對象。
如果Map集合中沒有指定的key元素存在,則返回null。
注意:獲取也只能根據(jù)key獲取value,不能通過value獲取key。
分析和步驟:
1)創(chuàng)建集合HashMap<String,String>集合對象map,返回值類型是Map<String,String>;
2)使用map對象調(diào)用put函數(shù)向集合中添加學(xué)生的學(xué)號作為key,姓名作為value;
3)使用map對象調(diào)用get函數(shù)根據(jù)鍵key=003獲取value值,并使用String類型接收返回值value;
4)使用map對象調(diào)用get函數(shù)根據(jù)鍵key=007獲取value值,并使用String類型接收返回值value1;
5)輸出對象map和獲得的value、value1;
package cn.xueximap.function.demo;
import java.util.HashMap;
import java.util.Map;
/*
* Map中的get函數(shù)的演示
* V get(Object key)
返回指定鍵所映射的值;如果此映射不包含該鍵的映射關(guān)系,則返回 null。
*/
public class MapGetDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//給集合中添加數(shù)據(jù)
map.put("001","張三");
map.put("002","李四");
map.put("003","王二");
map.put("004","麻子");
map.put("005","王五");
map.put("006","麻子");
//通過集合對象調(diào)用get函數(shù)根據(jù)key獲取value值
String value = map.get("003");
String value1 = map.get("007");
//輸出集合中的數(shù)據(jù)
System.out.println(map);
System.out.println(value);
//由于集合中沒有key為007的數(shù)據(jù),所以返回null
System.out.println(value1);
}
}
5)判斷方法:

boolean containsKey(Object key) 判斷Map集合中是否包含指定的key元素存在,包含返回true,否則返回false;
boolean containsValue(Object value) 判斷Map集合中是否包含指定的value元素存在,包含返回true,否則返回false;

判斷集合是否為空,集合中沒有元素返回true,有元素返回false;
分析和步驟:
1)創(chuàng)建集合HashMap<String,String>集合對象map,返回值類型是Map<String,String>;
2)使用map對象調(diào)用put函數(shù)向集合中添加學(xué)生的學(xué)號作為key,姓名作為value;
3)使用map對象調(diào)用containsKey()函數(shù)根據(jù)鍵key=002來判斷map是否含有002這個(gè)鍵,并使用boolean類型接收返回值boo,并輸出boo;
4)使用map對象調(diào)用containsValue()函數(shù)根據(jù)值value=”田七”來判斷map是否含有”田七”這個(gè)值,并使用boolean類型接收返回值boo1,并輸出boo1;
5)map調(diào)用clear()函數(shù)清空集合;
6)map調(diào)用isEmpty()函數(shù)判斷集合是否為空,為空返回true,否則返回false;
package cn.xuexi.map.function.demo;
import java.util.HashMap;
import java.util.Map;
/*
* Map中的判斷函數(shù)講解
*/
public class MapBooleanDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//給集合中添加數(shù)據(jù)
map.put("001","張三");
map.put("002","李四");
map.put("003","王二");
map.put("004","麻子");
map.put("005","王五");
map.put("006","麻子");
//判斷集合中是否包含指定的key值
boolean boo = map.containsKey("002");
System.out.println(boo);
//判斷集合中是否包含指定的value值
boolean boo1 = map.containsValue("田七");
System.out.println(boo1);
//清除集合
map.clear();
//判斷集合是否為空 集合中沒有元素返回true,由元素返回false
boolean boo2 = map.isEmpty();
System.out.println(boo2);
}
}
6)其他方法:

size() 獲取Map集合中存儲的key-value對應(yīng)關(guān)系的個(gè)數(shù)。獲取集合長度。

Set<K> keySet() 返回此映射中包含的所有鍵的 Set集合

Collection<V> values() 返回此映射中包含的所有值的 Collection 集合
分析和步驟:
1)創(chuàng)建集合HashMap<String,String>集合對象map,返回值類型是Map<String,String>;
2)使用map對象調(diào)用put函數(shù)向集合中添加學(xué)生的學(xué)號作為key,姓名作為value;
3)使用map對象調(diào)用size()函數(shù)獲取map集合中鍵值對個(gè)數(shù),并輸出結(jié)果;
4)使用map對象調(diào)用values()函數(shù)獲取map集合中value值的個(gè)數(shù),使用Collection集合進(jìn)行接收并遍歷輸出結(jié)果;
5)使用map對象調(diào)用keySet()函數(shù)獲取map集合中key值的個(gè)數(shù),使用Set集合進(jìn)行接收并遍歷輸出結(jié)果;
package cn.xuexi.map.function.demo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
* Map中的其他函數(shù)講解
*/
public class MapOtherFunctionDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//給集合中添加數(shù)據(jù)
map.put("001","張三");
map.put("002","李四");
map.put("003","王二");
map.put("004","麻子");
map.put("005","王五");
map.put("006","麻子");
//返回map集合中的鍵值對的個(gè)數(shù)
int size = map.size();
System.out.println(size);
//獲取map集合中value值
Collection<String> values = map.values();
//遍歷集合
for (String value : values) {
System.out.println(value);
}
//獲取map集合中的所有鍵集合
Set<String> keys = map.keySet();
//遍歷set集合
for (String key : keys) {
System.out.println(key);
}
}
}
2.3、Map集合的遍歷
通過前面的學(xué)習(xí)我們已經(jīng)知道,針對Collection集合,它的遍歷可以使用Iterator迭代器進(jìn)行遍歷。
但是Map集合不能直接使用Iterator迭代器來遍歷,因?yàn)樵贛ap集合根本就沒有提供獲得迭代器對象的函數(shù)。
既然不能通過Iterator迭代器直接來遍歷,那是否可以間接使用Iterator迭代器來遍歷Map集合呢?
之前講Map集合時(shí),Map集合是用來存儲一對key-value兩個(gè)對象,既然存儲的是兩個(gè)對象,那么能不能只獲取其中一個(gè)所有的對象?
可以。Map集合屬于雙列集合,里面有兩個(gè)單列集合。由于Map集合中的key是唯一的,所以我們我可以先獲取Map集合中的key,然后根據(jù)key值來獲取Map集合中的value值。
問題1:Map中的key對象,應(yīng)該使用哪個(gè)集合存儲?
key必須保證唯一性(不能重復(fù)),使用Set集合存儲。
問題2:如何將Map中的key存儲到Set集合呢?
可以使用Map中的keySet方法:獲取Map集合中所有的key對象并存儲到Set集合中。

這樣通過上述操作就可以將Map集合中的key存放到Collection集合下面的set中,然后使用Iterator遍歷Map集合中的key所在的Set集合,進(jìn)而可以使用
Map集合中的
通過key間接的獲取每個(gè)value。
注意:Map接口中提供了2個(gè)方法可以獲取到Map中的key 或者key和value關(guān)系對象
keySet():Map中的key存儲到Set集合中。
entrySet():Map中的key和value關(guān)系對象存儲到Set集合中。
注:由于Map中key是唯一的,不能重復(fù)的,所以Map集合中鍵值對整體也是不能重復(fù)的,但是單獨(dú)value值是可以重復(fù)的。
2.3.1、使用keySet方法來遍歷Map集合(根據(jù)鍵來遍歷)必須掌握
使用keySet方法來遍歷集合的圖解如下圖所示:

]
代碼如下所示:
需求:Map的遍歷方式1:keySet遍歷
向Map集合中存儲幾個(gè)key-value對象,即夫妻鍵值對。
分析:
A:找到所有丈夫的集合
B:讓丈夫一個(gè)一個(gè)出來
C:讓丈夫去找老婆
思路:
A:找到所有鍵的集合:keySet()
B:循環(huán)遍歷,取出每個(gè)鍵
C:根據(jù)鍵找值:get(Object key)
分析和步驟:
1)創(chuàng)建Map集合對象map;
2)使用集合對象map調(diào)用put函數(shù)向集合中添加幾個(gè)數(shù)據(jù),丈夫作為key,妻子作為value;
3)使用集合對象map調(diào)用keySet函數(shù)獲取Map集合中所有的key,把所有的key保存到Set集合中,并獲取對象set;
4)使用迭代器Iterator來對Set集合中的key進(jìn)行迭代,使用迭代器對象it調(diào)用next()函數(shù)來獲取key值;
5)然后使用Map集合的對象map調(diào)用get()函數(shù),迭代的key值作為參數(shù)獲得key值對應(yīng)的value,并輸出打印key和value值;
package cn.xuexi.map.iterator.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
/*
* 使用keySet函數(shù)遍歷Map集合
*/
public class MapKeySetDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//向集合中添加數(shù)據(jù)
map.put("李晨","范冰冰");
map.put("鄧超","孫儷");
map.put("文章","馬伊琍");
map.put("汪峰","章子怡");
/*
* 通過Map的對象調(diào)用keySet函數(shù)獲得Map中的key值并保存到Set集合
* 所有的key鍵都保存到以下的set集合中了
*/
Set<String> keys = map.keySet();
//使用迭代器迭代Set集合
for (Iterator<String> it = keys.iterator(); it.hasNext();) {
//分別取出每個(gè)key值
String key = it.next();
//使用Map的對象調(diào)用get函數(shù)根據(jù)key獲得value
String value = map.get(key);
//輸出key和value
System.out.println(key+"===="+value);
}
}
}
2.3.2、使用entrySet遍歷(會使用即可,開發(fā)使用的較少)
在Map集合中提供的entrySet方法,它可以獲取到當(dāng)前Map集合中的key和value的對應(yīng)關(guān)系對象(映射對象)。
在Map集合中把key和value看成一組數(shù)據(jù),然后Java中把這一組數(shù)據(jù)又一次封裝成了一個(gè)新的對象,這個(gè)對象就是key和value的對應(yīng)關(guān)系對象。然后把這些對象保存到了Set集合中。Set集合中保存的是key和value對應(yīng)關(guān)系對象。
說明:
Set集合中保存的是key和value對應(yīng)關(guān)系對象屬于Map.Entry類型。

注意:
通過查閱API得知Map.Entry類型表示在Map接口中還有一個(gè)內(nèi)部接口Entry。
而在內(nèi)部接口Entry中有如下方法:

1)getKey()方法表示可以通過Map.Entry類型的對象調(diào)用此函數(shù)來獲得在Set集合中保存的是key和value對應(yīng)關(guān)系對象中的key值;
2)getValue()方法表示可以通過Map.Entry類型的對象調(diào)用此函數(shù)來獲得在Set集合中保存的是key和value對應(yīng)關(guān)系對象中的value值;
3)setValue()方法表示可以通過Map.Entry類型的對象調(diào)用此函數(shù)來修改在Set集合中保存的是key和value對應(yīng)關(guān)系對象中的value值;
使用Map集合中的entrySet()函數(shù)遍歷集合的過程圖解如下圖所示:

代碼實(shí)現(xiàn)如下所示:
需求:演示:Map的遍歷方式2:entrySet遍歷。
向Map集合中存儲幾個(gè)key-value對象,即夫妻鍵值對。
分析:
A:首先拿到所有夫妻的結(jié)婚證集合
B:取出每個(gè)結(jié)婚證
C:從結(jié)婚證中拿到丈夫和妻子
問題:結(jié)婚證是什么呢?
Map中存儲的元素,其實(shí)是成對的鍵值對數(shù)據(jù)。結(jié)婚證就是 一個(gè)鍵值對 對象。每一個(gè)鍵值對對象中有一個(gè)鍵和一個(gè)值。
思路:
A:獲取所有的鍵值對對象的集合
B:循環(huán)遍歷,拿到每個(gè)鍵值對對象
C:從鍵值對對象中取出鍵和值
問題:如何獲取鍵值對對象?鍵值對對象是個(gè)什么類型呢?
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的 鍵值對對象 的 Set集合。
Map.Entry<K,V> 就是鍵值對對象
K getKey() 返回與此項(xiàng)對應(yīng)的鍵。
V getValue() 返回與此項(xiàng)對應(yīng)的值。
分析和步驟:
1)創(chuàng)建Map集合對象map;
2)使用集合對象map調(diào)用put函數(shù)向集合中添加幾個(gè)數(shù)據(jù),丈夫作為key,妻子作為value;
3)使用集合對象map調(diào)用entrySet函數(shù)獲取Map集合中所有的key和value對應(yīng)關(guān)系的對象,把所有的對應(yīng)關(guān)系的對象保存到Set集合中,并獲取對象keyValues;
4)使用迭代器Iterator來對Set集合中的的對應(yīng)關(guān)系的對象keyValues進(jìn)行迭代,使用迭代器對象it調(diào)用next()函數(shù)來獲取單個(gè)獨(dú)立的key-value對應(yīng)關(guān)系對象keyValue;
5)然后使用keyValue對象調(diào)用Map.Entry中的getKey()和getValue()函數(shù)來獲得每個(gè)對應(yīng)關(guān)系對象中的key和value,最后輸出key和value值;
package cn.xuexi.map.iterator.demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/*
* 使用Map集合中的entrySet函數(shù)遍歷集合
*/
public class MapEntrySetDemo {
public static void main(String[] args) {
// 創(chuàng)建集合對象
Map<String, String> map = new HashMap<String,String>();
//向集合中添加數(shù)據(jù)
map.put("李晨","范冰冰");
map.put("鄧超","孫儷");
map.put("文章","馬伊琍");
map.put("汪峰","章子怡");
/*
* 使用entrySet()函數(shù)獲得鍵值對對應(yīng)關(guān)系的對象
* KeyValues表示鍵值的對應(yīng)關(guān)系的對象
*/
Set<Map.Entry<String, String>> keyValues = map.entrySet();
//迭代Set集合
/*
* Iterator<Map.Entry<String, String>> 表示每次迭代出來的是Map.Entry類型,
* 并且對應(yīng)關(guān)系對象中的key和value都是String類型
*/
/*for (Iterator<Map.Entry<String, String>> it = keyValues.iterator(); it.hasNext();) {
Map.Entry<String, String> keyValue = it.next();
//keyValue表示每個(gè)獨(dú)立的key-value對應(yīng)關(guān)系的對象,現(xiàn)在分別取出對應(yīng)關(guān)系中的key和value
String key = keyValue.getKey();
String value = keyValue.getValue();
//輸出key和value
System.out.println(key+"---"+value);
}*/
//使用foreach循環(huán)遍歷Set集合
for (Map.Entry<String, String> keyValue : keyValues) {
/*
* keyValue表示單個(gè)獨(dú)立的映射關(guān)系對象,現(xiàn)在分別取出對應(yīng)關(guān)系中的key和value
*/
String key = keyValue.getKey();
String value = keyValue.getValue();
//輸出key和value
System.out.println(key+"。。。。"+value);
}
}
}