第1章:集合類1.1 集合概述下面我們來學(xué)習(xí)一個(gè)比較重要的類,集合類,這個(gè)在工作中也是經(jīng)常使用的。 數(shù)據(jù)多了需要存儲(chǔ),比如數(shù)組,而對(duì)象多了也需要存儲(chǔ)。 數(shù)組能存對(duì)象嗎?可以的。 到底會(huì)產(chǎn)生多少對(duì)象,是不確定的,所以數(shù)組就沒法存儲(chǔ)了。 這個(gè)時(shí)候出現(xiàn)了集合類, 面向?qū)ο笳Z言對(duì)事物的體現(xiàn)都是以對(duì)象的形式,所以為了方便對(duì)多個(gè)對(duì)象的操作,就對(duì)對(duì)象進(jìn)行存儲(chǔ),集合就是存儲(chǔ)對(duì)象最常用的一種方式。 集合和數(shù)組的特點(diǎn) 相同點(diǎn): 集合和數(shù)組都是容器 不同點(diǎn): 數(shù)組可以存儲(chǔ)基本數(shù)據(jù)類型和對(duì)象,長(zhǎng)度是固定的; 集合長(zhǎng)度是可變的,但是集合只能存儲(chǔ)對(duì)象。 如圖1.1所示。 從下向上一步一步抽取到最上面的一層。Collection,這是一個(gè)接口 假設(shè)左邊的是一種裝水的容器,右邊的也是,抽取到最后這些容器都是可以裝水的,這個(gè)就是共性。 Collection下面有list,還有set, 這兩個(gè)下面還有很多具體的類,我們后面會(huì)具體的講解。在這就不寫了。 圖1.1 集合體系結(jié)構(gòu) 想要學(xué)習(xí)和使用這個(gè)體系,一個(gè)快速有效的方法就是,查閱該體系中頂層的類或者接口中的方法,了解該體系的共性基本功能,建立最子類的對(duì)象進(jìn)行功能的使用。 那也就是說我要首先去學(xué)習(xí)Collection接口中的功能,這里面的東西學(xué)完之后就掌握一半了,剩下的就是具體子類之間的一些區(qū)別了。 詳細(xì)一些的集合體系結(jié)構(gòu)圖如圖1.2所示。 圖1.2 集合體系結(jié)構(gòu) 1.2 Collection功能到API文檔中看一下collection中都有哪些方法,如圖1.3所示。 這個(gè)接口是在java.util包中,以后要用的時(shí)候就需要時(shí)就需要用import 導(dǎo)入這個(gè)包中的類了,咱們前面學(xué)習(xí)的一些類都會(huì)java.lang包里面的,java.lang包里面的類在使用的時(shí)候是不需要導(dǎo)包的。 圖1.3 集合中的方法 大致看一下API文檔中列出來的集合中的所有方法。 創(chuàng)建集合對(duì)象: 先導(dǎo)包,創(chuàng)建對(duì)象。找一個(gè)子類的實(shí)現(xiàn)類,Arraylist 操作代碼如圖1.4所示。 圖1.4 集合操作 下面演示一下Collection中帶All的方法 代碼如圖1.5所示。 圖1.5 Collection的操作 1.3迭代器前面介紹了一下Collection里面的常見方法 還有一個(gè)比較重要的方法,是iterator,叫迭代, 其實(shí)可以把迭代理解為順序讀取,它的返回值比較特殊,如圖1.6所示 圖1.6 迭代器 迭代器里面的方法如圖1.7所示。 圖1.7 迭代器里面的方法 先寫一個(gè)簡(jiǎn)單的例子,獲取一下集合中的元素 寫個(gè)例子,先創(chuàng)建ArrarList,再添加元素,調(diào)用iterator方法 代碼如圖1.8所示。 這里面直接使用next取值是有問題的,可能會(huì)報(bào)錯(cuò)。 圖1.8 迭代器操作 下面這種寫法就可以了。 如圖1.9 所示。 圖1.9 迭代器操作 注意了: 1:容器都有取出方式 2:容器的取值方式都符合某一規(guī)則 3:每個(gè)容器自身都有自己的數(shù)據(jù)結(jié)構(gòu),但是取出的規(guī)則都一致。 這樣我們可以用統(tǒng)一規(guī)則來操作所有容器的取出。 例如,商場(chǎng)外面的投幣機(jī),投個(gè)幣,就可以操作搖桿進(jìn)行抓取, 可以把這個(gè)容器認(rèn)為是一個(gè)集合,里面都是布娃娃,這些布娃娃可以認(rèn)為是集合里面的元素,這個(gè)夾子,就是迭代器。如圖1.10所示。 圖1.10 布娃娃機(jī) 在集合中,取出元素的格式是固定的。 只要是Collection集合體系,迭代器就是通用去除方式。 1.4 集合練習(xí)-存儲(chǔ)自定義對(duì)象存儲(chǔ)自定義Person對(duì)象,有姓名、年齡等基本屬性 將對(duì)象存儲(chǔ)到集合中,并取出,獲取其姓名或者年齡。 首先對(duì)該對(duì)象進(jìn)行描述,如圖1.11所示。 圖1.11 Person類描述 下面定義一個(gè)集合并且添加元素,使用iterator方法可以打印出集合中的元素,但是打印的卻是元素的內(nèi)存地址值,如圖1.12所示。 圖1.12 操作集合 想要打印元素的名稱,需要調(diào)用getName方法,但是報(bào)錯(cuò),提示getName不在Object類中。那說明it.next方法的返回值是對(duì)象,如圖1.13所示。 圖1.13 代碼 下面開始分析 add(Object obj);// 可以接收任意類型對(duì)象,但是任意類型的對(duì)象都被提升為了Object。所以在取出元素時(shí),取出來的也是Object。 那如果想使用Person對(duì)象中的屬性的時(shí)候,就需要強(qiáng)轉(zhuǎn)了,看這個(gè)例子,如圖1.14所示。 圖1.14 強(qiáng)轉(zhuǎn) 但是會(huì)發(fā)現(xiàn)代碼執(zhí)行報(bào)錯(cuò)了,因?yàn)樵谝粋€(gè)while循環(huán)中不應(yīng)該多次調(diào)用next方法,每一次調(diào)用next方法都是取出一個(gè)新元素,所以代碼需要調(diào)整一下,如圖1.15所示。這樣改造才行。 圖1.15 強(qiáng)制轉(zhuǎn)換 其實(shí)list里面存儲(chǔ)的都是對(duì)象的內(nèi)存地址,如圖1.16所示。 圖1.16 list內(nèi)存圖 注意: Collection coll = new ArrayList(); //集合中存儲(chǔ)的都是對(duì)象引用。 Iterator it = coll.iterator(); //獲取迭代器后,迭代器中持有的也是元素的引用 1.5 集合框架中的常用接口Collection接口有兩個(gè)子接口: List(列表),Set(集合) List:可存放重復(fù)元素(因?yàn)樵赜兴饕菢?biāo)),元素存取是有序的。 Set:不可以存放重復(fù)元素,元素存取是無序的。 第2章:list詳解2.1 List集合中常見的共性方法看這個(gè)集合框架體系圖,如圖2.1所示。 圖2.1 集合框架體系圖 我們已經(jīng)搞定了collection接口,它下面有兩個(gè)小弟,List和Set 接下來就來學(xué)習(xí)一下list接口,看下API中的介紹。 Collection |--List:該容器中的元素是有序的(存儲(chǔ)的順序和取出的順序一致) 該容器中的元素都有索引(角標(biāo)),該集合可以存儲(chǔ)重復(fù)的元素。 下面學(xué)習(xí)一下list集合中常見的共性方法。 因?yàn)?/span>list接口繼承了collection,所以主需要介紹list中特有的方法就可以了。 List的子類還使用Arraylist。 添加元素:add 刪除元素:remove 修改元素:set 獲取元素:get、indexOf、subList 代碼實(shí)現(xiàn)如圖2.2所示。 圖2.2 List的使用 針對(duì)List我們就先講這么多常用的方法。 2.2 List集合子類對(duì)象的特點(diǎn)下面學(xué)習(xí)下list集合中子類對(duì)象的特點(diǎn)。 List下面一共有3個(gè)子類,有一個(gè)已經(jīng)不常用了,這3個(gè)最主要的區(qū)別是因?yàn)閿?shù)據(jù)結(jié)構(gòu)不一樣。 List:有序,可重復(fù),有索引 | --Vector:底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組結(jié)構(gòu)。jdk1.0版本。線程安全的。無論增刪還是查詢都非常慢。 | --ArrayList:底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組結(jié)構(gòu)。線程不安全的。所以ArrayList的出現(xiàn)替代了Vector,但是查詢的速度很快。 | --LinkedList:底層是鏈表數(shù)據(jù)結(jié)構(gòu)。線程不安全的,同時(shí)對(duì)元算的增刪操作效率很高 。 Arraylist中的方法和list中的基本一樣,在這我們就不說了。 注意:鏈表結(jié)構(gòu)是這樣的。讓后一個(gè)元素記住前一個(gè)元素的地址。 數(shù)組和鏈表的區(qū)別: 當(dāng)向數(shù)組中插入一個(gè)元素的時(shí)候,插入位置后面的元素都要往后移一位。元素越多越慢,如圖2.3所示。 圖2.3 數(shù)組結(jié)構(gòu) 如果是鏈表的話,插入一個(gè)新元素,只需要讓插入的元素記住前一個(gè)元素的位置,后一個(gè)元素記住插入元素的位置即可,非常簡(jiǎn)單快速。如圖2.4所示。 刪除也特別簡(jiǎn)單,只需要讓K記住A即可。中間的元素就刪掉了。 圖2.4 鏈表結(jié)構(gòu) 總結(jié): 鏈表:增刪快,查詢慢 數(shù)組:增刪慢,查詢快 Arraylist:查詢快 Linkedlist:增刪快 Vector:查詢和增刪都特別慢,但是線程安全。 在這發(fā)現(xiàn)Arraylist和Vector都是數(shù)組結(jié)構(gòu)的,那數(shù)組怎么實(shí)現(xiàn)長(zhǎng)度可變的存儲(chǔ)呢? 它們內(nèi)部封裝了一個(gè)默認(rèn)長(zhǎng)度為10的數(shù)組。當(dāng)超出長(zhǎng)度時(shí),集合內(nèi)部會(huì)自動(dòng)生成一個(gè)新的數(shù)組。將原數(shù)組中的元素復(fù)制到新數(shù)組中,再將新元素添加到新數(shù)組中。 新數(shù)組到底有多大呢? ArrayList 50% 延長(zhǎng) Vector 100% 延長(zhǎng) 2.3 List集合子類-LinkedList下面詳細(xì)講一下下list中的linkedlist 鏈表的特有方法,是對(duì)頭尾元素進(jìn)行操作,看api文檔。 1. 在鏈表頭尾添加元素 addFirst、addLast jdk1.6以后用這兩個(gè)方法替代 offerFirst、offerLast 2. 獲取集合中的元素,集合的長(zhǎng)度不變 注意:如果集合中沒有元素,那么該方法會(huì)發(fā)生異常NoSuchElementException getFirst、getLast jdk1.6以后用這兩個(gè)方法替代 peekFirst、peekLast,如果集合中沒有元素,該方法不會(huì)拋異常,會(huì)返回null 3. 獲取集合中的元素,但是該元素會(huì)被從集合中刪除,集合的長(zhǎng)度會(huì)改變 removeFirst、removeLast jdk1.6以后用這兩個(gè)方法替代 pollFirst、pollLast,如果集合中沒有元素,該方法不會(huì)拋異常,會(huì)返回null 代碼實(shí)現(xiàn)如圖2.5所示。 圖2.5 linkedlist操作 這個(gè)也能取到第一個(gè)元素,但是集合會(huì)變化。如圖2.6所示。 圖2.6 linkedlist操作 2.4 List集合子類-Vector這個(gè)類已經(jīng)不常用了,一般只在多線程環(huán)境下使用。在這不做進(jìn)一步分析?;镜氖褂霉δ軈⒄?/span>Collection接口中的功能。 總結(jié)一下: Collection |--ArrayList |--LinkedList |--Vector 2.5 集合練習(xí)題-對(duì)字符串中的數(shù)值進(jìn)行排序將字符串中的數(shù)值進(jìn)行排序后,生成一個(gè)新的字符串。 "39 -10 18 0 21 5" 思路: 1. 先把字符串中的數(shù)值取出 2. 存儲(chǔ)到int數(shù)組中 3. 對(duì)int數(shù)組排序 4. 將int數(shù)組變成字符串 代碼如圖2.7所示。 圖2.7 案例代碼 針對(duì)這些代碼可以提取出一些方法,不建議把代碼都寫到main方法中。 抽取后的代碼如圖2.8所示。 圖 2.8 代碼抽取方法 第3章:Set詳解3.1 Set集合概述講完了collection中的list之后,再說下collection下面的set接口 Collection |--List:該容器中的元素是有序的(存儲(chǔ)的順序和取出的順序一致) 該容器中的元素都有索引(角標(biāo)),該集合可以存儲(chǔ)重復(fù)的元素。 |--Set:Set集合中的元素是無序,不可以重復(fù)的,set接口的方法和collection中的方法一致,Set集合取出元素的方法只有迭代器。 應(yīng)用場(chǎng)景:假設(shè)要存儲(chǔ)的元素必須是唯一的,這個(gè)時(shí)候就可以使用Set集合。 Set中常用的是HashSet和TreeSet。 3.2 HashSet發(fā)現(xiàn)HashSet存入的順序和取出的順序不一樣,也就是說HashSet集合沒有按照添加的順序存儲(chǔ)。 如圖3.1所示。 圖3.1 hashSet操作 hashSet中不可以用有重復(fù)元素,如圖3.2所示。 圖3.2 hashSet操作 具體要使用list還是set要根據(jù)具體的業(yè)務(wù)需求,假設(shè)要存儲(chǔ)的元素必須是唯一的,這個(gè)時(shí)候就可以使用set集合。 下面具體看下HashSet的特點(diǎn): HashSet:底層數(shù)據(jù)結(jié)構(gòu)是哈希表。 哈希表這種結(jié)構(gòu),其實(shí)就是對(duì)哈希值的存儲(chǔ)。線程不安全,存取速度快。 如圖3.3所示案例。 圖3.3 hashSet添加對(duì)象 存儲(chǔ)兩個(gè)相同姓名和年齡的人,如圖3.4所示。 圖3.4 hashSet添加對(duì)象 發(fā)現(xiàn)在HashSet中存儲(chǔ)了兩個(gè)相同的人,這是因?yàn)榕袛鄡蓚€(gè)人是否相等的依據(jù)HashSet是不知道的,所以需要我們自己定義。 所以就需要復(fù)寫Person類的equals方法,代碼如圖3.5所示。 圖3.5 復(fù)寫equals方法 再重新執(zhí)行一次HashSetDemo2,發(fā)現(xiàn),重復(fù)的元素還在。 那么說明代碼里面的比較重復(fù)功能沒有參考Person類的這個(gè)equals方法 在前面我們講ArrayList的時(shí)候,無論里面的contains和remove都是參考的equals方法。 重新思考這問題, 他判斷元素唯一性的依據(jù)到底是什么呢? HashSet:底層數(shù)據(jù)結(jié)構(gòu)是哈希表,哈希表這種結(jié)構(gòu),其實(shí)就是對(duì)哈希值的存儲(chǔ)。 HashSet在存儲(chǔ)時(shí)會(huì)計(jì)算元素的hash值,Object中有一個(gè)hashCode方法,如圖3.6所示。 圖3.6 hashCode方法 我們可以先把每個(gè)對(duì)象的hash值打印出來看一下,發(fā)現(xiàn)hash值都不同。如圖3.7所示。 圖3.7 hash值 之前hashset是根據(jù)元素的hash值判斷元素是否重復(fù), 現(xiàn)在我們的需求是根據(jù)姓名和年齡判斷元素是否重復(fù)。 在這就需要把hashCode覆蓋了,直接返回一個(gè)1, 執(zhí)行之后發(fā)現(xiàn),打印的結(jié)果是4個(gè),并且發(fā)現(xiàn)equals方法也執(zhí)行了。 注意了,從這可以看出來的,現(xiàn)在4個(gè)元素的hash值都是1,但是也都同時(shí)存在了,這就說明hashset集合保證元素唯一性,依賴的是元素的hashCode方法和equals方法。如圖3.8所示。 圖3.8 hashset代碼 總結(jié) HashSet是如何保證元素唯一性的呢? 通過equals方法和hashCode方法來保證元素的唯一性! 當(dāng)元素hash值值不同的時(shí)候,元素都有自己的獨(dú)立位置,不需要再判斷元素的equals方法。 當(dāng)元素的hash值相同時(shí),元素在hash表中位置相同,這時(shí)就需要再判斷一次元素的內(nèi)容是否相同,就需要調(diào)用元素的equals方法進(jìn)行一次比較,如果equals返回是true,那么視為是重復(fù)元素,只存儲(chǔ)一個(gè)。如果返回的是false,那么這兩個(gè)元素不是重復(fù)元素,會(huì)存儲(chǔ)在同一個(gè)hash值上。 為了建立自定義對(duì)象判斷元素是否重復(fù)的依據(jù),需要覆蓋hashCode方法和equals方法。而且最好依據(jù)對(duì)象的特有條件來建立hashCode和equals的實(shí)現(xiàn)。 注意了,其實(shí)這個(gè)時(shí)候直接把hashCode的值都返回1,效率是比較低的,因?yàn)檫@樣后來添加的所有元素都需要和之前添加的元素使用equals比較一次,因?yàn)樗麄兊?/span>hash值都相同。 所以我們實(shí)際應(yīng)該根據(jù)對(duì)象的特征返回不同的hashCode 在這我們要根據(jù)對(duì)象的姓名和年齡進(jìn)行比較,所以Person類中的hashCode可以這樣寫,如圖3.9所示。 圖3.9 hashCode方法 但是這樣的話如果張三的姓名的hashCode和李四的年齡相等,李四的姓名的hashCode和張三的年齡相等,這樣這兩個(gè)人使用我們自定義的hashCode返回值也是一樣的,為了避免這種特殊情況,我們就在age上乘以一個(gè)隨機(jī)數(shù),只要?jiǎng)e乘1就行。如圖3.10所示。 圖3.10 hashCode方法 一般我們自定義對(duì)象都會(huì)覆蓋兩個(gè)方法,hashCode和equals hashcode :建立該對(duì)象自身特點(diǎn)定義的hash值 equals:建立該對(duì)象內(nèi)容的判斷相同的依據(jù) 一般還會(huì)復(fù)寫toString 建立該對(duì)象對(duì)應(yīng)的字符串表現(xiàn)形式,因?yàn)槟J(rèn)toString打印的是對(duì)象的內(nèi)存地址值信息。 如圖3.11所示。 Person類中覆蓋toString方法。 圖3.11 覆蓋toString方法 3.3 TreesetTreeSet 底層數(shù)據(jù)結(jié)構(gòu)是二叉樹結(jié)構(gòu) 可以對(duì)Set集合中的元素進(jìn)行排序。這種結(jié)構(gòu),可以提高排序性能。線程不安全。 看這個(gè)例子,打印的順序和存儲(chǔ)的順序不一致,但是打印的順序是按照字典順序排序的。 如圖3.12所示。 圖3.12 treeSet 3.4 LinkedHashset鏈表結(jié)構(gòu),元素存入和取出的順序一致。是一個(gè)有序的集合。 代碼如圖3.13所示。 圖3.13 linkedHashSet操作 最后做一個(gè)總結(jié): ArrayList:數(shù)組 LinkedList:鏈表 HashSet:哈希表 TreeSet:二叉樹 想要保證元素唯一,就用Set集合,不需要就使用List集合。 實(shí)在搞不清楚,默認(rèn)就使用ArrayList。 3.5 集合擴(kuò)展-Queue隊(duì)列Queue:就是一個(gè)先入先出(FIFO)的隊(duì)列。 針對(duì)一些需要進(jìn)行排隊(duì)的需求中,可以使用Queue隊(duì)列。 Queue接口與List、Set同一級(jí)別,都是繼承了Collection接口。 Queue中常用的一個(gè)子類是ConcurrentLinkedQueue :是基于鏈接節(jié)點(diǎn)的、線程安全的隊(duì)列,并發(fā)訪問不需要同步。所以這個(gè)子類隊(duì)列在多線程環(huán)境下使用也是安全的。 常用功能 offer 添加一個(gè)元素并返回true 如果隊(duì)列已滿,則返回false poll 移除并返問隊(duì)列頭部的元素 如果隊(duì)列為空,則返回null peek 返回隊(duì)列頭部的元素 如果隊(duì)列為空,則返回null 代碼實(shí)現(xiàn)如圖3.14所示。 注意:隊(duì)列的.size()是要遍歷一遍集合的,所以會(huì)很慢,所以盡量要避免用size而改用isEmpty(). 圖3.14 queue隊(duì)列的使用 第4章:泛型4.1 泛型概述下面學(xué)習(xí)一個(gè)新的技術(shù)泛型。 先寫一個(gè)list集合的案例,代碼如圖4.1所示。 圖4.1 list代碼 集合中什么對(duì)象都能放。 剛開始存的是字符串,現(xiàn)在我們存一些數(shù)字類型的數(shù)據(jù)。 這樣編譯沒問題,但是執(zhí)行的時(shí)候就會(huì)報(bào)錯(cuò),類型轉(zhuǎn)換異常,integer不能強(qiáng)轉(zhuǎn)為string。如圖4.2所示。 我們自己知道只能向里面?zhèn)髯址?,但是別人并不知道該存什么類型的數(shù)據(jù),這樣就容易出問題了。 圖4.2 轉(zhuǎn)換異常 集合中存儲(chǔ)了不同類型的對(duì)象,取出時(shí),容易在運(yùn)行時(shí)期發(fā)生classCastException類型轉(zhuǎn)換異常。 為了避免這個(gè)問題的發(fā)生,如果在存儲(chǔ)的時(shí)候就明確了集合要操作的數(shù)據(jù)類型,這樣取出就沒有問題了。 jdk1.5的時(shí)候提供了泛型機(jī)制,可以解決這個(gè)問題,可以使用<> 來明確元素的類型。 加上泛型之后,編譯的時(shí)候就報(bào)錯(cuò)了。如圖4.3所示 圖4.3 泛型 泛型的好處: 1. 將運(yùn)行時(shí)期出現(xiàn)的classCastException問題,轉(zhuǎn)移到了編譯時(shí)期。 2. 避免了強(qiáng)制轉(zhuǎn)換的麻煩。 加了泛型之后,就不需要強(qiáng)轉(zhuǎn)了。如圖4.4所示。 圖4.4 泛型的使用 之前看到文檔中的這種形式我們沒有詳細(xì)解釋。如圖4.5所示。這個(gè)其實(shí)就是ArrayList的泛型參數(shù)。 注意:泛型里面不能指定int之類的基本數(shù)據(jù)類型,只能指定對(duì)象,例如integer 圖4.5 文檔介紹 4.2 泛型在集合中的使用泛型在集合中的使用,代碼如圖4.6所示。 圖4.6 泛型的應(yīng)用 4.3 泛型類下面看一下泛型在程序設(shè)計(jì)中的體現(xiàn)。 看這個(gè)代碼,最開始的時(shí)候我有一個(gè)student對(duì)象,我用一個(gè)tool工具類來對(duì)這個(gè)學(xué)生進(jìn)行設(shè)置。 后期又來了一個(gè)woker對(duì)象,我還要對(duì)這個(gè)對(duì)象也寫一個(gè)工具類進(jìn)行操作。 這樣代碼的重復(fù)度太高了。代碼如圖4.7所示。 圖4.7 代碼 可以把student改為Object,這樣是利用了多態(tài)。如圖4.8所示。 圖4.8 代碼 這樣操作的時(shí)候獲取對(duì)象是需要強(qiáng)制轉(zhuǎn)換的,如圖4.9所示。 圖4.9 代碼 這樣會(huì)有一點(diǎn)風(fēng)險(xiǎn)性,我把worker對(duì)象傳進(jìn)去的話,這個(gè)時(shí)候編譯也是沒有問題的,但是執(zhí)行的時(shí)候就有問題了。如圖4.10 所示。 所以說現(xiàn)在的程序就沒有什么可控性了。 圖4.10 類型轉(zhuǎn)換異常 這個(gè)問題是可以使用泛型來解決的。使用泛型來解決安全隱患。 將泛型定義在類上,該泛型,在該類中有效。如圖4.11所示,在類上定義了一個(gè)泛型Q,注意,這個(gè)字母也可以寫其他的字母,沒有什么特殊要求。 圖4.11 泛型 用的時(shí)候這樣用,編譯運(yùn)行是沒有問題的,如圖4.12所示。 圖4.12 泛型的使用 如果我不小心傳了個(gè)worker。 這個(gè)時(shí)候,編譯就會(huì)報(bào)錯(cuò)了。如圖4.13所示。這樣就可以避免前面所說的類型轉(zhuǎn)換的安全隱患了。 圖4.13 泛型的使用 什么時(shí)候使用泛型類呢? 當(dāng)類要操作的引用數(shù)據(jù)類型不確定的時(shí)候可以這樣用,泛型定義在類上,該泛型作用于整個(gè)類。 4.4泛型方法學(xué)完了泛型類之后,我們?cè)倏匆粋€(gè)小東西 這個(gè)代碼執(zhí)行是沒有問題的,如圖4.14所示。 圖4.14 泛型代碼 這個(gè)時(shí)候,創(chuàng)建tool的時(shí)候就需要指定泛型,后期就不能再傳遞其他類型的數(shù)據(jù)了。如圖4.15所示。 圖4.15 泛型代碼 那如果我想傳遞多種類型數(shù)據(jù)怎么辦呢? 當(dāng)泛型定義在類上,該泛型作用于整個(gè)類,當(dāng)該類建立對(duì)象時(shí),就明確了具體的類型,那么凡是使用了類上定義的泛型的方法,操作的類也就固定了。 需求:希望類中的方法可以操作任意類型而不受類中泛型的限制。 這個(gè)時(shí)候可以在方法上定義泛型,稱為泛型方法。 看這個(gè)例子:是沒問題的,字符串和數(shù)字都可以存儲(chǔ),如圖4.16所示。 圖4.16 泛型方法 靜態(tài)方法上不能使用類上定義的泛型,如圖4.17所示。 圖4.17 泛型代碼 當(dāng)類中定義static方法時(shí),靜態(tài)方法是不可以直接訪問類上的泛型, 因?yàn)轭惿系姆盒椭挥型ㄟ^建立對(duì)象才可以明確具體類型。 所以靜態(tài)方法如果操作的引用數(shù)據(jù)類型不確定,只能將泛型定義在方法上, 在靜態(tài)方法上定義泛型,必須定義在static關(guān)鍵字之后,如圖4.18所示。 圖4.18 靜態(tài)方法泛型 什么時(shí)候?qū)⒎盒投x在方法上呢? 當(dāng)方法中操作的應(yīng)用數(shù)據(jù)類型不確定,而且和對(duì)應(yīng)的對(duì)象執(zhí)行的類型也不一定一致。 這時(shí)就將泛型定義在方法上。 4.5 泛型接口如圖4.19所示代碼。 圖4.19 泛型代碼 有可能在實(shí)現(xiàn)類里面也不能確定具體要操作的數(shù)據(jù)類型,所以可以這樣做,在實(shí)現(xiàn)類里面指定具體的數(shù)據(jù)類型,如圖4.20所示。 圖4.20 泛型接口 4.6 泛型通配符(了解)下面來學(xué)習(xí)一個(gè)泛型中比較特殊的東西。這塊內(nèi)容作為了解即可。 看這個(gè)例子:如圖4.21所示。 圖4.21 代碼 這樣的話show里面就只能使用操作String類型的集合 下面呢,我們又有了一個(gè)hashset類型的集合,這樣的話show方法就沒法進(jìn)行操作了,因?yàn)?/span>hashset集合中存儲(chǔ)的是數(shù)字類型,如圖4.22所示。 圖4.22 代碼 這個(gè)時(shí)候可以使用泛型中的通配符。 泛型通配符的格式為:? 問號(hào)代表任意類型。 所以代碼需要把泛型改為?的格式,如圖4.23所示。 圖4.23 泛型通配符 為什么在這不使用T 呢? T:表示聲明為一個(gè)具體類型變量 ?:表示不確定類型的時(shí)候使用 這兩個(gè)類型見到的時(shí)候能看明白就可以了,一般我們?cè)陂_發(fā)的時(shí)候不會(huì)自己定義。 4.7 泛型的限定(了解)先定義這兩個(gè)類,代碼如圖4.24所示。 圖4.24 代碼 如果在主函數(shù)中定義一個(gè)集合,泛型指定為student,最后調(diào)用show的時(shí)候肯定是會(huì)報(bào)錯(cuò)的。 注意:定義集合要保證左右兩邊的泛型類型一直。 現(xiàn)在我還想要這兩個(gè)集合都能調(diào)用show方法,該怎么做呢? 可以使用我們前面講的泛型限定符,是可以的,但是使用泛型限定符的時(shí)候什么類都可以接收,但是最后代碼在獲取getName()的時(shí)候可能會(huì)有安全隱患,如果不是Person及其子類,都會(huì)報(bào)錯(cuò)的。 寫T表示只接收一種類型的,寫?表示接收任意類型,現(xiàn)在我只想接收person或者是person的子類對(duì)象 所以在這里我們想要限定,傳入的元素類型,要么是Person,要么是Person的子類型,這時(shí)就可以使用泛型的高級(jí)應(yīng)用,泛型的限定。 代碼如圖4.25所示。 注意:show方法可以這樣改,這樣就只接收person和person的子類,最后還可以使用執(zhí)行getName()方法 在這要注意,下面調(diào)用的方法只能是person類中的方法。 圖4.25 泛型的限定 泛型的限定: ? extends E: 接口E類型或者E類型的子類型。 ? super E: 接口E類型或者E的父類型。 4.8 泛型的限定在集合中應(yīng)用(了解)看api文檔中的應(yīng)用場(chǎng)景,如圖4.26所示。 在定義集合的泛型的時(shí)候,也可以實(shí)現(xiàn)泛型的限定來對(duì)集合中的元素進(jìn)行限制。
第5章:Map詳解5.1 Map概述下面我們學(xué)習(xí)一下集合的剩余部分,map Map和collection都屬于頂層接口 Map集合的特點(diǎn): 1. Map是一個(gè)雙列集合,Collection是單列集合 2. Map一次存一對(duì)元素,是鍵值對(duì)的形式,鍵和值有對(duì)應(yīng)關(guān)系,Collection是一次存一個(gè)元素。 3. Map集合必須要保證集合中鍵的唯一性! 可以這樣理解,Collection中存儲(chǔ)的是光棍,Map集合存儲(chǔ)的是夫妻。 看下api文檔中map的方法,如圖5.1所示。 圖5.1 map介紹 map集合中常見功能介紹 1. 添加 V put(K key, V value) :將K和V作為元素存儲(chǔ)到map集合,當(dāng)存入了相同 的key時(shí),新的value會(huì)覆蓋原來的value,并返回原來的value。 void putAll(Map<? extends K,? extends V> m) :把一個(gè)map集合添加到另一 個(gè)map集合中 2. 刪除 clear():清空集合內(nèi)的元素 v remove(k): 安裝鍵刪除,返回被刪除的鍵對(duì)應(yīng)的值 3. 判斷 boolean containsKey(Object key):判斷集合中是否包含指定的key boolean containsValue(Object value):判斷集合中是否包含指定的value boolean isEmpty():判斷集合是否為空 4. 獲取 int size():獲取map集合中元素的個(gè)數(shù) V get(Object key):通過鍵獲取值,還可以作為判斷某一個(gè)鍵是否存在的依據(jù) Collections values():獲取map集合中所有元素的值。 Set keySet():獲取map集合中所有的鍵。 Set entrySet():獲取的是鍵值的映射關(guān)系,將映射關(guān)系封裝成對(duì)象存儲(chǔ)到了Set集合中。 注意:Map集合沒有迭代器,迭代器是Collection集合具備的。 Map集合取出所有元素的原理:先將map集合轉(zhuǎn)成set集合,再進(jìn)行跌代。 5.2 Map子類特點(diǎn)及使用Map集合常見子類: |--Hashtable:底層是哈希表數(shù)據(jù)結(jié)構(gòu),是同步的 (線程安全),速度慢,不允 許存放null鍵,null值,已被HashMap替代。 |--Properties:用于配置文件的定義和操作,使用頻率非常高,同時(shí)鍵和 值都是字符串。是集合中可以和IO技術(shù)相結(jié)合的對(duì)象,到了IO部分再 學(xué)習(xí)它和IO相關(guān)的功能。 |--HashMap:底層也是哈希表數(shù)據(jù)結(jié)構(gòu),是不同步的 (線程不安全),速度快, 允許存放null鍵,null值。 |--LinkedHashMap:可以保證HashMap集合有序,存入的順序和取出的 順序一致。 |--TreeMap:對(duì)集合中元素的鍵進(jìn)行排序。 添加元素的時(shí)候注意了,如果指定的鍵已經(jīng)存在了,那么put語句會(huì)有返回值。返回原始值,并把新值覆蓋上去。 Map操作案例代碼如圖5.2所示。
5.3 Map取出方式一keySet下面看一下如何獲取map中所有的元素,單獨(dú)使用get是不行的。 如何獲取集合中所有的元素呢? 第一種方式: 先獲取所有的key(鍵),再通過對(duì)所有的key進(jìn)行遍歷,在遍歷中通過get方法獲取每一個(gè)key對(duì)應(yīng)的value。 使用Map結(jié)合中的keySet方法,可以獲取Map集合中key的集合。 獲取所有的key和value,代碼如圖5.3所示。 圖5.3 獲取集合中的元素 畫圖分析這個(gè)過程,如圖5.4所示。 圖5.4 取值流程 5.4 Map取出方式二entrySet第二種方式: 先獲取map中的鍵值關(guān)系封裝成一個(gè)個(gè)的entry對(duì)象,存儲(chǔ)到一個(gè)set集合中,再迭代這個(gè)set集合,根據(jù)entry獲取對(duì)應(yīng)的key和value。 代碼如圖5.5所示。 圖5.5 獲取map集合中的元素 畫圖分析這個(gè)流程,如圖5.6所示: 圖5.6 取值流程分析 5.5 HashMap集合中存儲(chǔ)自定義對(duì)象需求: 每一個(gè)學(xué)生都有自己的歸屬地 學(xué)生有姓名和年齡屬性,將學(xué)生封裝成student對(duì)象。 因?yàn)閷W(xué)生對(duì)象和歸屬地有對(duì)應(yīng)關(guān)系,所以用map集合存儲(chǔ)該元素。 鍵:Student 值:String 注意:同姓名和同年齡的學(xué)生視為同一個(gè)人。 先建立一個(gè)student類,代碼如下5.7所示。 圖5.7 student類 先演示正常數(shù)據(jù)插入,最后再插入一個(gè)相同元素,在這里我們認(rèn)為姓名和年齡相同就是同一元素。代碼如圖5.8所示。 圖5.8 代碼 要保證學(xué)生對(duì)象的唯一性,需要建立學(xué)生對(duì)象自身的判斷相同的依據(jù)。 因?yàn)槭谴娣诺搅?/span>Hash表中,所以需要覆蓋hashcode和equals方法。 代碼如圖5.9所示。 圖5.9 修改后的代碼 5.6保證HashMap集合有序-LinkedHashMap現(xiàn)在數(shù)據(jù)可以存進(jìn)去了,但是現(xiàn)在是無序的。 之前在說set集合的時(shí)候下面有一個(gè)子類,linkedHashSet,可以保證順序 對(duì)應(yīng)map的話也有一個(gè)linkedHashMap,可以保證存入的順序和取出的順序一致,代碼如圖5.10所示。 圖5.10 linkedHashMap的使用 5.7 TreeMap現(xiàn)在我想對(duì)集合中的元素按照指定的順序排序 這個(gè)時(shí)候就需要使用TreeMap了,代碼如圖5.11所示: 圖5.11 TreeMap的用法 5.8 獲取Map中的所有值-values方法現(xiàn)在的需求,我就想拿到所有學(xué)生的歸屬地,想看看他們都來自于哪。 代碼如圖5.12所示。 圖5.12 values方法 5.9 Map集合擴(kuò)展某機(jī)構(gòu)有線上班和線下班。 線上班: 01 zhangsan 02 lisi 線下班: 01 wangwu 02 zhaoliu 這個(gè)時(shí)候就可以在Map中存儲(chǔ)Map了。(當(dāng)然也可以在map中存儲(chǔ)自定義對(duì)象) 線上班有很多學(xué)生,線下班也有很多學(xué)生 需要有一個(gè)map存儲(chǔ)線上學(xué)生的編號(hào)和姓名 需要有一個(gè)map存儲(chǔ)線下學(xué)生的編號(hào)和姓名 因?yàn)榫€下和線上都屬于某機(jī)構(gòu), 所以某機(jī)構(gòu)也是一個(gè)集合 這個(gè)集合里面有兩種類型的班級(jí)。 所以就是類似這種格式的 某機(jī)構(gòu)<班級(jí)類型,學(xué)生集合<學(xué)生編號(hào),學(xué)生姓名>> 代碼實(shí)現(xiàn)如圖5.13所示。 圖5.13 嵌套map 5.10 MapUtils工具類需求如下: 單詞計(jì)數(shù)案例 需求:統(tǒng)計(jì)字符串中相同單詞出現(xiàn)的次數(shù) 輸出結(jié)果 單詞:出現(xiàn)總次數(shù) 代碼如圖5.14所示。 圖5.14 單詞計(jì)數(shù)案例 針對(duì)上面的代碼可以使用MapUtils進(jìn)行改造,改造之后如圖5.15所示。 注意:在使用MapUtils的時(shí)候需要添加commons-collections依賴jar包。 把commons-collections拷貝到項(xiàng)目的lib目錄下,然后buildPath即可使用。 圖5.15 MapUtils的使用 MapUtils.getInteger(map, key, defaultValue); 這行代碼的意思是從map這個(gè)集合中根據(jù)key查詢value值,如果查詢不到的話就返回defaultValue。能查詢到的話就返回key對(duì)應(yīng)的value值。 第6章:集合工具類如圖6.1所示的集合關(guān)系圖,大部分常用的都講了,現(xiàn)在就剩右下角的工具包沒有講了。 圖6.1集合關(guān)系圖 假設(shè)要對(duì)ArrayList中的元素進(jìn)行排序,那么直接存儲(chǔ)到TreeSet中不就有序了嗎? 但是我們的元素是有重復(fù)的,不能直接存到TreeSet里面。 這時(shí)就可以考慮使用集合框架的工具類來完成。 集合中的工具類有兩個(gè)。 Collections 和 Arrays 這兩個(gè)工具類的特點(diǎn):類中的方法都是靜態(tài)的,不需要?jiǎng)?chuàng)建對(duì)象,直接使用類名調(diào)用即可。 下面先學(xué)習(xí)一下Collections工具類的一些用法 6.1 Collections-sort 方法看api文檔中的介紹,如圖6.2所示、 圖6.2 Collections工具類介紹 這個(gè)類里面都是靜態(tài)方法。 Collections:集合對(duì)象的工具類,提供了操作集合的方法 需求: 想對(duì)這個(gè)集合中的元素進(jìn)行排序,按照字典順序排序。代碼實(shí)現(xiàn)如圖6.3所示。 圖6.3 集合元素排序 6.2 Collections-reverseOrder方法如果想要按照倒序排序呢?可以使用Collections中的倒序比較器。 代碼如圖6.4所示。 圖6.4 倒序排序 6.3 Collections-max方法按照自然順序獲取最大值,如圖6.5所示。 圖6.5 獲取最大值 6.4 Collections-其他方法代碼如圖6.6所示。 圖6.6 Collections中的其他方法 下面這個(gè)要注意一下 Collections: List synchronizedList(List list):可以將一個(gè)不同步(線程不安全)的list集合轉(zhuǎn)成一個(gè)同步(線程安全)的list集合。 XXX synchronizedXXX(XXX xxx):將非同步的集合變成同步集合的方法。 多個(gè)線程操作ArrayList,會(huì)造成數(shù)據(jù)錯(cuò)亂。Vector是線程安全的,但是效率很低。 可以自己加鎖,但是這樣比較麻煩 所以集合工具類提供了一個(gè)比較方便的方法,將非同步的集合變成同步集合的方法。 6.5 ArraysArrays:提供了操作數(shù)組的方法。 這個(gè)類里面就幾個(gè)方法,其余的都是對(duì)應(yīng)的重載方法。 這里面有一個(gè)比較重要的方法 asList:數(shù)組變集合 代碼如圖6.7所示、 圖6.7 asList的使用 數(shù)組轉(zhuǎn)成集合之后有什么好處? 當(dāng)數(shù)組變成集合后,就可以使用集合的方法來操作數(shù)組,而不用自己再寫代碼操作數(shù)組了,但是有些方法是不可以使用的,只要是改變集合長(zhǎng)度的方法都不能用,因?yàn)閿?shù)組是固定長(zhǎng)度的。 Arrays的tostring方法 把數(shù)組轉(zhuǎn)為字符串,代碼如圖6.8所示。 圖6.8 數(shù)組轉(zhuǎn)字符串 6.6 集合轉(zhuǎn)成數(shù)組說完了數(shù)組變集合,咱們?cè)倏纯醇献償?shù)組 看api中Collection的方法,第一個(gè)返回值是object數(shù)組,需要強(qiáng)制轉(zhuǎn)換。 第二個(gè)是一個(gè)泛型,這個(gè)就不需要強(qiáng)轉(zhuǎn)了。如圖6.9所示 圖6.9 API文檔介紹 看這個(gè)例子,代碼如圖6.10所示。 圖6.10 集合轉(zhuǎn)數(shù)組 在這其實(shí)指定數(shù)組的長(zhǎng)度使用集合的size方法最合適,如圖6.11所示。 當(dāng)指定的長(zhǎng)度小于集合的長(zhǎng)度,該方法內(nèi)部會(huì)自動(dòng)創(chuàng)建一個(gè)該類型的新數(shù)組長(zhǎng)度和集合長(zhǎng)度一致,用于存儲(chǔ)集合中的元素。 如果指定的數(shù)組長(zhǎng)度等于集合的長(zhǎng)度,那么該方法就不會(huì)創(chuàng)建新數(shù)組,而是使用傳遞進(jìn)來的數(shù)組。 所以在定義數(shù)組時(shí),最好定義長(zhǎng)度和集合長(zhǎng)度相同的數(shù)組,這樣就不用創(chuàng)建新數(shù)組了。 圖6.11 集合轉(zhuǎn)數(shù)組 將集合轉(zhuǎn)成數(shù)組有什么用呢? 其實(shí)是限定了對(duì)元素的增刪操作。 第7章:增強(qiáng)for循環(huán)7.1增強(qiáng)for循環(huán)最開始可以這樣迭代集合中的元素,如圖7.1所示。 圖7.1 迭代操作 但是天天這樣寫發(fā)現(xiàn)也比較麻煩。 發(fā)現(xiàn)collection有一個(gè)父類,iterable接口,實(shí)現(xiàn)這個(gè)接口的類是可以使用foreach循環(huán)操作的。文檔解釋 如圖7.2所示。 圖7.2 文件介紹 JDK1.5版本以后的新特性,Collection有一個(gè)父接口Iterable, 該接口的出現(xiàn)封裝了iterator方法,并提供了一個(gè)增強(qiáng)for循環(huán)。 格式: for(元素類型變量 : 數(shù)組或者Collection集合) { } 例子:這樣發(fā)現(xiàn)代碼就非常簡(jiǎn)潔了。如圖7.3所示。 圖7.3 foreach迭代 增強(qiáng)for循環(huán)和傳統(tǒng)for循環(huán)有什么不同呢? 增強(qiáng)for循環(huán)在使用時(shí),必須要有被遍歷的目標(biāo),而且只能遍歷數(shù)組和Collection集合,簡(jiǎn)化了迭代。 傳統(tǒng)for循環(huán),它的應(yīng)用更為普遍。 注意:如果在遍歷的時(shí)候需要用到角標(biāo),還是要用傳統(tǒng)的for循環(huán)。 第8章:可變參數(shù)
|
|