淺談Java泛型

使用泛型的目的

當(dāng)我們第一次接觸泛型時(shí),第一個(gè)問(wèn)題肯定會(huì)是,為什么要使用泛型?最直接的答案是為了避免轉(zhuǎn)型,使得編譯器能夠在編譯期就發(fā)現(xiàn)轉(zhuǎn)型錯(cuò)誤而不用等到運(yùn)行時(shí)。

比如說(shuō),當(dāng)我們聲明了一個(gè)泛型為Integer的列表,那么該列表的元素就只能是Integer,當(dāng)我們往里面放非Integer的元素時(shí),編譯器就能夠發(fā)現(xiàn),

List<Integer> intList = new ArrayList<>();
intList.add("abc"); //編譯期錯(cuò)誤,不能存放String對(duì)象到intList中

同理,當(dāng)我們從列表中獲取元素時(shí),我們也不再需要使用強(qiáng)制轉(zhuǎn)型將列表中的元素轉(zhuǎn)型成Integer

Integer i = intList.get(0);
Integer i = (Integer)intList.get(0);//不再需要進(jìn)行強(qiáng)制轉(zhuǎn)型

然而,泛型卻有一些容易令人困惑的地方,第一個(gè)就是關(guān)于泛型的帶有通配符的上下界。

帶有通配符的上下界

Java泛型現(xiàn)在支持兩種帶有通配符的上下界的表達(dá)方式,

  1. ? extends T - 這里的?表示類型T的任意子類型,包括類型T本身。
  2. ? super T - 這里的?表示類型T的任意父類型,包括類型T本身。

這兩者的含義都很容易理解和區(qū)分,難點(diǎn)在于我們什么時(shí)候該用 ? extends T, 什么時(shí)候改用? super T.

比如說(shuō),由于? extends T表示類型T和它的任意子類型,那么我們可以說(shuō)List<? extends Number> 實(shí)例可以添加任意為Number子類的元素嗎?

    List<? extends Number> intList = new ArrayList<>();
    intList.add(1); //complier error
    intList.add(3.14); //compiler error

從上述的代碼來(lái)看,它并不像我們所理解的那樣工作。那么,我們?cè)谑裁磿r(shí)候需要使用? extends T和? super T呢? 這里有一條簡(jiǎn)單的規(guī)則 (Get and Put Rule):

當(dāng)你需要從一個(gè)數(shù)據(jù)結(jié)構(gòu)中獲取數(shù)據(jù)時(shí)(get), 那么就使用? extends T. 如果你需要存儲(chǔ)數(shù)據(jù)(put)到一個(gè)數(shù)據(jù)結(jié)構(gòu)時(shí),那么就使用? super T. 如果你又想存儲(chǔ)數(shù)據(jù),又想獲取數(shù)據(jù),那么就不要使用通配符? . 即直接使用具體泛型T。

所以,根據(jù)Get&Put規(guī)則,在上面的例子中,我們是需要往數(shù)據(jù)結(jié)構(gòu)里面存儲(chǔ)數(shù)據(jù)的,所以需要使用? super T. 修改上面的例子,你可以發(fā)現(xiàn)程序可以工作了。

    List<? super Number> intList = new ArrayList<>();
    intList.add(1); //it works
    intList.add(3.14); //it works

第二個(gè)比較讓人困惑的是關(guān)于泛型的類型系統(tǒng)。

泛型的類型系統(tǒng)

泛型的類型系統(tǒng)中最令人困惑的地方就是,Integer類是Number類的子類,而List<Integer>卻不是List<Number>的子類。也就是說(shuō),你不能將List<Integer>實(shí)例對(duì)象直接賦值給List<Number>實(shí)例對(duì)象。 List<Integer>不是List<Number>子類的原因我們只能通過(guò)反證法推理出來(lái):

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<Number> nums = ints; // compile-time error
nums.add(3.14);
assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

從上面的例子中,假設(shè)List<Integer>是List<Number>的子類,那么List<Integer>實(shí)例對(duì)象ints就可以賦值給List<Number>實(shí)例對(duì)象nums。接著我們可以往nums實(shí)例添加非Integer對(duì)象,如float對(duì)象3.14。但是這個(gè)是不允許的,因?yàn)閚ums實(shí)例實(shí)際上是List<Integer>實(shí)例對(duì)象,它不能接受任何非Integer對(duì)象。由此可證,List<Integer>并不是List<Number>的子類。但是,List<Integer>是Collection<Integer>的子類。

所以對(duì)于泛型的類型系統(tǒng)來(lái)講,它應(yīng)當(dāng)遵循以下一些規(guī)則,摘抄自Java深度歷險(xiǎn)-Java泛型

引入泛型之后的類型系統(tǒng)增加了兩個(gè)維度:一個(gè)是類型參數(shù)自身的繼承體系結(jié)構(gòu),另外一個(gè)是泛型類或接口自身的繼承體系結(jié)構(gòu)。第一個(gè)指的是對(duì)于List<String>和List<Object>這樣的情況,類型參數(shù)String是繼承自O(shè)bject的。而第二種指的是List接口繼承自Collection接口。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則:

  1. 相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。即List<String>是Collection<String> 的子類型,List<String>可以替換Collection<String>。這種情況也適用于帶有上下界的類型聲明。
  1. 當(dāng)泛型類的類型聲明中使用了通配符的時(shí)候,其子類型可以在兩個(gè)維度上分別展開(kāi)。如對(duì)Collection<? extends Number>來(lái)說(shuō),其子類型可以在Collection這個(gè)維度上展開(kāi),即List<? extends Number>和Set<? extends Number>等;也可以在Number這個(gè)層次上展開(kāi),即Collection<Double>和 Collection<Integer>等。如此循環(huán)下去,ArrayList<Long>和 HashSet<Double>等也都算是Collection<? extends Number>的子類型。
  2. 如果泛型類中包含多個(gè)類型參數(shù),則對(duì)于每個(gè)類型參數(shù)分別應(yīng)用上面的規(guī)則。

最后我們來(lái)看看在Java泛型參數(shù)是如何進(jìn)行推斷的。

泛型參數(shù)的自動(dòng)推斷

有兩種方式可以指定泛型參數(shù)的類型,一種是顯示的指定,比如對(duì)于下面的方法,

public class GenericUtil {
    public static <T> List<T> asList(T... array) {
        List<T> tlist = new ArrayList<>();
        for (T t : array) {
            tlist.add(t);
        }
        return tlist;
    }
}

我們可以顯示的指定泛型參數(shù)的類型,

System.out.println(GenericUtil.<String>asList("a","b","c"));

除了顯示的指定之外,我們還可以隱式的指定, 實(shí)際上就是讓編譯器自己去推斷。

System.out.println(GenericUtil.asList("a","b","c"));

編譯器可以通過(guò)asList方法接收的參數(shù)類型來(lái)推斷出T實(shí)際上就是String。 如果從方法接收的參數(shù)類型推斷不出來(lái)的話,那么編譯器還會(huì)從方法賦值的目標(biāo)參數(shù)來(lái)推斷,比如說(shuō)下面的newObject方法,

public class GenericUtil {

    public static <T> T newObject(String className) throws Exception {
        Class clz = Class.forName(className);
        return (T) clz.newInstance();
    }
}

從方法接收參數(shù)上編譯器并不能推斷出T是什么類型。這個(gè)時(shí)候可以從方法賦值的目標(biāo)參數(shù)進(jìn)行推斷,

  Person p = GenericUtil.newObject("com.kevin.hibernate01.Person");

從方法賦值的目標(biāo)參數(shù)p可以得知,newObject方法中定義的泛型參數(shù)T類型應(yīng)該為Person類型。

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

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

  • 1. 泛型概述 泛型為JDK1.5之后sun公司推出的新功能,泛型可以消除源代碼中的許多強(qiáng)制類型轉(zhuǎn)換,泛型對(duì)于數(shù)據(jù)...
    yuan_dongj閱讀 4,554評(píng)論 0 8
  • 第8章 泛型 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場(chǎng)...
    光劍書架上的書閱讀 2,199評(píng)論 6 10
  • 開(kāi)發(fā)人員在使用泛型的時(shí)候,很容易根據(jù)自己的直覺(jué)而犯一些錯(cuò)誤。比如一個(gè)方法如果接收List作為形式參數(shù),那么如果嘗試...
    時(shí)待吾閱讀 1,125評(píng)論 0 3
  • 我到底是愛(ài)你還是不愛(ài)你呢?是因?yàn)閻?ài)你才在乎你還是因?yàn)槟悴辉诤跷椅也鸥诤跄??如果你不?ài)我或者沒(méi)那么愛(ài)我最重要的是沒(méi)...
    水也云云閱讀 218評(píng)論 0 0
  • 風(fēng)清揚(yáng),雪舞狂,這路上,我在! 每個(gè)人的路都有自己的走法,我們各行其道,各運(yùn)常修,讓線譜交織,讓生命喝彩。 從來(lái)到...
    人泥閱讀 162評(píng)論 0 1

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