29-泛型--泛型限定(泛型上限+泛型下限+上限的體現+下限的體現+萬用字元的體現)+集合查閱的技巧
一、泛型上限
1、迭代並列印集合中的元素
(1)集合即可能是List,也可能是Set,用Collection提高擴充套件性
(2)當容器中存放的元素不確定,且裡面不準備使用具體型別的情況下,使用萬用字元
注:
(1)萬用字元:?,未知型別。不明確型別時,可以用?來表示,意味著什麼型別都可以傳入
(2)執行時,引數中的泛型<>只要有一個型別具備了,裡面就都是統一型別
/** * 迭代並列印集合中的元素 * (1)使用Collection提高擴充套件性 * (2)容器中存放的元素不確定,且裡面不準備使用具體的型別,使用萬用字元?,意味著什麼型別都可以傳入 */ public static void printCollection(Collection<?> coll){ for (Iterator<?> it = coll.iterator(); it.hasNext(); ){ // String str = it.next(); //不使用具體的型別 //不能明確型別 System.out.println(it.next()); } }
2、<T>與<?>的區別
(1)<T>:如果有指定<T>,就意味著<T>能代表一個具體型別,並能被操作(<T>可以被接收)
(2)<?>:僅在不明確型別,且不對返回值型別進行操作時,用?來表示。呼叫的是Object中的方法 -- 用得較多
/** * 返回值型別:T */ public static <T> T method(Collection<T> coll){ Iterator<T> it = coll.iterator(); T t = it.next(); //返回值為T,T這個型別是可以被操作的 return t; }
3、學生類繼承自Person類(class Student extends Person),但在Collection中,Person是一個單獨的型別,Student也是一個單獨的型別。Collection<>只能存一個具體的型別。如果Collection<Person>型別的引數接收的是儲存Student型別元素的集合,即Collection<Person> coll = new ArrayList<Student>();,左右兩邊泛型不匹配(一般情況下,寫泛型要具備的特點是:左右兩邊泛型要一致),編譯報錯。本來容器想裝Person,結果new的只能裝Student,其他的Person子類裝不了,不合適
//Collection<Person>:只能接收儲存Person物件的集合
public static void method(Collection<Person> coll){
Iterator<Person> it = coll.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("zhangsan"));
list.add(new Student("xiaoqi"));
// method(list); //編譯報錯。method()方法的引數 Collection<Person>:Person是一個單獨的型別,Student也是一個單獨的型別
}
想要接收Person的子類物件,Collection<? extends Person>,即 ?全都是來自Person的子類
//Collection<? extends Person>:只能接收儲存Person或Person子類的集合 -- 泛型的限定
public static void method(Collection<? extends Person> coll){
Iterator<? extends Person> it = coll.iterator();
while (it.hasNext()){
Person p = it.next(); //存入的都是Person或Person子類,可以用Person接收(此處是多型)
System.out.println(p.getName()); //多型,方法 編譯看左邊,執行看右邊
// System.out.println(it.next());
}
}
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("zhangsan"));
list.add(new Student("xiaoqi"));
method(list);
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc1");
list1.add("abc2");
// method(list1); //編譯報錯,只能接收儲存Person或Person子類的集合
}
4、Collection<? extends Xxx>:只接收Xxx型別或者Xxx型別的子類 -- 泛型的限定(上限)。取出時,用Xxx型別接收,此時是多型(呼叫方法:編譯看左邊,執行看右邊)
注:Collection<?> <==> Collection<? extends Object>
二、泛型下限
1、對型別進行限定
(1)? extends E:接收E型別或者E的子型別物件 -- 上限(擴充套件性較強,使用較多)
(2)? super E:接收E型別或者E的父型別物件 -- 下限(此種做法不是很多)
2、迭代器的泛型一定和獲取迭代器物件集合的泛型一致
//Collection<? super Student>:只能接收儲存Student或Student父類(Person)的集合,不能接收儲存Worker的集合 -- 下限
//此種做法不多(應用不明顯)
public static void method(Collection<? super Student> coll) {
//迭代器的泛型一定和獲取迭代器物件集合的泛型一致
Iterator<? super Student> it = coll.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
三、上限的體現
1、boolean addAll(Collection<? extends E> c):將指定collection中的所有元素都新增到此Collection中(擴充套件性很強)
class MyCollection<E> {
//在明確型別E的情況下,新增的就是已知型別E。但一次新增一個 比較慢
public void add(E e) {
}
//集合中裝什麼型別,在新增新集合的時候,新集合也是什麼型別
// public void add(MyCollection<E> e) {
// }
//Collection中addAll()方法的實現原理
//存元素時使用上限(<? extends E>),擴充套件型別。取出時,不存在型別安全隱患
public void addAll(MyCollection<? extends E> e) {
}
}
2、一般在儲存元素時使用上限(? extends E)。因為一旦確定好型別,存入的就都是E或E的子類,取出時都按照上限型別E來運算,不會出現型別安全隱患
注:上限使用較多(可以擴充套件型別)
/*
ArrayList list1 = new ArrayList();
list1.add(new Person("zhangsan"));
ArrayList list2 = new ArrayList();
list1.add("abc");
//沒有指定泛型,是Object,什麼都可以存入。取出時,型別會存在安全隱患(型別不匹配)
list1.add(list2);
*/
ArrayList<Person> list1 = new ArrayList<Person>();
list1.add(new Person("zhangsan"));
ArrayList<Student> list2 = new ArrayList<Student>();
list2.add(new Student("xiaoqi"));
ArrayList<String> list3 = new ArrayList<String>();
list3.add("abc");
//addAll(Collection<? extends E> c):取出時,按Person型別來取,Peron可以接收學生等子類,不存在型別安全隱患
list1.addAll(list2);
// list1.addAll(list3); //錯誤。型別不匹配
四、下限的體現
1、TreeSet的部分建構函式
(1)TreeSet(Collection<? extends E> c)
(2)TreeSet(Comparator<? super E> comparator)
2、Student extends Person,若想按照姓名排序,需自定義比較器。無論是Student還是Person,用的都是同一段程式碼,只是泛型不同而已,且用的都是父型別Person的方法
class CompByName implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
int temp = p1.getName().compareTo(p2.getName());
return temp == 0 ? p1.getAge() - p2.getAge() : temp;
}
}
class CompByStuName implements Comparator<Student> {
@Override
public int compare(Student p1, Student p2) {
int temp = p1.getName().compareTo(p2.getName());
return temp == 0 ? p1.getAge() - p2.getAge() : temp;
}
}
若想對Student、Worker統一地進行共性型別排序,怎麼排?
Student、Worker的排序方式依賴於Person的排序方式。比較有一個特點,要把集合中的元素取出來比較存放位置。要把集合中的元素取出來進行比較,就要把取出來的元素進行接收。接收時,存放的是什麼型別不重要,最重要的是接收進來的型別要大一些。就意味著Student、Worker都可以用Person的比較器
class Xxx implements Comparator<? super Student> {}
3、何時使用下限?
通常對集合中的元素進行取出操作時,可以使用下限。存什麼型別,可以用 這個型別或這個型別的父型別 來接收 -- 較為少用
eg:比較器就是取集合中的元素,用一個父型別來接收,保證取出的全都可以被接收到
說明:一個容器TreeSet<Person>中既能新增Person物件,又能addAll()新增子類物件,意味著有Person又有Student、Worker。想統一對這些物件進行排序,排的時候要把它們取出來比較,拿什麼來接收?此時應該拿Person來接收。意味著如果儲存的全部都是Student、Worker,也都能用Person接收。只要用的是Person的方法來進行比較,全用此方法即可
五、萬用字元的體現
1、boolean containsAll(Collection<?> c):contains()方法的原理是在用equals()做判斷,而equals()方法任何物件都具備,且其引數是Object
原因:
(1)任何物件都有equals()方法
(2)equals()可以接收任意物件(比的是地址值)
ArrayList<Person> list1 = new ArrayList<Person>();
list1.addAll(new Person("zhangsan"));
ArrayList<Person> list2 = new ArrayList<Person>();
list2.addAll(new Person("xiaoqi"));
ArrayList<String> list3 = new ArrayList<String>();
list3.add("abc");
//不報錯。因為 boolean containsAll(Collection<?> c)
list1.containsAll(list2);
list1.containsAll(list3);
2、何時使用<?> ?
(1)只要裡面用的全都是Object的方法,就用<?>
(2)有的方法返回一個集合,但不知道返回的集合是什麼型別,用<?> (返回<?>就用<?>接收即可)
六、集合查閱的技巧
1、需要唯一嗎?
需要:Set
需要指定順序嗎?
需要:TreeSet
不需要:HashSet
不需要,但想要有一個和儲存一致的順序(有序):LinkedHashSet
不需要:List
需要頻繁增刪嗎?
需要:LinkedList
不需要:ArrayList
需要同步(效率低)嗎?
需要:Vector
2、怎樣記住每一個容器的結構和所屬體系呢?
看名字
(1)字尾名就是該集合所屬的體系(通過體系可以判斷其特點)
|-- List :有角標、有序、可重複
|-- ArrayList
|-- LinkedList
|-- Set :元素唯一,無序
|-- HashSet
|-- TreeSet
(2) 字首名就是該集合的資料結構
看到 array:就要想到陣列,就要想到查詢快(有角標)
看到 link:就要想到連結串列,就要想到增刪快,就要想到 add、get、remove + first/last 的方法
看到 hash:就要想到雜湊表,就要想到唯一性,就要想到元素需要覆蓋hashCode()方法和equals()方法
看到 tree:就要想到二叉樹,就要想到排序,就要想到兩個介面Comparable、Comparator
注:通常,這些常用的集合容器都是不同步的。Vector是同步的
3、看到排序,想到元素需要具備比較性,就要使用Comparable或Comparator