java定义list_我的Java Web之路59 - Java中的泛型
本系列文章旨在記錄和總結自己在Java Web開發之路上的知識點、經驗、問題和思考,希望能幫助更多(Java)碼農和想成為(Java)碼農的人。
目錄
介紹
還記得我在這篇文章(我的Java Web之路32 - Spring MVC基于注解的控制器)中列舉的Handler方法支持的眾多返回值類型和注解嗎?其中有不少是如下形式的:
- HttpEntity, ResponseEntity
- DeferredResult
- Callable
- ListenableFuture, java.util.concurrent.CompletionStage, java.util.concurrent.CompletableFuture
還有這篇文章(我的Java Web之路52 - Spring JDBC初步使用)提到 RowMapper 接口是一個泛型接口,使用時是這樣的:
new RowMapper() { ... }還有這篇文章(我的Java Web之路55 - ORM框架(MyBatis)初步使用)中我們定義HouseMapper接口時,其中一個方法的返回值使用了泛型接口List:
List selectAll();還有這篇文章(我的Java Web之路58 - Spring整合ORM(MyBatis)2)也提到MapperFactoryBean實際上是一個泛型類,使用基于Java配置的方式如下:
@Beanpublic MapperFactoryBean houseMapper() throws Exception { ... }這些類和接口使用時都在類名或接口名后面添加了一對尖括號括起其他類名的內容,這就是Java中的泛型,本篇文章就粗略介紹一下,讓我們對它有個基礎的認識,以后遇到它就能夠理解了。
再談Java中的類型
大家都知道,Java語言雖然是一種面向對象的編程語言,但同時也是一種強類型的編程語言,即任何一個變量都需要先聲明它的類型之后才能使用它。關于類型和變量的一些知識,大家可以參考這篇文章和這篇文章。
總的來說,Java中的類型有兩種,一種是基本類型(英文是primitive types),包括八個:byte、short、int、long、float、double、char、boolean 。
另一種是引用類型(英文是reference),因為引用類型是指向某一個對象/實例的,所以如果某個變量是引用類型的話,通常我們說該變量是某某類的引用,即該引用所指向的對象/實例的類型是某某類 / 接口。
所以,我們把基本類型和各種類 / 接口都統稱為類型,即每一個類 / 接口就是一種類型,也就是說我們可以無限擴展類型,因為我們可以定義無限多個類 / 接口。
為什么需要泛型?
解釋了Java中的類型之后,我們再來思考一下為什么需要泛型呢?或者說泛型解決了什么問題呢?
我們經常會遇到這種情況,一些代碼邏輯(歸為算法也未嘗不可,就當是廣義上的算法吧)實際上與它處理何種類型的數據是無關的。
舉個例子,就拿上面我們經常用到的列表(List)這個類/接口所代表的算法來說,它就好像是現實中的火車一樣(可能有些不太恰當),一節一節的,想裝旅客就裝旅客,想裝某種貨物就裝某種貨物,因此就有這樣的方法:
E get(int n); //獲取第N節車廂的東西,可能是旅客,也可能是其他某種貨物E set(int n, E element); //將旅客或者其他某種貨物裝進第N節車廂當然,列表(List)這個接口還有很多方法,這里只是拿出這兩個來舉例。可以看到,列表(List)這種算法(實際上是數據結構)獨立于各種數據類型,即它可以容納各種數據類型的數據。
反過來說,假如沒有泛型的話,我們就需要為每一種類型設計一種List,比如:
interface 旅客List { 旅客 get(int n); 旅客 set(int n, 旅客 element);}interface 蘋果List { 蘋果 get(int n); 蘋果 set(int n, 蘋果 element);}//必要的話,還需要定義其他List不知道大家有沒有發現上述代碼的最大問題是什么?
沒錯,就是重復啊!除了幾個地方的數據類型不同,其他代碼都是相同的。所以說,泛型本質上就是解決代碼重復問題的。有了泛型,你就可以這樣定義List:
interface List { T get(int n); T set(int n, T element);}使用的時候,我們再指定是何種類型的List:
List list_旅客;List list_蘋果;或許你會想到,既然在Java語言中,一切都是Object類的子類(除了那八個基本類型外),我們可以用Object類來設計List啊:
interface List { Object get(int n); Object set(int n, Object element);}使用的時候,將類型強制轉換不就可以了嗎:
List list_旅客;list_旅客.set(0, 旅客A);旅客 旅客A = (旅客)list_旅客.get(0);不過,這樣一來我們就需要使用強制類型轉換,而強制類型轉換是非常不安全的,比如,往往在某個地方將旅客塞進了某個List,而使用的時候卻將它強制轉換成蘋果,這不就出錯了嗎。特別是Java編譯器是不能發現此類錯誤的,只有在程序運行時才能發現(錯誤是越早發現越好)。
而使用泛型,我們在定義某個List變量時就可以指定該List是用來裝何種類型的數據的,一來Java編譯器可以發現此錯誤,因為它解析代碼的時候就能夠記住指定的類型啊;二來我們無需使用強制類型轉換。
事實上,Java的泛型在底層就是使用Object來實現的,只不過是由Java編譯器為我們進行強制類型轉換。
綜上所述,泛型有如下好處:
- 消除重復(可以編寫獨立于類型的算法,即泛型算法);
- 編譯時就能進行類型檢測;
- 無需使用強制類型轉換。
Java中的泛型
Java中的泛型分兩種:
- 泛型類型
- 泛型方法
泛型對應的英文單詞是 generic :
adj.一般的; 普通的; 通用的; 無廠家商標的; 無商標的;
n.同“a generic drug";
[例句]Parmesan is a generic term used to describe a family of hard Italian cheeses.
帕爾瑪干酪是意大利硬奶酪的通稱。
[其他]復數:generics
可以看到,泛型這個譯法一方面與 generic 的本義(泛)是相符的,另一方面又兼顧了編程領域的含義(類型)。
泛型的本質是將類型變為一種參數,這就叫做類型的參數化(parameterized over types)。我們可以拿它與普通變量的參數化進行類比,這樣有助于我們的理解和記憶:
要注意,形參的英文單詞是 parameter;實參的英文單詞是 argument 。
所以,類型形參就是 type parameter;類型實參就是 type argument 。
泛型類型
泛型類型又包括兩種:
- 泛型類
- 泛型接口
泛型類的定義只需要在類名之后添加一對尖括號,然后在尖括號中聲明若干個類型形參:
new RowMapper() { ... }泛型接口的定義也是類似:
List selectAll();當然,尖括號中聲明的類型形參就可以在該類或接口中使用了,不管是在屬性中還是在方法中都可以使用,比如:
@Beanpublic MapperFactoryBean houseMapper() throws Exception { ... }由于歷史原因,泛型是在JDK的某個版本引入的,所以JDK中存在將原來不是泛型的類型,在后來的版本中改造成了泛型類型,而為了與之前的版本兼容,所以原來不是泛型的類型仍然可以使用,于是Java就引入原始類型(Raw Type)的概念。
舉個例子,ArrayList原來不是泛型,后來被改造成了泛型:
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable所以,不使用泛型方式的ArrayList就叫原始類型,我們仍然可以定義原始類型的變量,甚至將泛型類型的對象賦值給該變量,當然,反過來也可以,如下:
ArrayList arrayList = new ArrayList();ArrayList listOfInt = list;不過,這種代碼極其不安全,大家最好盡量避免,一般IDE都會給出提示,Java編譯器在編譯時也會給出警告。
類型形參的命名也有一些約定俗成的規定,但你也可以不遵從:
E - Element:表示元素的類型,通常用在表示集合、容器等概念的泛型類 / 接口中。K - Key:表示鍵的類型,通常用在映射概念的泛型類 / 接口中。N - NumberT - Type:一般化的命名。V - Value:表示值的類型,通常用在映射概念的泛型類 / 接口中。S,U,V 等 - 一般化的命名,有多個類型形參時使用。泛型方法
泛型方法是指在方法的定義中有自己的類型形參的聲明,如果僅僅使用了泛型類型已經聲明的形參,那就不算是泛型方法,舉一個官方文檔中的例子:
public class Util { public static boolean compare(Pair p1, Pair p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); }}public class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}泛型方法的類型形參的聲明是放在方法返回值的前面,也是使用尖括號將若干形參括起來的形式,如上面的 compare() 方法的定義。
實際調用方法時,可以在方法名前寫上類型實參,如:
Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);當然,前輩們已經把Java編譯器實現的足夠智能,就算你不寫類型實參,它也能夠從方法實參的類型中推斷出來:
Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);Java編譯器的這個功能就叫做類型推斷(type inference)。
總結
本篇文章介紹了Java泛型的基本知識,原理上也是很簡單的,大家只要把它當做普通類/接口和普通方法即可,只不過是多了一些語法而已。我們只需要記住,泛型主要還是為了
- 消除重復
- 和實現類型安全。
當然,泛型還有很多內容,比如:
- 類型形參可以設定一些邊界,比如只允許某個類的子類或父類當做類型實參。
- 泛型類/接口的繼承有些不同,并不是說類型實參有父子關系,它們的泛型類就擁有父子關系,事實是它們沒有任何關系。
- 泛型中的通配符的使用。
- 類型推斷。
- 類型擦除。
- 一些最佳實踐。
- 等等。
這些內容以后慢慢介紹。
總結
以上是生活随笔為你收集整理的java定义list_我的Java Web之路59 - Java中的泛型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java rmi漏洞工具_学生会私房菜【
- 下一篇: mysql判断数字的函数_Mysql必读