天天看點

Java筆記:泛型、限定通配符與非限定通配符1 泛型2 限定通配符與非限定通配符3 PECS(Producer Extends Consumer Super)原則

目錄

  • 1 泛型
  • 2 限定通配符與非限定通配符
    • 2.1 限定通配符
    • 2.2 非限定通配符
  • 3 PECS(Producer Extends Consumer Super)原則
    • 3.1 Producer Extends
    • 3.2 Consumer Super

1 泛型

在集合中存儲對象并在使用前進行類型轉換是多麼的不友善。泛型防止了那種情況的發生。 它提供了編譯期的類型安全,確定你隻能把正确類型的對象放入集合中,避免了在運作時出現ClassCastException。

  • 不使用泛型
/**
 * 這樣做的一個不好的是Box裡面現在隻能裝入String類型的元素,今後如果我們需要裝入Integer等其他類型的元素,
 * 還必須要另外重寫一個Box,代碼得不到複用,使用泛型可以很好的解決這個問題。
 */
public class Box {
    private String object;

    public void set(String object) {
        this.object = object;
    }

    public String get(){
        return object;
    }
}
           
  • 使用泛型
public class  GenericBox<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
           

2 限定通配符與非限定通配符

2.1 限定通配符

限定通配符對類型進行了限制。有兩種限定通配符:

  1. <? extends T>它通過確定類型必須是T及T的子類來設定類型的上界;
  2. <? super T>它通過確定類型必須是T及T的父類設定類型的下界;

泛型類型必須用限定的類型來進行初始化,否則會導緻編譯錯誤。

2.2 非限定通配符

表示了非限定通配符,因為可以用任意類型來替代。

public class BoundaryCharExample {
    //查找一個泛型數組中大于某個特定元素的個數
    public static <T> int countGreaterThan(T[] array,T elem){
        int count = 0;
        for (T e : array) {
            if (e > elem) { // compiler error
                ++count;
            }
        }
        return count;
    }
    /*
    * comliler error:但是這樣很明顯是錯誤的,
    * 因為除了short, int, double, long, float, byte, char等原始類型,
    * 其他的類并不一定能使用操作符" > "
    * 解決 --> 使用限定通配符/邊界符
    * */
}
           

使用限定通配符改進:

public class BoundaryCharExample2 {
    public static <T extends Comparable<T>> int countGreaterThan(T[] array,T elem){
        //<T extends Comparable<T>>就是通配符,類型必須是 Comparable<T>及其子類
        int count = 0;
        for (T e : array) {
            if (e.compareTo(elem)>0) {
                ++count;
            }
        }
        return count;
    }
}
           

3 PECS(Producer Extends Consumer Super)原則

  • Producer Extends:如果你需要一個隻讀List,用它來produce T,那麼使用? extends T;
  • Consumer Super:如果你需要一個隻寫List,用它來consume T,那麼使用? super T;

3.1 Producer Extends

對于實作了<? extends T>的集合類隻能将它視為 Producer 向外提供(get)元素, 而不能作為 Consumer 向外擷取(add)元素。

public class GenericExample {
    public static void main(String[] args) {
        List<? extends Fruit> fruits = new ArrayList<Apple>();
        //? extends Fruit表示的是Fruit及其子類
        
        // Compile Error: can't add any type of object:
        //fruits.add(new Apple());
        //fruits.add(new Orange());
        //fruits.add(new Fruit());
        //fruits.add(new Object());
        //fruits.add(null); // Legal but uninteresting
    }
}
           

Compile Error: can’t add any type of object:

從編譯器的角度去考慮,List<? extends Fruit> fruits自身可以有多種含義:

List<? extends Fruit> fruits = new ArrayList<Fruit>();

List<? extends Fruit> fruits = new ArrayList<Apple>();

List<? extends Fruit> fruits = new ArrayList<Orange>();

// 這裡Apple和Orange都是Fruit子類
           
  1. 當我們嘗試add一個Apple的時候,fruits可能指向new ArrayList< Orange >();
  2. 當我們嘗試add一個Orange的時候,fruits可能指向new ArrayList< Apple >();
  3. 當我們嘗試add一個Fruit的時候,這個Fruit可以是任何類型的Fruit,而fruits可能隻想是某種特定類型的Fruit,是以編譯器無法識别,報錯。

應用示例:

public class GenericReading {
    private List<Apple> apples = Arrays.asList(new Apple());
    private List<Fruit> fruit = Arrays.asList(new Fruit());

    private class Reader<T>{ //Reader<T> 是自定義的泛型類
         /*T readExact(List<T> list){ 
             return list.get(0);
         }*/
        T readExact(List<? extends T> list){// 使用通配符來解決這個問題
            // ? extends T 表示 T 及 T 的子類
            return list.get(0); //TODO :get()方法
        }
    }

    @Test
    public void test(){
        Reader<Fruit> fruitReader=new Reader<Fruit>();
        //Fruit f=fruitReader.readExact(apples);
        // 使用 readExact(List<T> list)  
        // Errors: List<Fruit> cannot be applied to List<Apple>.
        
        Fruit f=fruitReader.readExact(apples);//正确
        System.out.println(f);
    }
}
           

3.2 Consumer Super

對于實作了<? super T>的集合類隻能将它視為 Consumer 向外擷取(add)元素, 而不能作為 Producer 向外提供(get)元素。

從編譯器的角度考慮,對于List<? super Apple> list,它可以有下面幾種含義:

List<? super Apple> list = new ArrayList<Apple>();
List<? super Apple> list = new ArrayList<Fruit>();
List<? super Apple> list = new ArrayList<Object>();
           

當我們嘗試通過list來get一個Apple的時候,可能會get得到一個Fruit,這個Fruit可以是Orange等其他類型的Fruit,是以編譯器無法識别,報錯。

應用示例:

public class GenericWriting {
    private List<Apple> apples = new ArrayList<Apple>();
    private List<Orange> oranges = new ArrayList<Orange>();
    private List<Fruit> fruit = new ArrayList<Fruit>();

    <T> void writeExact(List<T> list, T item) {
        list.add(item); //TODO :這裡是 add
    }

    // ? super T 
    // T 及 T 的父類
    <T> void writeWithWildcard(List<? super T> list, T item) {
        list.add(item);
    }

    void func1(){
        writeExact(apples,new Apple());
        writeExact(fruit,new Apple());
    }

    void func2(){
        writeWithWildcard(apples, new Apple());
        writeWithWildcard(fruit, new Apple());
    }

    @Test
    public void test(){
        func1();
        func2();
    }
}
           

JDK 8 Collections.copy() 源碼:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        //dest 就是 隻寫的 List
        //src 就是 隻讀的 List
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }
           

RandomAccess是一個空的、用來标記的接口;

用處是當要實作某些算法時,會判斷目前類是否實作了RandomAccess接口,會選擇不同的算法;

接口RandomAccess中内容是空的,隻是作為标記用。比如List下的ArrayList和LinkedList。其中ArrayList實作了RandomAccess。而LinkedList沒有。我們可以利用instanceof來判斷哪一個是實作了RandomAccess。分辨出兩個集合。其中ArrayList使用for循環周遊快,而LinkedList使用疊代器快。那麼通過分辨,不同的集合使用不同的周遊方式;