JAVA基礎(chǔ)之泛型

1、為什么引入泛型

bug是編程的一部分,我們只能盡自己最大的能力減少出現(xiàn)bug的幾率,但是誰也不能保證自己寫出的程序不出現(xiàn)任何問題。

錯誤可分為兩種:編譯時錯誤運行時錯誤。編譯時錯誤在編譯時可以發(fā)現(xiàn)并排除,而運行時錯誤具有很大的不確定性,在程序運行時才能發(fā)現(xiàn),造成的后果可能是災(zāi)難性的。

使用泛型可以使錯誤在編譯時被探測到,從而增加程序的健壯性。

來看一個例子:

    public class Box {
        private Object object;

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

        public Object get() {
            return object;
        }
    }

按照聲明,其中的set()方法可以接受任何java對象作為參數(shù)(任何對象都是Object的子類),假如在某個地方使用該類,set()方法預(yù)期的輸入對象為Integer類型,但是實際輸入的卻是String類型,就會拋出一個運行時錯誤,這個錯誤在編譯階段是無法檢測的。例如:

Box box = new Box;  
box.set("abc");  
Integer a = (Integer)box.get();  //編譯時不會報錯,但是運行時會報ClassCastException

運用泛型改造上面的代碼:

public class Box<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

當(dāng)我們使用該類時會指定T的具體類型,該類型參數(shù)可以是類、接口、數(shù)組等,但是不能是基本類型

比如:

Box<Integer> box = new Box<Integer>;  //指定了類型類型為Integer
//box.set(“abc”);  該句在編譯時就會報錯
box.set(new Integer(2));
Integer a = box.get();  //不用轉(zhuǎn)換類型

可以看到,使用泛型還免除轉(zhuǎn)換操作。

在引入泛型機制之前,要在方法中支持多個數(shù)據(jù)類型,需要對方法進行重載,在引入范型后,可以更簡潔地解決此問題,更進一步可以定義多個參數(shù)以及返回值之間的關(guān)系。

例如

public void write(Integer i, Integer[] ia);
public void write(Double  d, Double[] da);
public void write(Long l, Long[] la);

范型版本為:

public <T> void write(T t, T[] ta);

總體來說,泛型機制能夠在定義類、接口、方法時把“類型”當(dāng)做參數(shù)使用,有點類似于方法聲明中的形式參數(shù),如此我們就能通過不同的輸入?yún)?shù)來實現(xiàn)程序的重用。不同的是,形式參數(shù)的輸入是值,而泛型參數(shù)的輸入是類型。

2、命名規(guī)則

類型參數(shù)的命名有一套默認規(guī)則,為了提高代碼的維護性和可讀性,強烈建議遵循這些規(guī)則。JDK中,隨處可見這些命名規(guī)則的應(yīng)用。

  • E - Element (通常代表集合類中的元素)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. – 第二個,第三個,第四個類型參數(shù)……

注意,父類定義的類型參數(shù)不能被子類繼承。

也可以同時聲明多個類型變量,用逗號分割,例如:

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }
  
    public V getValue() {
        return value;
    }
}

下面的兩行代碼創(chuàng)建了OrderedPair對象的兩個實例。

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");

//也可以將new后面的類型參數(shù)省略,簡寫為:
//Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);

//也可以在尖括號內(nèi)使用帶有類型變量的類型變量,例如:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

泛型是JDK 5.0之后才引入的,為了兼容性,允許不指定泛型參數(shù),但是如此一來,編譯器就無法進行類型檢查,在編程時,最好明確指定泛型參數(shù)。

同樣,在方法中也可是使用泛型參數(shù),并且該參數(shù)的使用范圍僅限于方法體內(nèi)。例如:

public class Util {
        //該方法用于比較兩個Pair對象是否相等。
        //泛型參數(shù)必須寫在方法返回類型boolean之前
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
//實際上,編譯器可以通過Pair當(dāng)中的類型來推斷compare需要使用的類型,所以可以簡寫為:
// boolean same = Util. compare(p1, p2);

有時候我們想讓類型參數(shù)限定在某個范圍之內(nèi),就需要用到extends關(guān)鍵字(extends后面可以跟一個接口,這里的extends既可以表示繼承了某個類,也可以表示實現(xiàn)了某個接口),例如,我們想讓參數(shù)是數(shù)字類型:

    class Box<T extends Number> {  //類型參數(shù)限定為Number的子類

        private T t;

        public Box(T t) {
            this.t = t;
        }

        public void print() {
            System.out.println(t.getClass().getName());
        }

        public static void main(String[] args) {

            Box<Integer> box1 = new Box<Integer>(new Integer(2));
            box1.print();  //打印結(jié)果:java.lang.Integer
            Box<Double> box2 = new Box<Double>(new Double(1.2));
            box2.print();  //打印結(jié)果:java.lang.Double

            Box<String> box2 = new Box<String>(new String("abc"));  //報錯,因為String類型不是Number的子類
            box2.print();
        }

    } 

如果加入多個限定,可以用“&”連接起來,但是由于java是單繼承,多個限定中最多只能有一個類,而且必須放在第一個位置。例如:

    class Box<T extends Number & Cloneable & Comparable> {

        //該類型必須為Number的子類并且實現(xiàn)了Cloneable接口和Comparable接口。
        //……
    }

3、泛型類的繼承

java是面向?qū)ο蟮母呒壵Z言,在一個接受A類參數(shù)的地方傳入一個A的子類是允許的,例如:

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // 因為Integer是Object的子類

這種特性同樣適用于類型參數(shù),例如:

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // Integer是Number的子類
box.add(new Double(10.1));  // Double同樣是Number的子類

但是,有一種情況很容易引起混淆,例如:

//該方法接受的參數(shù)類型為Box<Number>
public void boxTest(Box<Number> n) { 
    //……
}
//下面兩種調(diào)用都會報錯
boxTest(Box<Integer>);
boxTest(Box<Double>);

雖然Integer和Double都是Number的子類,但是Box<Integer>與Box<Double>并不是Box<Number>的子類,不存在繼承關(guān)系。Box<Integer>與Box<Double>的共同父類是Object。

1.png

以JDK中的集合類為例,ArrayList<E> 實現(xiàn)了 List<E>接口,List<E>接口繼承了 Collection<E>接口,所以,ArrayList<String>是List<String>的子類,而非List<Integer>的子類。三者的繼承關(guān)系如下:

2.png

4、類型推斷

先來看一個例子:

    public class Demo {

        static <T> T pick(T a1, T a2) {
            return a2;
        }

    }

靜態(tài)方法pick()在三個地方使用了泛型,分別限定了兩個輸入?yún)?shù)的類型與返回類型。調(diào)用該方法的代碼如下:

Integer ret = Demo.<Integer> pick(new Integer(1), new Integer(2));

前文已經(jīng)提到,上面的代碼可以簡寫為:

Integer ret = Demo.pick(new Integer(1), new Integer(2));

因為java編譯器會根據(jù)方法內(nèi)的參數(shù)類型推斷出該方法返回的類型應(yīng)該為Integer,這種機制稱為類型推斷(Type Inference)。

那么問題來了,加入兩個輸入?yún)?shù)為不同的類型,應(yīng)該返回什么類型呢?

例如:

pick("d", new ArrayList<String>());

第一個參數(shù)為String類型,第二個參數(shù)為ArrayList類型,java編譯器就會根據(jù)這兩個參數(shù)類型來推斷,盡量使返回類型為最明確的一種。本例中,String與ArrayList都實現(xiàn)了同樣的接口——Serializable,當(dāng)然,他們也是Object的子類,Serializable類型顯然比Object類型更加明確,因為它的范圍更小更細分,所以最終的返回類型應(yīng)該為Serializable:

Serializable s = pick("d", new ArrayList<String>());

在泛型類實例化的時候同樣可以利用這種機制簡化代碼,需要注意的是,尖括號“<>”在此時是不能省略的。例如:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();
//編譯器能推斷出后面的類型,所以可以簡化為:
Map<String, List<String>> myMap = new HashMap<>();
//但是,不能簡化為:
Map<String, List<String>> myMap = new HashMap();
//因為HashMap()是HashMap原始類型(Raw Type)的構(gòu)造函數(shù),而非HashMap<String, List<String>>的構(gòu)造函數(shù),如果不加“<>”編譯器不會進行類型檢查

5、通配符

上文中我們提到過一個例子:

public void boxTest(Box<Number> n){
        //……
}

該方法只能接受Box<Number>這一種類型的參數(shù),當(dāng)我們輸入一個Box<Double>或者Box<Integer>時會報錯,盡管Integer與Double是Number的子類??墒侨绻覀兿M摲椒梢越邮躈umber以及它的任何子類,該怎么辦呢?

這時候就要用到通配符了,改寫如下:

public void boxTest(Box<? extends Number> n){
        //……
}

? extends Number就代表可以接受Number以及它的子類作為參數(shù)。這種聲明方式被稱為上限通配符(upper bounded wildcard)。

相反地,如果我們希望該方法可以接受Integer,Number以及Object類型的參數(shù)怎么辦呢?應(yīng)該使用下限通配符(lower bounded wildcard):

public void boxTest(Box<? super Integer> n){
        //……
}

? super Integer代表可以接受Integer以及它的父類作為參數(shù)。

如果類型參數(shù)中既沒有extends 關(guān)鍵字,也沒有super關(guān)鍵字,只有一個?,代表無限定通配符(Unbounded Wildcards)。

通常在兩種情況下會使用無限定通配符:

  • 如果正在編寫一個方法,可以使用Object類中提供的功能來實現(xiàn)
  • 代碼實現(xiàn)的功能與類型參數(shù)無關(guān),比如List.clear()List.size()方法,還有經(jīng)常使用的Class<?>方法,其實現(xiàn)的功能都與類型參數(shù)無關(guān)。

來看一個例子:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

該方法只能接受List<Object>型的參數(shù),不接受其他任何類型的參數(shù)。但是,該方法實現(xiàn)的功能與List之中參數(shù)類型沒有關(guān)系,所以我們希望它可以接受包含任何類型的List參數(shù)。代碼改動如下:

public static void printList(List<?> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

需要特別注意的是,List<?>與List<Object>并不相同,無論A是什么類型,List<A>是List<?>的子類,但是,List<A>不是List<Object>的子類。

例如:

List<Number> lb = new ArrayList<>();
List<Integer> la = lb;   // 會報編譯錯誤,盡管Integer是Number的子類,但是List<Integer>不是List<Number>的子類

List<Integer>與List<Number>的關(guān)系如下:

3.png

所以,下面的代碼是正確的:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // 不會報錯, List<? extends Integer> 是 List<? extends Number>的子類

下面這張圖介紹了上限通配符、下限通配符、無限定通配符之間的關(guān)系:

4.png

編譯器可以通過類型推斷機制來決定通配符的類型,這種情況被稱為通配符捕獲。大多時候我們不必擔(dān)心通配符捕獲,除非編譯器報出了包含“capture of”的錯誤。例如:

    public class WildcardError {

        void foo(List<?> i) {
            i.set(0, i.get(0));  //會報編譯錯誤
        }
    }

上例中,調(diào)用List.set(int,E)方法的時候,編譯器無法推斷i.get(0)是什么類型,就會報錯。

我們可以借助一個私有的可以捕獲通配符的helper方法來解決這種錯誤:

    public class WildcardFixed {
        void foo(List<?> i) {
            fooHelper(i);
        }


        // 該方法可以確保編譯器通過通配符捕獲來推斷出參數(shù)類型
        private <T> void fooHelper(List<T> l) {
            l.set(0, l.get(0));
        }

    }

按照約定俗成的習(xí)慣,helper方法的命名方法為“原始方法”+“helper”,上例中,原始方法為“foo”,所以命名為“fooHelper”。

關(guān)于什么時候該使用上限通配符,什么時候該使用下限通配符,應(yīng)該遵循一下幾項指導(dǎo)規(guī)則。

首先將變量分為in-變量out-變量in-變量持有為當(dāng)前代碼服務(wù)的數(shù)據(jù),out-變量持有其他地方需要使用的數(shù)據(jù)。

例如copy(src, dest)方法實現(xiàn)了從src源頭將數(shù)據(jù)復(fù)制到dest目的地的功能,那么src就是in-變量,而dest就是out-變量。當(dāng)然,在一些情況下,一個變量可能既是in-變量也是out-變量。

  • in-變量使用上限通配符;
  • out-變量使用下限通配符;
  • 當(dāng)in-變量可以被Object類中的方法訪問時,使用無限定通配符;
  • 一個變量既是in-變量也是out-變量時,不使用通配符

注意,上面的規(guī)則不適用于方法的返回類型。

6、類型擦除

java編譯器在處理泛型的時候,會做下面幾件事:

  • 將沒有限定的類型參數(shù)用Object替換,保證class文件中只含有正常的類、接口與方法;
  • 在必要的時候進行類型轉(zhuǎn)換,保證類型安全;
  • 在泛型的繼承上使用橋接方法(bridge methods)保持多態(tài)性。

這類操作被稱為類型擦除(Type Erasure)。

例如:

    public class Node<T> {

        private T data;
        private Node<T> next;

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }

        public T getData() {
            return data;
        }
        // ...
    }

該類中的T沒有被extends或者super限定,會被編譯器替換成Object:

    public class Node {

        private Object data;
        private Node next;

        public Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }

        public Object getData() {
            return data;
        }
        // ...
    }

如果T加了限定,編譯器會將它替換成合適的類型:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
       this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

改造成:

    public class Node {

        private Comparable data;
        private Node next;

        public Node(Comparable data, Node next) {
            this.data = data;
            this.next = next;
        }

        public Comparable getData() {
            return data;
        }
        // ...
    }

方法中的類型擦除與之類似。

有時候類型擦除會產(chǎn)生一些我們預(yù)想不到的情況,下面通過一個例子來分析它是如何產(chǎn)生的。

   public class Node<T> {

        public T data;

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

        public void setData(T data) {
            System.out.println("Node.setData");
            this.data = data;
        }
    }

    public class MyNode extends Node<Integer> {
        public MyNode(Integer data) {
            super(data);
        }

        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }
    }

上面的代碼定義了兩個類,MyNode類繼承了Node類,然后運行下面的代碼:

MyNode mn = new MyNode(5);
Node n = mn;            
n.setData("Hello");     
Integer x = mn.data;    // 拋出ClassCastException異常

上面的代碼在類型擦除之后會轉(zhuǎn)換成下面的形式:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         

n.setData("Hello");
Integer x = (String)mn.data;   // 拋出ClassCastException異常

我們來看看代碼是怎么執(zhí)行的:

  • (1)n.setData("Hello")調(diào)用的其實是MyNode類的setData(Object)方法(從Node類繼承的);
  • (2)n引用的對象中的data字段被賦值一個String變量;
  • (3)mn引用的相同對象中的data預(yù)期為Integer類型(mn為Node<Integer>類型);
  • (4)第四行代碼試圖將一個String賦值給Integer類型的變量,所以引發(fā)了ClassCastException異常。

當(dāng)編譯一個繼承了帶有參數(shù)化泛型的類或借口時,編譯器會根據(jù)需要創(chuàng)建被稱為bridge method的橋接方法,這是類型擦除中的一部分。

上例中MyNode繼承了Node<Integer>類,類型擦除之后,代碼變?yōu)椋?/p>

    class MyNode extends Node {

        //編譯器添加的橋接方法
        public void setData(Object data) {
            setData((Integer) data);
        }

        // MyNode的該方法并沒有覆寫父類的setData(Object data)方法,因為參數(shù)類型不一樣
        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }

        // ...
    }

7、注意事項

為了高效地使用泛型,應(yīng)該注意下面幾個方面:

(1)不能用基本類型實例化類型參數(shù)

例如

   class Pair<K, V> {

        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        // ...
    }

當(dāng)創(chuàng)建一個Pair類時,不能用基本類型來替代K,V兩個類型參數(shù)。

Pair<int, char> p = new Pair<>(8, 'a');  // 編譯錯誤
Pair<Integer, Character> p = new Pair<>(8, 'a');  //正確寫法

(2)不可實例化類型參數(shù)

例如:

public static <E> void append(List<E> list) {
    E elem = new E();  // 編譯錯誤
    list.add(elem);
}

但是,我們可以通過反射實例化帶有類型參數(shù)的對象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // 正確
    list.add(elem);
}

List<String> ls = new ArrayList<>();
append(ls, String.class);  //傳入類型參數(shù)的Class對象

(3)不能在靜態(tài)字段上使用泛型

通過一個反例來說明:

public class MobileDevice<T> {
    private static T os;  //假如我們定義了一個帶泛型的靜態(tài)字段

    // ...
}

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

因為靜態(tài)變量是類變量,被所有實例共享,此時,靜態(tài)變量os的真實類型是什么呢?顯然不能同時是Smartphone、Pager、TabletPC。

這就是為什么不能在靜態(tài)字段上使用泛型的原因。

(4)不能對帶有參數(shù)化類型的類使用cast或instanceof方法

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // 編譯錯誤
        // ...
    }
}

傳給蓋該方法的參數(shù)化類型集合為:

S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }

運行環(huán)境并不會跟蹤類型參數(shù),所以分辨不出ArrayList<Integer>與ArrayList<String>,我們能做的至多是使用無限定通配符來驗證list是否為ArrayList:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // 正確
        // ...
    }
}

同樣,不能將參數(shù)轉(zhuǎn)換成一個帶參數(shù)化類型的對象,除非它的參數(shù)化類型為無限定通配符(<?>):

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // 編譯錯誤

當(dāng)然,如果編譯器知道參數(shù)化類型肯定有效,是允許這種轉(zhuǎn)換的:

List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // 允許轉(zhuǎn)變,類型參數(shù)沒變化

(5)不能創(chuàng)建帶有參數(shù)化類型的數(shù)組

例如:

List<Integer>[] arrayOfLists = new List<Integer>[2]; // 編譯錯誤

下面通過兩段代碼來解釋為什么不行。先來看一個正常的操作:

Object[] strings = new String[2];
strings[0] = "hi";   // 插入正常
strings[1] = 100;    //報錯,因為100不是String類型

同樣的操作,如果使用的是泛型數(shù)組,就會出問題:

Object[] stringLists = new List<String>[];  // 該句代碼實際上會報錯,但是我們先假定它可以執(zhí)行
stringLists[0] = new ArrayList<String>();   // 插入正常
stringLists[1] = new ArrayList<Integer>();  // 該句代碼應(yīng)該報ArrayStoreException的異常,但是運行環(huán)境探測不到

(6)不能創(chuàng)建、捕獲泛型異常

泛型類不能直接或間接繼承Throwable類

class MathException<T> extends Exception { /* ... */ }    //編譯錯誤

class QueueFullException<T> extends Throwable { /* ... */} // 編譯錯誤

方法不能捕獲泛型異常:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // 編譯錯誤
        // ...
    }
}

但是,我們可以在throw子句中使用類型參數(shù):

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // 正確
        // ...
    }
}

(7)不能重載經(jīng)過類型擦除后形參轉(zhuǎn)化為相同原始類型的方法

先來看一段代碼:

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());

打印結(jié)果可能與我們猜測的不一樣,打印出的是true,而非false,因為一個泛型類的所有實例在運行時具有相同的運行時類(class),而不管他們的實際類型參數(shù)。

事實上,泛型之所以叫泛型,就是因為它對所有其可能的類型參數(shù),有同樣的行為;同樣的類可以被當(dāng)作許多不同的類型。

認識到了這一點,再來看下面的例子:

public class Example { 
    public void print(Set<String> strSet) { }  //編譯錯誤
    public void print(Set<Integer> intSet) { }  //編譯錯誤
}

因為Set<String>與Set<Integer>本質(zhì)上屬于同一個運行時類,在經(jīng)過類型擦出以后,上面的兩個方法會共享一個方法簽名,相當(dāng)于一個方法,所以重載出錯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載: https://blog.csdn.net/s10461/article/details/53941091...
    DaneYang閱讀 543評論 1 6
  • 泛型 體驗泛型 沒有使用泛型時,只要是對象,不管是什么類型的對象,都可以存儲進同一個集合中。使用泛型集合,可以將一...
    bo客先生閱讀 773評論 0 8
  • 泛型的定義及使用 1. 定義泛型: 2. 類中使用泛型 3. 使用泛型類 4. 使用泛型的優(yōu)勢? 多泛型變量的定義...
    xue57233閱讀 486評論 0 1
  • 一:什么是泛型 泛型在我們的代碼中使用非常廣泛的一部分知識,今天就系統(tǒng)的把泛型總結(jié)一下,并記錄自己的學(xué)習(xí)歷程...
    蓉漂里的小白閱讀 678評論 0 2
  • 泛型類和泛型方法 泛型是Java語言中實現(xiàn)程序多態(tài)的一種重要方法,泛型多用于底層代碼中,以此來保證代碼的通用型。今...
    寫程序的小火箭閱讀 266評論 0 0

友情鏈接更多精彩內(nèi)容