1. 程式人生 > >Java泛型中的萬用字元

Java泛型中的萬用字元

1、上界萬用字元

首先,需要知道的是,Java語言中的陣列是支援協變的,什麼意思呢?看下面的程式碼:

    static class A extends Base{
        void f() {
            System.out.println("A.f");
        }
    }

    static class B extends A {
        void f() {
            System.out.println("B.f");
        }
    }

    static class C extends B {
        void
f() { System.out.println("C.f"); } } static { A[] arrayA = new A[3]; arrayA[0] = new A(); arrayA[1] = new B(); arrayA[2] = new C(); for (A item : arrayA) { item.f(); } } //output A.f B.f C.f

我們明明讓陣列的型別為A,但是向其中加入B、C也是可以行得通的,為什麼呢?我們發現B繼承了A,屬於A的子類,C繼承了B,屬於B的子類,Java中的繼承是可以傳遞的,所以C依然屬於A的子類,所以B和C都是A的子類,另外一點,在Java中,型別向上轉換是非常自然的,不需要強制轉換會自動進行,也就是說,B和C的例項都可以自動轉換為型別A的例項。好了,有了這樣的背景知識,我們可以來看一下上界通配了,在java中,可以使用

        A a = new B();
        A b = new C();
        C c = new C();

       List<? extends A> list = new ArrayList<A>();

      list.add(a);
      list.add(b);
      list.add(c);

我們覺得很自然這樣做是無可厚非的,對吧?但是編譯器很顯然不允許我們這樣做,為什麼?我們的list的型別使用了上界萬用字元啊,而且匹配的是所有A的子類,而我們add的都是A的子類啊,為什麼不可以呢?我們再來看一下

    void joke(List<? extends A> list) {
        A a = new B();
        A b = new C();
        C c = new C();

        list.add(a);
        list.add(b);
        list.add(c);
    }

當然上面的程式碼是無法通過編譯的,我們分析一下為什麼,記住

    private static void jokeIn(List<?extends A> list) {
        //
    }

    static {
        List<? extends A> list = new ArrayList<>();
        List<? extends B> list1 = new ArrayList<>();
        List<? extends C> list2 = new ArrayList<>();

        jokeIn(list);
        jokeIn(list1);
        jokeIn(list2);
    }

好吧,問題來了,當我們傳到joke方法中的引數是List

     List<? extends A> list = Arrays.asList(a, b);

Arrays.asList(T … data)使用了ArrayList的一個建構函式:

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

可以看到使用了陣列的協變,使得我們可以在Arrays.asList裡面傳遞進去所以A的子類物件。

2、下界萬用字元

上界定義了可以達到了最高點,超出就是違法的;而下界則是說明了底線,你只能比底線更高階,低於底線就是違法的。在java裡面,可以使用

    void joke(List<? super A> list) {
        A a = new B();
        A b = new C();
        C c = new C();

        list.add(a);
        list.add(b);
        list.add(c);
    }

此時的joke方法的引數是List

    static {
        List<? super A> list = new ArrayList<>();
        List<? super B> list1 = new ArrayList<>();
        List<? super C> list2 = new ArrayList<>();

        jokeIn(list);
        jokeIn(list1); // error
        jokeIn(list2); //error
    }

好吧,問題出現了,我們可以將List

       List<? super A> lists = new ArrayList<>();
        lists.add(a);
        lists.add(b);
        lists.add(c);

解釋一下,lists裡面的元素型別是這樣一種型別,這種型別是A的基類,我們只是界定了下界,只要高於這個下界,就可以被lists接收,而b、c的基類都是A,可以被lists接收,所以上面的程式碼是可以工作的。

3、無界萬用字元

有了上界和下界,還有無界,需要說明的一點是,不能同時使用上界和下界,因為有無界啊(開玩笑的)!!
我們在java中使用

我是想要java的範型來編寫這段程式碼,我在這裡並不是想使用原生類
型,但是在當前這種情況下,泛型引數可以持有任何型別。

                     ----來自《java程式設計思想》15.10.3 無界萬用字元(686頁)

使用無界萬用字元的一種場景是:如果向一個使用

    static class Holder<T> {
        private T data;

        public Holder() {

        }

        public Holder(T data) {
            this.data =data;
        }

        public T getData() {
            return data;
        }

        public void setData(T data) {
            this.data = data;
        }
    }


    static <T> void actual(Holder<T> holder) {
        System.out.println(holder.getData());
    }

    static void func(Holder<?> holder) {
        actual(holder);
    }

    static {

        Holder<?> holder = new Holder<>("hujian");

        func(holder);

    }

可以看到,actual的引數是具體的T,而func的引數是無界的

    static <T> void actual(Holder<T> holder) {
        T data = holder.getData();
        if (data instanceof String) {
            actual((String) data);
        } else if (data instanceof Integer) {
            actual((Integer) data);
        } else if (data instanceof Double) {
            actual((Double) data);
        }
    }

    static void actual(String holder) {
        System.out.print("string:" + holder);
    }

    static void actual(Integer holder) {
        System.out.println("Integer:" + holder);
    }

    static void actual(Double holder) {
        System.out.println("double:" + holder);
    }

    static void func(Holder<?> holder) {
        actual(holder);
    }