0xe3aad

0xe3aad

Java泛型

擦拭法#

編譯器把類型<T>視為 Object#

public class Pair<T> {
    private final T first;
    private final T last;

    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }

    public T getFirst() {
        return first;
    }

    public T getLast() {
        return last;
    }
}

Java 中泛型是在編譯階段的,編譯器將上述代碼經過擦除法,JVM 實際看到的代碼如下所示:

public class Pair {
    private final Object first;
    private final Object last;

    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }

    public Object getFirst() {
        return first;
    }

    public Object getLast() {
        return last;
    }
}

編譯器根據<T>實現安全的強制轉型#

Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();

編譯器根據 T 的類型安全強轉:

Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();

擦拭法的局限性#

  • <T>不能是基本類型,因為擦除法要將其替換為Object
  • 無法獲取攜帶泛型的Class,因為泛型是在編譯期,所以獲取到的都為Pair<Object>
    例如:
@Test
void testGetClass() {
    Pair<String> p1 = new Pair<>("Hello", "World");
    Pair<Integer> p2 = new Pair<>(114, 514);
    Class c1 = p1.getClass();
    Class c2 = p2.getClass();

    assertEquals(c1, c2);
    assertEquals(c1, Pair.class);
}
  • 不恰當的覆寫方法
class Pair<T> {
    public boolean equals(T t) {}
}

這是因為,定義的equals(T t)方法實際上會被擦拭成equals(Object t),而這個方法是繼承自Object的,編譯器會阻止一個實際上會變成覆寫的泛型方法定義。

泛型繼承#

類可以繼承一個泛型類。在繼承了泛型類型的情況下,子類可以獲取父類的泛型類型。例如:IntPair可以獲取到父類的泛型類型Integer

public class IntPair extends Pair<Integer> {

    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}

class IntPairTest {
    @Test
    void getSuperParameterizedType() {
        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) t;
            Type[] types = pt.getActualTypeArguments(); // may have many types
            Type firstType = types[0];
            Class<?> typeClass = (Class<?>) firstType;
            assertEquals(typeClass, Integer.class);
        }
    }
}

extends通配符#

public class PairHelper {
    static int add(Pair<Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

image

如上,add函數接收的參數類型為Pair<Number>,所以我們傳入Pair<Number>是沒有問題的;但傳入Pair<Integer>時,會得到一個編譯錯誤,這是因為雖然IntegerNumber的子類,但是Pair<Integer>卻不是Pair<Number>的子類。

為了解決這個問題,可以使用Pair<? extends Number>,這樣就可以接收泛型類型為Number子類的Pair類型了:

static int add(Pair<? extends Number> p) {
    Number first = p.getFirst();
    Number last = p.getLast();
    return first.intValue() + last.intValue();
}

@Test
void addNumberBased() {
    assertEquals(579, add(new Pair<Integer>(123, 456)));
}

讀取#

  • 能夠從List<? extends Number>中獲取到Number類型,因為其包含的元素是Number類型或者Number的自類型。
  • 不能從List<? extends Number>中獲取到Integer類型,因為其保存的元素可能是Double
  • 不能從List<? extends Number>中獲取到Double類型,因為其保存的元素可能是Integer

寫入#

  • 不能添加NumberList<? extends Number>中,因為其保存的元素可能是IntegerDouble
  • 不能添加IntegerList<? extends Number>中,因為其保存的元素可能是Double
  • 不能添加NumberList<? extends Number>中,因為其保存的元素可能是Integer
public class ExtendsTest {
    List<? extends Number> nums1 = new ArrayList<Number>();
    List<? extends Number> nums2 = new ArrayList<Integer>();
    List<? extends Number> nums3 = new ArrayList<Double>();

    void get() {
        Number x = nums1.get(0);
        // Integer y = nums2.get(0); // compile error
        // Double z = nums3.get(0);  // compile error
    }

    void set() {
        /*
        nums1.add(new Number() {
            @Override
            public int intValue() {
                return 0;
            }

            @Override
            public long longValue() {
                return 0;
            }

            @Override
            public float floatValue() {
                return 0;
            }

            @Override
            public double doubleValue() {
                return 0;
            }
        });
         */ // compile error
        // nums2.add(new Integer(1));    // compile error
        // nums3.add(new Double(1.2)); // compile error
    }
}

super通配符#

<? super T> 描述了通配符下界,即具體的泛型參數需要滿足條件:泛型參數必須是 T 類型或它的父類。

讀取#

  • 我們不能保證可以從List<? super Integer>類型對象中讀取到 Integer 類型的數據,因為其可能是 List<Number> 類型的。
  • 我們不能保證可以從List<? super Integer>類型對象中讀取到 Number 類型的數據,因為其可能是 List<Object> 類型的。
  • 唯一能夠保證的是,我們可以從List<? super Integer>類型對象中獲取到一個 Object 對象的實例。

寫入#

對於上面的例子中的 List<? super Integer> array 對象:

  • 我們可以添加 Integer 對象到 array 中,也可以添加 Integer 的子類對象到 array 中,因為Integer及其父類都可以接受Integer的子類對象。
  • 我們不能添加 Double/Number/Object 等不是 Integer 的子類的對象到 array 中。

image

extends vs super#

對於List<? super Integer> l1#

  • 正確的理解:? super Integer 限定的是泛型參數。令 l1 的泛型參數是 T, 則 TIntegerInteger 的父類,因此 IntegerInteger 的子類的對象就可以添加到 l1 中.
  • 錯誤的理解:? super Integer限定的是插入的元素的類型,因此只要是 IntegerInteger 的父類的對象都可以插入 l1 中。

對於List<? extends Integer> l2:#

  • 正確的理解:? extends Integer 限定的是泛型參數。令 l2 的泛型參數是 T, 則 TIntegerInteger 的子類,進而我們就不能找到一個類 X, 使得 X 是泛型參數 T 的子類,因此我們就不可以向 l2 中添加元素。不過由於我們知道了泛型參數 TIntegerInteger 的子類這一點,因此我們就可以從 l2 中讀取到元素 (取到的元素類型是 IntegerInteger 的子類), 並可以存放到 Integer 中。
  • 錯誤的理解:? extends Integer 限定的是插入元素的類型,因此只要是 IntegerInteger 的子類的對象都可以插入 l2

應用:PECS#

PECS 原則: Producer Extends, Consumer Super

  • Producer extends: 如果我們需要一個 List 提供類型為 T 的數據 (即希望從 List 中讀取 T 類型的數據), 那麼我們需要使用 ? extends T, 例如 List<? extends Integer>。 但是我們不能向這個 List 添加數據。
  • Consumer Super: 如果我們需要一個 List 來消費 T 類型的數據 (即希望將 T 類型的數據寫入 List 中), 那麼我們需要使用 ? super T, 例如 List<? super Integer>。但是這個 List 不能保證從它讀取的數據的類型。
  • 如果我們既希望讀取,也希望寫入,那麼我們就必須明確地聲明泛型參數的類型,例如 List
public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i = 0; i < src.size(); i++) {
            dest.set(i, src.get(i));
        }
    }
}

Source code#

References#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。