Java筆記 - 泛型

1.泛型的提出
先來看一段代碼

//創建ArrayList集合,向其中添加字符串
ArrayList al = new ArrayList();
al.add("abc");
al.add("def");
al.add(new Integer(123));//①

Iterator it = al.iterator();
while(it.hasNext()){
    String str = (String)it.next();
    System.out.println(str);
}

編譯通過,但是運行發現在①處報錯了,因爲在添加Integer對象後,輸出時轉成String對象就會出現類型轉換錯誤。這樣就會造成安全隱患,我們希望能在編譯的時候就提示我們錯誤。所以可以對ArrayList進行類型定義。

ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("def");
//al.add(new Integer(123));編譯報錯,類型錯誤,String類型的ArrayList中無法添加Integer類型的對象

Iterator<String> it = al.iterator();//取出對象也定義String類型,就不需要再做強轉了
while(it.hasNext()){
    String str = it.next();
    System.out.println(str);
}

這樣就把運行時錯誤轉移到編譯時錯誤,因爲我們已經對ArrayList集合中的元素類型做了定義,所以就無法向其中加入Integer類型的元素。

泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在使用/調用時傳入具體的類型(類型實參)。

//ArrayList接口定義:
public class ArrayList<E> extends AbstractList<E> 
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }

    //...省略掉其他具體的定義過程

}

在上述例子中,String就是類型實參,而ArrayList接口定義中的E就是類型形參,當給形參傳入String的時候,就確定其具體類型。

2.泛型類、接口、方法

interface Inter<Q> {//泛型接口
    public  void show1(Q q);
}

public class ClassDemo<T, Q> implements Inter<Q>{//泛型類
    private T t;
    public void set(T t){
        this.t = t;
    }
    public T get(){
        return t;
    }

    public <W> void show(W w){//泛型方法
        System.out.println("show:"+w.toString());
    }

    public  void show1(Q q) {
        // TODO Auto-generated method stub

        System.out.println("show1:"+q);
    }

}


public class Demo {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ClassDemo<String,Double> cd = new ClassDemo<String,Double>();
        cd.set("abc");
        //cd.set(new Integer(12));//類型不匹配
        System.out.println(cd.get());
        cd.show(new Integer(12));
        cd.show1(new Double(123.123));
    }
}

輸出結果:
show:12
abc
show1:123.123

3.類型通配符

public static void main(String[] args) {

    ArrayList<String> al = new ArrayList<String>(); 
    al.add("abc");
    al.add("hehe");

    ArrayList<String> al2 = new ArrayList<String>();    
    al2.add("abc2");
    al2.add("hehe2");

    printCollection(al);
    printCollection(al2);
}

public static void printCollection(Collection<?> al){//使用Collection,LinkedList,HashSet,TreeSet...所有的集合都可以接收
    Iterator<String> it = al.iterator();

    while(it.hasNext()){
        System.out.println(it.next());
    }
}

類型通配符?可以用來表示未知的引用類型
4.泛型的上限和下限
上限

public static void main(String[] args) {

    ArrayList<Person> al1 = new ArrayList<Person>();    
    al1.add(new Person("abc",30));
    al1.add(new Person("abc4",34));

    ArrayList<Student> al2 = new ArrayList<Student>();
    al2.add(new Student("stu1",11));
    al2.add(new Student("stu2",22));

    ArrayList<Worker> al3 = new ArrayList<Worker>();
    al3.add(new Worker("stu1",11));
    al3.add(new Worker("stu2",22));

    ArrayList<String> al4 = new ArrayList<String>();
    al4.add("abcdeef");
    //al1.addAll(al4);//錯誤,類型不匹配。

    al1.addAll(al2);
    al1.addAll(al3);

    System.out.println(al1.size());
}

查閱API文檔,添加集合的泛型爲boolean addAll(Collection< ? extends E> c),< ? extends E>表示可以接收E類型或者E的子類型對象,E是類型的上限。因爲即使接收的類型是Student或Worker,但是addAll後都會類型提升爲Person,而Person是可以用來接收Student和Worker的,並且在取出的時候也是Person類型的。
一般情況下,在存儲元素的時候都是用上限,因爲這樣取出都是按照上限類型來運算的。不會出現類型安全隱患。

下限

public static void main(String[] args) {

    TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());
    al1.add(new Person("abc4",34));
    al1.add(new Person("abc1",30));
    al1.add(new Person("abc2",38));

    TreeSet<Student> al2 = new TreeSet<Student>(new CompByName());
    al2.add(new Student("stu1",11));
    al2.add(new Student("stu7",20));
    al2.add(new Student("stu2",22));

    TreeSet<Worker> al3 = new TreeSet<Worker>();
    al3.add(new Worker("stu1",11));
    al3.add(new Worker("stu2",22));

}
/*
 * class TreeSet<Worker>
 * {
 *      Tree(Comparator<? super Worker> comp);
 * }
 */

class CompByName implements Comparator<Person>{
    public int compare(Person o1, Person o2) {
        int temp = o1.getName().compareTo(o2.getName());
        return temp==0? o1.getAge()-o2.getAge():temp;
    }
}

class CompByStuName implements Comparator<Student>{
    public int compare(Student o1, Student o2) {
        int temp = o1.getName().compareTo(o2.getName());
        return temp==0? o1.getAge()-o2.getAge():temp;
    }
}

class CompByWorkerName implements Comparator<Worker>{
    public int compare(Worker o1, Worker o2) {
        int temp = o1.getName().compareTo(o2.getName());
        return temp==0? o1.getAge()-o2.getAge():temp;
    }
}

在上面的例子中,我們創建了三個TreeSet集合,並分別向其中存入Person、Student、Worker類型的元素。查看API文檔中關於TreeSet構造函數的說明:public TreeSet(Comparator< ? super E> comparator)構造一個新的空 TreeSet,它根據指定比較器進行排序。所以爲他們分別創建比較器並作爲構造函數的參數進行傳遞。但是觀察發現其實Student和Worker的比較器都是依賴於Person的比較器。而如果我們想要把集合中的元素取出來比較以確定其位置,就要對取出來的元素進行接收,這裏就是由比較器接收。不管我們存放的是什麼類型的元素,我們在接收的時候,可以用該類型來接收,也可以用該類型的父類型來接收。就是說如果我們在al1集合中存入Person和Student類型的元素,在取出來進行比較的時候,接收的時候需要用Person類型來進行接收以保證所有取出來的類型都能接收到。

一般情況下,通常對集合中的元素進行取出操作時,可以使用下限。這樣可以保證所有取出來的類型都能進行接收

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章