1. 程式人生 > >29-泛型--泛型限定(泛型上限+泛型下限+上限的體現+下限的體現+萬用字元的體現)+集合查閱的技巧

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