java零基礎入門-高級特性篇(六) 泛型 中

泛型的使用位置,除了最常見的約束集合元素,還可以使用在介面,類,方法上面。最本質的原因就是為了在使用介面,類,方法的時候,可以將類型作為參數,進行類型的參數傳遞。這樣可以使程序的編寫更加的靈活,在創建對象,調用方法的時候動態的指定類型,所以泛型也可以理解為類型的參數化。

類型參數化

光看名字,又不好理解,通俗點可以這樣理解。定義方法,介面的時候可以傳遞參數,參數通常都是指定類型的,比如

public void add(Student student);

public void add (Teacher teacher);

這裡的student是Student類型的參數,teacher是Teacher 類型的參數,他們已經指定好類型了。那麼類型的參數化,就是指將類型作為參數傳遞進方法。比如

public void add(E e);

這裡的add方法並沒有指定任何一個具體的類型,而是將類型也作為了參數,E是任何一個類型,e是任意類型E的實例。如果傳遞進來的類型參數是Student,那麼這個方法就是add(Student student);,如果傳遞進來的類型參數是Teacher ,那麼這個方法就是add (Teacher teacher);

類型參數化

當使用泛型定義參數的時候,每一個傳遞進來的類型參數,就創建了一個該方法的版本,add(Student student)是一個add(E e)的版本,add (Teacher teacher)也是一個add(E e)版本。類型參數化的好處是使代碼變得更加靈活,原因就在於此,因為可以通過對類型的抽象,使代碼匹配各種不同有具體類型版本的需求。

泛型介面和泛型類

泛型介面的定義,public interface man<T>{...}。在介面名後面加上泛型類型參數T,這樣就定義了一個泛型介面。

泛型介面

在介面中定義的類型參數可以在介面中當做類型使用,任何需要類型的地方都可以使用類型參數替代。比如傳遞的類型是Teacher,那麼run(T t)就是老師在跑路,getObject()方法返回一個老師對象,getAll(String name)方法可以根據學校名字獲取所有老師。加入傳遞的是Student,那麼上面三個方法分別是學生在跑路,獲取一個學生對象,根據學校名稱返回所有學生。使用泛型介面,可以在實現的時候才定義具體需要實現的類型,使介面可以進行更高級的抽象。

泛型類的定義,public class Man<T>{...},在類名後面加上泛型類型參數T,這樣就定義了一個泛型類。

泛型類

和泛型介面不同,類有構造器,並且構造器也可以使用泛型類型參數。在這個泛型類裡面,使用了兩個泛型類型參數,如果有必要可以定義更多的泛型參數。

如果java裡面沒有繼承這個特性,那麼泛型到這裡就講完了,但是,正因為java有繼承這個特性,會導致很多其他的問題出現,其複雜程度會幾何級的上升,後面的知識點對抽象能力和思維能力有較高的要求,請做好戰鬥準備。

下面從集合開始,先來思考幾個前面沒有思考過的問題。

1.如果集合加上了泛型,那麼如果添加的元素是泛型的子類或者父類能添加進去嗎?

添加子類父類

上面例子可以看出,如果泛型類型有子類,添加泛型類型的子類是可以的,但是如果泛型類型有父類,往集合添加泛型類型的父類會出現編譯錯誤。因為子類繼承了父類的所有方法,所以如果添加的是子類,當從集合取出的元素調用泛型類型的方法也不會有什麼問題。但是如果定義的是子類的泛型集合,放入的是父類元素,當要使用子類方法的時候,父類元素可能沒有,那麼就會發生錯誤,所以泛型是子類型的話,是不允許加入父類型元素的。

2.再看另一個問題,如果父類是泛型類型,如何定義子類?

泛型類的子類

如果將一個類定義為泛型類,那麼在創建該泛型類的子類的時候不能將子類直接繼承該泛型類,而是需要指定父類泛型的類型。比如父類是Book<T>,子類不能直接extends Book<T>,而是需要指定T的類型,上例中使用的Book<Double>作為類型。

在java中,泛型不能繼承和實現。為什麼?WHY?請手動滑動到本章最上面,跟我一起念,類型參數化。問題的關鍵就在這裡,因為泛型將類型作為一種參數,而參數是什麼?在定義方法的時候,他不需要具體指定是什麼數據,但是一旦你調用使用這個方法,就必須指定這個參數具體是什麼。

使用泛型

由於方法中的泛型需要在定義類的時候就指定,所以如果需要使用含有泛型的方法,必須在創建該泛型類對象的時候就需要指定泛型類型,因為使用的時候必須指定類型,不論是普通參數還是泛型參數。那為什麼繼承的時候也要確定泛型呢?因為繼承就是在使用一個已經定義好的類,使用泛型類,就要指定類型。

3.用什麼樣的參數形式來接受List<Book>這種形式的參數?

現在需要為所有List抽象一個方法,不論給的參數是List<Book>,List<String>,都可以接收並且列印List中的元素。是不是理所當然的想到了List<Object>?用List<Object>來接收參數就行了嘛。

泛型類型不匹配

啪啪啪,臉是不是很疼。顯然這樣是不可以的,錯誤提示參數類型不匹配,Object是所有類型的父類,但是List<Object>並不是List<Book>的父類,那應該使用什麼方法達到上面的要求呢?泛型提供了一個泛型通配符用於接收所有類型的泛型類型。

泛型的通配符

通配符

泛型的通配符可以很好的解決所有泛型類型父類的問題,使用<?>來作為類或介面的泛型參數,這樣就可以抽象出泛型類的父類。比如用List<?>可以看做所有List的父類,Set<?>可以看做所有Set的父類,使用?來表示一個未定義的類型,用來接受任何類型參數。

但是如果使用通配符,在部分功能上是會受到限制的。

1.只能通過Object遍歷集合。在訪問通配符泛型List<?>的時候,集合里的元素只能當做Object來訪問,因為在定義的時候只是一個通配符,不是具體類型,所以不能進行類型轉換隻能作為Object訪問。

2.不能使用add方法。List提供的add(E e)方法是需要指定類型的,這裡不是E嗎?這是個泛型類型啊?為什麼要提供類型?因為這是定義,一旦要使用add(E e)方法,必須指定具體的類型。定義通配符以後,在使用通配符的方法里是不知道類型的,所以不能使用add方法。

不能用add方法

就算是Object類型也不能使用add方法,為什麼?假設可以添加,會發生什麼問題?如果我使用List<Book>作為參數,傳入到printAllObject方法,運行完列印元素的語句後,會往List<Book>類型的集合裡面新增一個Object類型的對象,而Object又是Book類型的父類,上面說過,泛型類型的父類型元素不能添加到該集合,所以這裡就算是Objcet類型也不能添加。所以使用泛型通配符的話,這個集合的作用就是使用Object類型來遍歷它。

上面第二點,如果集合使用了泛型通配符,要往集合添加Object是不允許的,因為無論最後來的是什麼類型,Object都是這個類型的父類,所以不允許添加Object類型。那麼如果我可以保證添加一個元素,一定是泛型類型的子類,那麼是不是可以添加元素了?這個問題就涉及到泛型通配符的上下限問題了。下章繼續。

本章有很多類名稱相同,但是內容不同,請在不同的包下進行操作。

推薦閱讀:

相关文章