今日任務
1、TreeSet介紹(掌握TreeSet集合的應用)
2、Comparable 接口介紹(掌握)
3、Comparator 比較器介紹(掌握)
4、Collection下的接口和實現類的總結
5、泛型技術(掌握泛型的基本使用)
1、TreeSet介紹(掌握)
1.1、TreeSet介紹
目前為止我們學習三個主要集合:
ArrayList:它的底層使用的可變數組,可以根據下標操作集合中的元素,可以重復,保證存取順序。
LinkedList:它的底層是鏈表,有頭有尾,可以根據頭尾操作集合中的數據。也可以保存重復數據。
HashSet:它的底層是哈希表,不能保存重復數據,不保證存取順序。
TreeSet:它是Set接口下的一個間接實現類。它可以保證對象元素唯一,存取無序,線程不安全,效率高,同時還可以對存放在其中的對象進行排序。
說明:
1)將數據存儲在TreeSet集合中時就已經開始排序了;
2)這里說的自然排序可以暫時可以理解為升序排序(因為通過查看源代碼發現底層就是升序排序);
1.2、TreeSet演示
分析和步驟:
1)創建一個TreeSetDemo類;
2)在這個類中定義一個method_1函數;
3)在method_1函數中創建TreeSet類的對象set;
4)使用對象set調用add函數向集合中添加幾個字符串對象,并使用iterator迭代器遍歷集合;
5)在定義一個method_2函數,在這個類中同樣創建TreeSet類的對象set,并使用對象set調用add函數向集合中添加幾個整數,并使用iterator迭代器遍歷集合;
依次給集合存入整數20 31 10 13 23 5 51 5”
package cn.xuexi.set;
import java.util.Iterator;
import java.util.TreeSet;
/*
* TreeSet集合的演示
*/
public class TreeSetDemo {
public static void main(String[] args) {
//調用自定義函數
method_2();
}
public static void method_2() {
//創建集合對象 自然排序 升序
TreeSet set = new TreeSet();
//向集合中添加數據
set.add(20);
set.add(31);
set.add(10);
set.add(13);
set.add(23);
set.add(5);
set.add(51);
set.add(5);
//遍歷集合
for (Iterator it = set.iterator(); it.hasNext();) {
//輸出集合中的數據
System.out.println(it.next());
}
}
public static void method_1() {
//創建集合對象 自然排序 升序
TreeSet set = new TreeSet();
//向集合中添加數據
set.add("abc");
set.add("abcdef");
set.add("acc");
set.add("Abc");
set.add("abcd");
set.add("bbb");
set.add("bbb");
//遍歷集合
for (Iterator it = set.iterator(); it.hasNext();) {
//輸出集合中的數據
System.out.println(it.next());
}
}
}
輸出結果分別是:
說明:這里先暫時把自然排序理解為升序排序,其實真正的自然排序和我們后面講到的比較器Comparable接口有關。
通過之前所學的集合,我們發現不同的集合底層的數據結構是不一樣的,從而導致存儲數據方式也不一樣了,那么我們接下來就會
來研究下為什么TreeSet集合不能保存重復元素和怎樣進行排序的呢?
這顯然和TreeSet底層的數據結構有關系。那么底層到底是個什么樣的數據結構呢?
1.3、樹結構介紹
通過查閱API我們得知TreeSet集合是基于TreeMap的實現,而TreeMap是基于二叉樹(紅黑樹)結構,也就是說TreeSet集合的底層使用的二叉樹(紅黑樹)結構。
樹結構:它也是數據結構中的一種。在計算機領域中樹結構指的是倒立的樹。
樹結構存儲的數據,每個數據也需要節點來保存。
而TreeSet集合底層是二叉樹的數據結構,什么是二叉樹呢?
二叉樹:每個節點的下面最多只能有2個子節點。
說明:最多表示一個節點下面可以有兩個子節點或者一個子節點或者沒有子節點。
在二叉樹的根節點左側的節點稱為左子樹,在根節點的右側的節點稱為右子樹。
既然已經得知TreeSet集合底層是二叉樹,那么二叉樹是怎樣存儲數據的呢?是怎樣保證存儲的數據唯一并有序的呢?
二叉樹的存儲流程:
當存儲一個元素的時候,如果是樹的第一個元素,這個元素就作為根節點。
如果不是第一個元素,那么就拿要存儲的元素與根節點進行比較大小:
大于根元素:就將要存儲的元素放到根節點的右側,作為右葉子節點。
等于根元素:丟棄。
小于根元素:就將要存儲的元素放到根節點的左側,作為左葉子節點。
總結:二叉樹是通過比較大小來保證元素唯一和排序的。
如何遍歷二叉樹?
遍歷二叉樹有四種方式:前序遍歷、中序遍歷、后序遍歷、按層遍歷。
前序遍歷,就是先訪問 根節點------>左子樹------>右子樹
中序遍歷,就是先訪問 左子樹------>根節點------>右子樹
后序遍歷,就是先訪問 左子樹------>右子樹------>根節點
按層遍歷,就是把一棵樹從上到下,從左到右依次取出
而在TreeSet的底層使用中序遍歷二叉樹,即左----->根------>右。
二叉樹的結構圖如下圖所示:
1.4、TreeSet集合存放自定義對象
案例:使用TreeSet集合存儲Student對象。存儲的Student對象要求按照年齡排序。
步驟和分析:之前我們一直向TreeSet集合中存儲整數和字符串,接下來我們要存儲自定義類Student對象。
1)定義一個Student類,在這個類中定義name和age兩個屬性,并創建構造函數給屬性初始化值,并復寫toString()函數;
2)定義一個測試類TreeSetDemo;
3)在TreeSetDemo類中創建TreeSet集合對象ts;
4)使用集合對象ts調用TreeSet集合中的add()函數分別向集合中添加幾個學生對象,并遍歷;
學生Student類代碼如下:
package cn.xuexi.set;
/*
* 描述學生
*/
public class Student {
//屬性
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
測試類TreeSetDemo代碼如下:
package cn.xuexi.set;
import java.util.Iterator;
import java.util.TreeSet;
/*
* TreeSet集合的演示
*/
public class TreeSetDemo {
public static void main(String[] args) {
//創建集合對象
TreeSet ts = new TreeSet();
//向集合中添加數據
ts.add(new Student("張三",19));
ts.add(new Student("李四",20));
ts.add(new Student("王五",17));
ts.add(new Student("黑旋風",19));
//遍歷集合
for (Iterator it = ts.iterator(); it.hasNext();) {
//輸出集合中的數據
System.out.println(it.next());
}
}
}
上述代碼運行結果會報如下錯誤:
分析異常的原因:
通過以上報異常錯誤原因大概知道我們要將自己定義的類Student的存儲到TreeSet集合中的時候,發生自定義類Student不能被轉換到Comparable比較器接口的異常,我們在代碼中明明沒有書寫和Comparable接口相關的代碼啊,那怎么會報這個異常呢?
這里我們需要查看一下TreeSet底層的源代碼進行進一步分析。
2.5、TreeSet集合的源碼分析
問題1:為什么TreeSet集合中存儲的元素可以不重復呢?
TreeSet集合底層使用了二叉樹結構,在存儲元素時,拿存儲的元素會和樹結構中已經存在元素進行比較大小(結果有三種:大于、小于、相等)。當比較的結果相等時,表示該元素已經存在了,則不存儲。
問題2:為什么TreeSet集合中存儲的元素會排序呢?
在存儲元素時,先拿要存儲的元素和樹結構中已經存在的元素進行比較大小,如果要存儲的元素大于樹結構中已存在的元素,則把要存儲的元素存放比較元素的右邊;如果要要存儲的元素小于樹結構中已存在的元素,則把要存儲的元素放在左邊。
分析TreeSet集合的源碼查找為什么會報上述異常:
說明:
1)通過查看源代碼得知當執行new TreeSet()集合時,相當于NavigableMap m=new TreeMap();
而NavigableMap是TreeMap的父接口,所以這里發生多態了。
這里可以理解m就代表TreeMap集合類的對象。
2)在TreeSet集合類中的add()函數體里面,使用對象m調用的put()函數肯定是TreeMap集合中的函數,所以接下來我們要去TreeMap集合類中找put()函數。
說明:
通過查看TreeMap集合中put函數中得知,在底層是要將我們自定義類型Student的對象強制轉換為Comparable比較器接口對象,而在java中面向對象中。如果兩種類型相互轉化必須要有繼承或者實現關系,而這里我們自定義的類Student和Comparable接口根本就沒有關系,所以會發生類型轉換異常。
知道錯誤原因之后,接下來我們要學習Comparable接口到底是什么?為什么底層要使用它。
2、Comparable接口介紹(掌握)
2.1 Comparable接口概述
通過查閱API得知:如果一個類要排序,那么這個類必須實現這個接口。
問題1:之前向TreeSet集合中存儲數據為什么存儲整數和字符串就沒問題呢?
整數的包裝類是Integer類型:
Integer實現了這個接口。
字符串的類型是String類型,而String類型也實現了這個接口。
由上述得知我們自己定義的類Student也要實現Comparable接口,這樣在底層就可以強制類型轉換了,就不會報異常了,既然不會報異常了就能調用Comparable接口中的compareTo()函數進行對Student的對象自然排序了。
既然已經實現了這個接口,那么必須實現接口中的抽象方法:
說明:
1)實現這個方法,必須返回一個int值,這個值可以是正數、負數、0代表當前對象大于、小于、等于指定的對象。
如果返回值是正數:表示當前對象就是調用此函數執行的對象大于指定的對象,就是作為參數的對象;
如果返回值是0 :表示當前對象就是調用此函數執行的對象等于指定的對象,就是作為參數的對象;
如果返回值是負數:表示當前對象就是調用此函數執行的對象小于指定的對象,就是作為參數的對象;
2)一旦我們的對象實現了Comparable接口,實現了compareTo方法,代表我們的對象就可以比較大小了。這種比較稱為自然排序。
2.2 解決上述向TreeSet集合中存放自定義對象時異常問題
通過以上分析,我們得出如下結論:
我們要向TreeSet中存儲自定義對象,這個自定義對象所屬的類需要實現Comparable接口,并實現compareTo方法。
改進程序:讓Student類實現Comparable接口,并重寫compareTo方法。
由于最開始我們想按照存儲的學生對象的年齡進行排序,所以我們應該在Student類中復寫compareTo()函數時,在函數中書寫比較兩個學生對象年齡的代碼,這個compareTo()函數是用來比較當前學生對象和指定的學生對象的大小,返回值可以是正數 、 0、負數。
所以在該函數中對兩個學生對象的年齡進行相減,然后將相減后的結果返回給底層代碼就可以了。
修改Student類中的代碼如下:
說明:
復寫Comparable接口中的compareTo()函數,這個函數是用來比較兩個學生對象是否相等。
如果函數返回是0代表往TreeSet集合中存儲的數據都是相等的數據;
如果返回正數或者負數說明集合中的數據都不想等,則會將所有數據都保存到TreeSet集合中。
而我們想要的是年齡相等的學生在集合中只保存一個對象,所以這里我們需要調用兩個學生對象的age屬性進行比較:
this.age表示調用函數對象的年齡,
但是上述代碼不能使用o.age,因為Object類中根本就沒有age屬性,age是子類Student類中特有的屬性,多態發生了想使用子類特有的屬性或者行為,這里需要強制類型轉換------》Student s= (Student)o ;
強制類型轉換有風險,使用需謹慎 。所以使用instanceof進行判斷。
問題升級:
要求在TreeSet集合中保存自定義Student類對象時,只有年齡和姓名都相同時保存一個對象,而如果只有年齡或者名字相同時是可以保存在集合中的,不算重復相同的數據。
所以這里需要繼續改進Student類的函數,我們除了比較年齡,在年齡一樣的情況下,還要比較姓名:
這里假設年齡相等,那么我們怎么比較兩個人的姓名呢?
注意:這里是比較姓名,而姓名是字符串,比較兩個字符串可以使用String類中的equals()函數和compareTo()函數,那么這里我們應該使用compareTo函數,因為我們要給底層返回int類型,而equals返回值類型是boolean類型,所以我們使用compareTo函數比較姓名是否相等。
代碼如下:
總結:
TreeSet集合存放對象時需要注意的問題:
需要保證對象具備比較功能,也就是對象所屬的類一定要實現Comparable接口,并且需要實現compareTo函數。在compareTo函數中書寫具體的比較方式。
TreeSet集合怎么保證對象的唯一:
當給TreeSet集合中保存對象的時候,會調用對象的比較功能compareTo函數,只要這個函數返回的0,就認為2個對象相同,只保存其中的一個,另一個對象不會被保存到集合中,直接被刪除。
3、Comparator比較器介紹(掌握)
3.1 Comparator自定義比較器概述
給TreeSet集合中保存對象的時候,由于TreeSet集合可以對其中保存的對象進行排序。要求保存的對象必須具備比較功能。因此要求給TreeSet集合中保存的對象所屬的類必須實現Comparable接口。
需求:把字符串保存到TreeSet集合中,但是要求按照字符串的長度排序。
分析:String類本身已經具備了比較功能,它的比較功能是按照字符串中字符的字典順序比較的(就是使用Comparable接口的重寫compareTo方法進行排序,我們把這種排序方式叫做自然排序)。而現在我們希望按照字符串的長度進行比較。現在String類中的比較功能不適合當前程序的要求了。
另外由于String類是final修飾的,我們無法去繼承這個類,就無法去復寫String類中的compareTo方法,無法復寫String類中的compareTo方法,就無法將compareTo方法改成比較字符串長度的方法。
上述的問題,不能使用繼承解決。Java給我們提供的相應的解決方案:
當我們要給TreeSet集合中保存對象的時候,如果對象具備的比較功能不適合當前我們的需求時,我們可以給TreeSet集合自身傳遞一個適合我們需求的比較對象。然后讓TreeSet集合按照我們傳遞進去這個對象對其中需要保存的數據進行比較。
而這個對象就是Comparator對象。
通過查閱API,我們得知創建TreeSet集合對象時,使用的構造函數中參數就可以接收Comparator對象:
什么是Comparator接口呢?
Comparator接口可以對集合中的元素進行排序,通過查看源碼分析,當我們把這個Comparator接口的實現類對象丟給集合之后,那么給集合中存放的對象,不會再使用對象自身的比較功能進行大小的比較,而會使用我們傳遞的Comparator的實現類對象進行比較。
Comparator接口的函數如下所示:
說明:
1)在Comparator接口中的compare方法中接受2個參數o1和o2,這2個參數就是集合中需要比較大小的2個對象;
2)這個方法,專門比較兩個元素的大小:
A.如果o1>o2,則返回 正整數;
B.如果o1==o2,則返回 零;
C.如果o1<o2,則返回 負整數;
需求:把字符串保存到TreeSet集合中,但是要求按照字符串的長度排序。
完成上述需求代碼如下:
分析步驟:
1)創建一個自定義比較器類MyComparator并實現Comparator接口;
2)MyComparator類復寫Comparator接口的compare函數,接收的參數都是Object類型的,所以需要強制轉換為Student類型的對象s1和s2;
3)使用對象s1和s2分別調length()函數獲得字符串的長度并相減,將結果賦值給一個整數變量temp;
4)使用判斷結構進行判斷如果temp==0,說明字符串長度相同則調用String類中的compareTo函數按照字典順序進行比較并返回結果;
5)如果長度不同返回長度的差值;
自定義比較器代碼如下所示:
package cn.xuexi.set;
import java.util.Comparator;
public class MyComparator implements Comparator{
public int compare(Object o1, Object o2) {
/*
* 這里發生多態了,那么我們先把Object類型強制轉換為String類型
* 這樣才能使用String類的對象調用length()函數進行長度比較
*/
if(!((o1 instanceof String) && (o2 instanceof String)))
{
//說明不是String類型
throw new RuntimeException("傳遞的必須是字符串");
}
//程序能夠運行到此處說明兩個對象肯定都是字符串
String s=(String)o1;
String s1=(String)o2;
//調用函數獲得字符串長度相減
int temp=s.length()-s1.length();
//如果字符串長度相同,按字符串的字典順序比較
if(temp==0)
{
//說明長度相同
return s.compareTo(s1);
}
else
{
//說明長度不同 返回長度的差值
return temp;
}
}
}
創建集合的測試類代碼如下所示:
分析和步驟:
1)創建一個測試類TreeSetDemo1;
2)創建自定義比較器類MyComparator的對象myComparator;
3)使用new關鍵字調用TreeSet類的的構造函數比較器類MyComparator的對象myComparator作為參數傳遞來創建集合TreeSet類的對象ts;
4)使用集合對象向集合中添加字符串數據;
5)使用迭代器遍歷集合,并輸出;
package cn.xuexi.set;
import java.util.Iterator;
import java.util.TreeSet;
/*
* 把字符串保存到TreeSet集合中,但是要求按照字符串的長度排序。
*/
public class TreeSetDemo1 {
public static void main(String[] args) {
//創建比較器對象
MyComparator myComparator = new MyComparator();
//創建集合對象
TreeSet ts=new TreeSet(myComparator);
//向集合中添加字符串數據
ts.add("abc");
ts.add("jjj");
ts.add("AAAAA");
ts.add("hahahsg");
//遍歷集合
for (Iterator it = ts.iterator(); it.hasNext();) {
//輸出集合中的數據
System.out.println(it.next());
}
}
}
注意:以下代碼是擴展內容,了解即可:
使用匿名內部類的時候要先導入Comparator接口的包,否則會報錯。
使用匿名內部類的方式來傳遞比較器對象完成測試類中的代碼如下所示:
package cn.xuexi.set;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
* 把字符串保存到TreeSet集合中,但是要求按照字符串的長度排序。
*/
public class TreeSetDemo1 {
public static void main(String[] args)
{
/*//創建比較器對象
MyComparator myComparator = new MyComparator();
//創建集合對象
TreeSet ts=new TreeSet(myComparator);*/
//匿名對象傳遞參數
// TreeSet ts=new TreeSet(new MyComparator());
TreeSet ts=new TreeSet(
new Comparator()
{
public int compare(Object o1, Object o2) {
/*
* 這里發生多態了,那么我們先把Object類型強制轉換為String類型
* 這樣才能使用String類的對象調用length()函數進行長度比較
*/
if(!((o1 instanceof String) && (o2 instanceof String)))
{
//說明不是String類型
throw new RuntimeException("傳遞的必須是字符串");
}
//程序能夠運行到此處說明兩個對象肯定都是字符串
String s=(String)o1;
String s1=(String)o2;
//調用函數獲得字符串長度相減
int temp=s.length()-s1.length();
//如果字符串長度相同,按字符串的字典順序比較
if(temp==0)
{
//說明長度相同
return s.compareTo(s1);
}
else
{
//說明長度不同 返回長度的差值
return temp;
}
}
});
//向集合中添加字符串數據
ts.add("abc");
ts.add("jjj");
ts.add("AAAAA");
ts.add("hahahsg");
//遍歷集合
for (Iterator it = ts.iterator(); it.hasNext();) {
//輸出集合中的數據
System.out.println(it.next());
}
}
}
結論: 如果集合中的對象元素沒有使用自然排序,這個時候我們就必須給一個比較器類Comparator的對象。
3.2 關于TreeSet集合使用的總結
當我們使用TreeSet的時候,需要對元素進行排序,那么會有兩種排序方式:
1)自然排序(升序):
要存儲的元素本身需要實現Comparable接口,實現compareTo方法,那么這個類就成為了一個可比較的類。
TreeSet就會替我們對元素進行自然排序。
2)比較器排序:
我們自定義類,實現Comparator接口,實現compare方法,這個類就成為了一個比較器類,專門比較大小。
然后,我們在創建TreeSet的時候,需要把自定義比較器對象作為TreeSet的構造函數的參數傳遞給TreeSet集合,最后TreeSet就會使用比較器對象對我們的元素進行排序。這個時候,元素即便沒有實現自然排序,也沒事。
注意:向TreeSet集合中保存null的時候,只能使用自定義比較器,不能使用自然排序比較器,否則會報空指針異常。
4、Collection下的接口和實現類總結
Collection:集合體系的頂層接口。
Collection集合(接口):
|----List集合(接口):存取元素有序、可以存儲重復元素、可以存儲null、有角標,可以精確控制集合中的每一個元素。
|-----ArrayList集合(類):
1)實現了List接口;
2)底層使用可變數組;
3)方法都是圍繞著角標操作的;
4)查詢遍歷效率比較高,增刪的效率比較低;
5)屬于線程不安全的集合類;
|-----LinkedList集合(類):
1)實現了List接口;
2)底層使用鏈表結構;
3)方法都是圍繞著頭和尾來設計的;
3)查詢遍歷效率比較低,增刪的效率比較高;
4)屬于線程不安全的集合類;
|-----Vector集合(類):底層可變數組,什么都慢,但線程安全,已經被ArrayList集合取代。
|----Set集合(接口):存取元素無序(LinkedHashSet除外,因為底層有鏈表,存取有序)、不能存儲重復元素、只能存儲一個null、沒有角標,只能通過迭代器遍歷獲取元素。該集合中的方法全部來自于Collection集合中。
|-----HashSet集合(類):
1)實現Set接口;
2)底層使用哈希表結構,保證對象唯一依賴于對象的hashCode 和 equals方法;
說明:使用對象的內容先調用hashCode ()函數生成哈希碼值,然后結合數組長度計算數組下標,如果生成的下標對應的空間中已經有數據,這時需要使用對象調用equals()函數來判斷兩個對象的內容是否相同,如果不相同,就會在當前空間在畫出一個空間來保存當前的對象。如果相同,直接舍去。
3)查詢元素和添加不重復元素的效率比較快;
|-----LinkedHashSet集合(類):
1)HashSet集合類的子類,存取元素有序,元素唯一,沒有自己的特有方法;
2)底層使用鏈表+哈希表結構;
補充:哈希表用來保存數據,保證數據唯一,不重復。鏈表用來記錄數據的添加順序,保證數據存取有序。
|-----TreeSet集合(類):
1)實現Set接口;
2)底層使用二叉樹結構;
3)存儲的元素會按照一定的規則進行排序;
4)只要向TreeSet集合中存儲數據,數據所屬的類要么實現Comparable接口或者創建TreeSet類的對象時傳遞Comparator。
Comparable:自然排序
它可以讓一個對象自身具備比較功能,哪個對象需要具備比較功能,這個對象所屬的類就需要實現Comparable接口,實現其中的compareTo方法。
Comparator:自定義比較器
它是單獨的比較器,可以把這個對象單獨丟給TreeSet集合,那么這時集合中的元素就可以按照當前指定的這個比較器進行比較大小。開發者如果需要使用比較器的時候,需要使用定義類來實現Comparator接口,同時實現其中的compare方法。
Java 中集合類的關系圖,如下圖所示:
5、泛型技術
5.1、泛型的引入
集合是一個容器,可以保存對象。集合中是可以保存任意類型的對象。
List list = new ArrayList();
list.add(“abc”); 保存的是字符串對象
list.add(123); 保存的是Integer對象
list.add(new Person()); 保存的是自定義Person對象
這些對象一旦保存到集合中之后,都會被提升成Object類型。當我們取出這些數據的時候,取出來的時候一定也是以Object類型給我們,所以取出的數據發生多態了。發生多態了,當我們要使用保存的對象的特有方法或者屬性時,需要向下轉型。而向下轉型有風險,我們還得使用 instanceof關鍵字進行判斷,如果是想要的數據類型才能夠轉換,不是不能強制類型轉換,使用起來相對來說比較麻煩。
舉例:
現在要使用String類的特有方法,需要把取出的obj向下轉成String類型。
String s = (String)obj;
代碼如下:
需求:查看集合中字符串數據的長度。
分析和步驟:
1)創建一個ArrayList的集合對象list;
2)使用list集合調用add()函數向集合中添加幾個字符串數據和整數數據;
3)迭代集合分別取出集合中的數據,并查看集合中的字符串數據的長度;
package cn.xuexi.generic.demo;
import java.util.ArrayList;
import java.util.Iterator;
/*
* 泛型引入
*/
public class GenericDemo1 {
public static void main(String[] args) {
// 創建集合對象
ArrayList list = new ArrayList();
// 向集合中添加數據
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(true);
list.add(123);
//迭代集合
for (Iterator it = list.iterator(); it.hasNext();) {
Object obj = it.next();
/*
* 需求:想使用String類型的特有的函數查看字符串的長度
* Object obj = it.next();上述代碼發生多態了,想使用String類中特有的函數必須得強制類型轉換
* 可是由于集合可以存儲任意類型的對象,而這里只是適合String類型的強制類型轉換,其他數據類型會報classCastException類轉換
* 異常,如果為了不報異常只能在轉換前需要判斷,這樣做比較麻煩
* 由于這里的需求只是遍歷集合后對于取出來的數據是String類型,查看他的長度,其他數據類型不管
* 我們能否想辦法不讓運行時報錯呢,在存儲的時候就告訴我,只要是String類型的可以存儲,其他數據類型不讓存儲,這樣做起來效率會高一些
*/
String s=(String)obj;
System.out.println(s+"長度是"+s.length());
}
}
}
上述的情況會發生ClassCastException異常。發生這個異常的原因是由于集合中保存了多種不同類型的對象,而在取出的時候沒有進行類型的判斷,直接使用了強轉。
換句話也就是說我們存儲的時候,任何類型都讓我們存儲。
然后我們取的時候,卻報錯,拋異常。非常不靠譜。你應該在我存的時候就告訴我:我只能存字符串,其他引用數據類型不能存儲,那么這樣我在取出數據的時候就不會犯錯了。
假設我們在使用集合的時候,如果不讓給集合中保存類型不同的對象,那么在取出的時候即使有向下轉型,也不會發生異常。
我們回顧下以前學習的數組容器:
在前面學習數組的時候,我們知道數組這類容器在定義好之后,類型就已經確定,如果保存的數據類型不一致,編譯直接報錯。
代碼舉例如下所示:
數組是個容器,集合也是容器,數組可以在編譯的時候就能檢測數保存的數據類型有問題,如果我們在定義集合的時候,也讓集合中的數據類型進行限定,然后在編譯的時候,如果類型不匹配就不讓編譯通過, 那么運行的時候也就不會發生ClassCastException。
要做到在向集合中存儲數據的時候限定集合中的數據類型,也就是說編譯的時候會檢測錯誤。java中從JDK1.5后提供了一個新的技術,可以解決這個問題:泛型技術。
5.2、泛型技術介紹
泛型的格式:
<具體的數據類型>
使用格式:
ArrayList<限定集合中的數據類型> list = new ArrayList<限定集合中的數據類型>();
說明:給集合加泛型,就是讓集合中只能保存具體的某一種數據類型。
使用泛型改進以上程序中存在的問題:
說明:由于創建ArrayList集合的時候就已經限定集合中只能保存String類型的數據,所以編譯的時候保存其他的數據類型就會報錯,這樣就達到了我們上述保存數據的目的了。
小結:
一、 泛型的好處?
1)解決了集合中存儲數據的不安全性;
2)把運行時可能發生的異常,放在編譯時作為編譯錯誤處理了,避免了運行時的異常;
3)省略了代碼中的強制類型轉換的書寫;
二、注意事項:
1)泛型只支持引用數據類型(類類型或接口類型等),泛型不支持基本數據類型:
2)泛型不支持數據類型以繼承的形式存在,要求前后泛型的數據類型必須一致:
3)在jdk1.7之后,泛型也可以支持如下寫法:
4)泛型兼容老版本,但是盡量避免,開發中不建議如下寫法:
說明:上述寫法是左邊有泛型,右邊沒有泛型。
注意:現在的開發中,泛型已經成為編寫代碼的規范。
6.3、泛型技術的簡單應用(課下多書寫)
練習1:泛型存儲自定義對象版本。
分析和步驟:
1)定義一個Student學生類,在這個類中定義兩個屬性name和age,并生成get和set方法;
2)創建一個測試類GenericDemo2類,在這個類中創建ArrayList<Student>類的對象list;
3)使用集合對象list調用add()函數向集合中添加幾個自定義的Student的對象,并賦值;
4)迭代遍歷集合;
以下是Student類:
package cn.xuexi.generic.demo;
/*
* 描述學生
*/
public class Student {
//屬性
String name;
int age;
public Student(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;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
以下是測試類GenericDemo2:
package cn.xuexi.generic.demo;
import java.util.ArrayList;
import java.util.Iterator;
/*
* 使用泛型存儲自定義對象
*/
public class GenericDemo2 {
public static void main(String[] args) {
// 創建集合
ArrayList<Student> list = new ArrayList<Student>();
//向集合中添加自定義對象Student
list.add(new Student("班長",19));
list.add(new Student("班花",18));
list.add(new Student("班草",23));
list.add(new Student("副班長",19));
//遍歷集合
for (Iterator<Student> it = list.iterator(); it.hasNext();) {
Student s = it.next();
System.out.println("我叫"+s.getName()+",我今年"+s.getAge());
}
}
}
練習2:使用泛型完成之前學習過的自定義比較器類Comparator。
需求:向TreeSet集合中存儲String類型的字符串,按長度比較字符串大小,如果長度相同再按自然排序比較。
分析和步驟:
1)自定義一個比較器類MyComparator 實現Comparator<String>接口;
2)在比較器類MyComparator復寫Comparator<String>接口中的compare()函數,在這個函數中按字符串長度比較字符串大小,如果長度相同,按自然排序比較;
3)定義一個測試類GenericDemo3 ;
4)在這個測試類中的main函數中創建TreeSet<String>集合對象set,自定義比較器對象new MyComparator()作為TreeSet<String>集合構造函數參數傳遞
5)使用set集合向TreeSet集合中添加幾個字符串對象數據;
6)遍歷集合;
以下是自定義比較器類MyComparator:
package cn.xuexi.generic.demo;
import java.util.Comparator;
/*
* 自定義比較器類
* 由于要向TreeSet集合中存儲String類型的數據,所以這里比較的也必須是String類型的數據,
* 所以這里的泛型直接寫String類型即可
*/
public class MyComparator implements Comparator<String>{
public int compare(String s1, String s2) {
// 按長度比較
int temp=s1.length()-s2.length();
if(temp==0)
{
//長度相同,按自然排序
return s1.compareTo(s2);
}
else
{
//長度不同 返回temp
return temp;
}
}
}
測試類GenericDemo3類:
package cn.xuexi.generic.demo;
import java.util.Iterator;
import java.util.TreeSet;
/*
* 向TreeSet集合中存儲
*/
public class GenericDemo3 {
public static void main(String[] args) {
// 創建集合對象
TreeSet<String> set=new TreeSet<String>(new MyComparator());
//向集合中添加數據
set.add("AAAAAAA");
set.add("aaa");
set.add("aaaaaaa");
set.add("bb");
set.add("AAAAAAA");
set.add("hsgsh");
set.add("d");
//迭代集合
for (Iterator<String> it = set.iterator(); it.hasNext();) {
String s = it.next();
System.out.println(s);
}
}
}
補充:泛型的擦除技術:
泛型其實是一種編譯器技術,泛型技術主要是在程序的編譯的時候限定程序中的數據類型,一旦程序編譯完成之后,生成的class文件中是沒有泛型的。
因為既然使用泛型技術限制了數據類型之后并且編譯通過了,到運行這里集合中的數據類型肯定已經統一了,沒有必要在class文件中加上泛型了。