Java泛型主題討論

說明:在學習泛型這一知識點中,主要參考自《瘋狂Java講義》第7章P307-P330的泛型內容,因為是跳著閱讀,所以前面的一些名詞不是特別清楚,這里也做出適當備注,供自己識記與理解。

1.泛型

理解:說到泛型,感覺最初是為了解決Java集合的一個缺點——當我們想要把一個對象放進集合里面的時候,集合就會忘記這個對象的數據類型,再次把它取出來時,它的編譯類型就會變成Object類型(運行類型不會變)。記住我們的目標是:在集合里面存儲不會被忘記數據類型的各種對象。例子:

 1 package FanXing;
 2 
 3 public class ListErr {
 4      public static void main(String[] args) {
 5              List strList = new ArrayList();
 6             strList.add("泛型主題討論");
 7             strList.add(017);//這里不小心把一個Integer對象放在了集合里面,可能報類型強制轉換異常ClassCastException
 8            for(int i=0;i<strList.size();i++)
 9             {String str = (String)strList.get(i);
10                 }
11     }
12 }
編譯信息:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
List cannot be resolved to a type
ArrayList cannot be resolved to a type

at FanXing.ListErr.main(ListErr.java:5)

圖中的紅色框已經提示我們需要用什么區解決所面臨的的問題了。

為了達到我們的目標,我們想到了可以手動實現編譯時去檢查類型。

例子:(既然會發生異常那我們就在運行前先檢查,我們這里先創建一個對象List,讓它只保存字符串類型,這樣就可以擴展ArrayList類)

package FanXing;import java.util.ArrayList;
import java.util.List;class StrList{private List strList = new ArrayList();public boolean add(String ele)//定義StrList的add方法,只添加字符串
    {return strList.add(ele);}public String get (int index){return (String)strList.get(index);}public int size(){return strList.size();}
}public class ListErr {public static void main(String[] args) {StrList strList = new StrList();strList.add("泛型主題討論");//strList.add(017);如果沒有這一句,代碼可以成功被編譯,否組會報錯。
            System.out.println(strList);for(int i=0;i<strList.size();i++){String str = strList.get(i);}}
}

上面的代碼中我們定義的StrList類實現了編譯時的異常檢查,當編譯到strList.add(017);時,程序試圖將一個Integer對象加入到StrList集合中,程序在這里會無法編譯通過,因為StrList只接受String的對象。

不過,既然只接受String對象的時候可以編譯通過,說明這個方法還是有用的,但是,這種方法雖然有效,局限性卻非常明顯——我們需要去定義大量的List子類。

雖然這樣也可以實現我們的目標:在集合里面存儲不會被忘記數據類型的各種對象。不過這樣非常非常麻煩,這個時候,我們的泛型就被設計出來了,有了它,我們的目標可以輕易實現。

package FanXing;import java.util.ArrayList;
import java.util.List;public class ListErr {public static void main(String[] args) {List<String> strList = new ArrayList<String>();//創建一個List集合,只保留字符串strList.add("泛型主題討論");for(int i = 0;i<strList.size();i++){String str = strList.get(i);}}
}

很顯然這樣代碼簡化了很多,List<String>說明這是一個String類型的List。

所以這里我們可以歸納出,如果List<>尖括號里面是其他類型的話也是同理,即有了一個JDK1.5以后引入的概念:

Java泛型(generics)【Java的參數化類型】 :是JDK 5中引入的一個新特性,允許在定義類和接口的時候使用類型參數(type parameter)。聲明的類型參數在使用時用具體的類型來替換。泛型最主要的應用是在JDK 5中的新集合類框架中。

泛型最大的好處是可以提高代碼的復用性。以List接口為例,我們可以將String、Integer等類型放入List中,如不用泛型,存放String類型要寫一個List接口,存放Integer要寫另外一個List接口,泛型可以很好的解決這個問題。
2.深入泛型:

①定義泛型接口、類

public interface List<E>//定義接口,指定形參E,在這個接口里面E可以作為泛型使用
{void add(E,x);Iterator<E> iterator();//A...
}
public interface Iterator<E>//在這個接口里,E可以作為類型使用
{E next();boolean hasNext();...
}
public interface Map<K,V>//K,V可以作為類型使用
{Set<K> keySet()//B V put (K key ,V value) ...
}

可以發現:在A、B處方法聲明返回值類型是Iterrator<E>和Set<K>,說明他們是一種特殊的數據類型,可以認為是Iterrator和Set類型的子類。

例如:使用List類型的時候,為E形參傳入String實參,則產生了一個新的類型List<String>,把它想象成E全部被String取代的特殊的List子接口。

public interface ListString extends List
{void add(String x);Iterator<String> iterator();...
}

這樣雖然只是設置了一個List<E>接口,實際實驗時卻是可以產生無數多個List 子接口。

【注意】包含泛型聲明的類型可以在定義變量、創建對象時傳入一個類型實參,從而可以動態的生成無數多個邏輯子表,但這種子類在物理上并不存在。也就是說,List<String>不會被替代成ListString,系統并沒有進行源代碼復制。

②從泛型類派生子類

當定義完泛型接口和泛型父類額時候,我們就可以為接口創建實現類或者從父類派生出子類,不過使用接口和父類的時候,不能再包含類型參數。

//錯誤演示public class A extends List<E>{//A繼承List,List不能跟類型形參
}   //正確演示1
public class A extends List<String>{//A繼承List,為List的E形參傳入String
} //正確演示2
public class A extends List{//A繼承List,也可以不為類型形參傳入實際的類型參數,不過可能會出現unchecked警告
} 

③并不存在泛型類

前面有提到,可以把List<String>類當成是List的子類,這里可能會給大家帶來誤解,實際上,系統并沒有為List<E>生成新的class文件,而且也不會把它當成新的類來處理。這里給一個驗證:

package FanXing;import java.util.ArrayList;
import java.util.List;public class ListErr {public static void main(String[] args) {List<String> aaa = new ArrayList<>();List<Integer> aaa1 = new ArrayList<>();System.out.println(aaa1.getClass()==aaa1.getClass());}
}

從輸出true可以看出,不管為泛型的類型形參傳入哪一種類型實參,對于Java來說,他們依然被當做同一個類來處理,在內存中也只占用一塊內存空間。

3.類型通配符

package FanXing;import java.util.List;public class test {public void test1 (List c){for(int i = 0;i<c.size();i++){System.out.println(c.get(i));}}
}

?這是一個普通的遍歷List集合的代碼,在編譯過程中出現了一個泛型警告,因為在這里使用List接口時沒有傳入實際的參數類型。

修改后:

package FanXing;import java.util.List;public class test {public void test1 (List<?> c){for(int i = 0;i<c.size();i++){System.out.println(c.get(i));}}
}

看到原來的List變成了List<?>,這里就引入了類型通配符的概念。

類型通配符就是一個“?”,它的元素類型可以匹配任何類型。

比如,當使用List<?>時,List就成了任何泛型List的父類,比如List既是List<String>的父類,又是List<Integer>的父類,但是,類型之間沒有繼承關系,String是Object的子類,List<String>不是List<Object>的子類。

①設置類型通配符的上限

格式:List<? extends XXX>它表示所有XXX泛型List的父類

②設定類型形參的上限

例子:

public class List<T extends Number & java.io.Serializable>
{...//表明T類型必須是Number類或者其子類,并且必須實現java.io.Serializable接口
}

注意:為類型參數指定多個上限時,所有的接口上限必須位于類上限之后。

4.泛型方法

格式:

修飾符 <T,S> 返回值類型 方法名 (形參列表)
{//方法體   
}    

泛型方法和類型通配符的區別:

①大多數時候都可以用泛型方法來替換通配符
②使用通配符情況:用來支持靈活的子類化
③泛型方法允許類型形參被用來表示方法的一個或多個參數之間的類型依賴關系,或者方法返回值與參數之間的類型依賴關系。如果沒有這樣的類型依賴關系,就不應該使用泛型方法。
④形參a的類型或返回值的類型依賴于另一個形參b的類型,則b的類型聲明不應該使用通配符,因為使用通配符表示類型b不確定,那么a的類型也不能確定,這時候要考慮使用泛型方法。
⑤類型不被依賴時,使用通配符。

泛型方法與方法重載:

public class MyUtils {// (1)public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {...}// (2)public static <T> T copy(Collection<? super T> dest, Collection<T> src) {...}public static void main(String[] args) {List<Number> ln = new ArrayList<>();List<Integer> li = new ArrayList<>();copy(ln, li); // 這里會編譯報錯
  }
}

允許根據方法參數泛型不同進行方法重載,但是調用時,如果編譯器分不清該調用哪個方法則編譯報錯,上面代碼中有兩個copy方法,調用的時候,編譯器既可以調用第一個copy ,也可以調用第二個copy,這樣它就無法確定調用哪個,就會引起編譯報錯。

5.擦除和轉換

?Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。類型擦除也是Java的泛型實現方式與C++模板機制實現方式之間的重要區別。

參考鏈接:Java泛型使用詳解

【備注1】:Java的編譯類型和運行類型的理解。

Java的編譯時類型是由聲明變量時使用的類型決定,運行時類型是由實際賦值的對象所決定。參考鏈接:Java的編譯類型和運行類型

【備注2】:Java泛型中K T V E ? 分別代表的含義:

E – Element (在集合中使用,因為集合中存放的是元素)

T – Type(Java 類)

K – Key(鍵)

V – Value(值)

N – Number(數值類型)

? – 表示不確定的java類型(無限制通配符類型)參考鏈接:泛型相關

【備注3】:為什么說在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型參數。

初步理解:

因為泛型是要在對象創建的時候才知道是什么類型的,而對象創建的代碼執行先后順序是static的部分,然后才是構造函數等等。所以在對象初始化之前static的部分已經執行了,如果你在靜態部分引用的泛型,那么毫無疑問虛擬機根本不知道是什么東西,因為這個時候類還沒有初始化。因此在靜態方法、數據域或初始化語句中,為了類而引用泛型類型參數是非法的。

?實際原因:

靜態變量是被泛型類所有實例所共享的。對于聲明為MyClass<T>的類,訪問其中的靜態變量的方法仍然是MyClass.myStaticVar。不管是通過new MyClass<String>還是new MyClass<Integer>創建的對象,都是共享一個靜態變量。假設允許類型參數作為靜態變量的類型。那么考慮下面一種情況:

MyClass<String> class1 = new MyClass<String>();MyClass<Integer> class2 = new MyClass<Integer>();class1.myStaticVar = "hello";class2.myStaticVar = 5;

由于泛型系統的類型擦除(type erasure)。myStaticVar被還原成Object類型,然后當調用class1.myStaticVar= "hello"; 編譯器進行強制類型轉換,即myStaticVar = (String)"hello";接著調用class2.myStaticVar語句時,編譯器繼續進行強制類型轉換,myStaticVar = (Integer)Integer.valueOf(5); 此時myStaticVar是String類型的,當然該語句會在運行時拋出ClassCastException異常,這樣一來存在類型安全問題。因此泛型系統不允許類的靜態變量用類型參數作為變量類型。

當然,靜態泛型方法也不允許。
參考鏈接:

有關靜態不允許使用類型參數的討論

為什么類型參數不能作為靜態變量的類型

【備注4】:Java泛型中上下界限定符extends 和 super的理解:

<? extends T>表示類型的上界,表示參數化類型可能是T或者T的子類;

<? super T>表示類型的下界,表示參數化類型是此類型的超類型(父類型),直至Object。

PECS原則:

如果要從集合中讀取類型T的數據,并且不能寫入,可以使用 ? extends 通配符;(Producer Extends)

如果要從集合中寫入類型T的數據,并且不需要讀取,可以使用 ? super 通配符;(Consumer Super)

如果既要存又要取,那么就不要使用任何通配符。

【備注5】:異常類

異常類一般分為兩種:異常(Exception)和錯誤(Error)

Exception就用try&catch&finally來處理,先在try中運行代碼,catch處理可能出現異常,finally是一定會執行到里面的代碼。 
常見的異常有: 
NumberFormatException(數字格式異常) 
IndexOutOfBoundsException(數組越界異常) 
ArithmeticException(除零異常) 
RuntimeException(運行時異常) 
異常常用的方法: 
getMessage():返回異常的詳細描述字符串。 
printStackTrace():跟蹤棧詳細輸出到標準錯誤輸出 
printStackTrace(PrintStream s):跟蹤棧詳細輸出到標準錯誤輸出到指定的輸出流 
getStackTrace():返回異常的跟蹤棧信息。 

Error一般是由虛擬機造成的系統崩潰的。

下一步學習拓展及計劃:

1.總結討論并做成思維導圖

2.理解學習中一直提到的異常這一章的內容

3.擦除的實例(自己嘗試用一個例子實踐)

轉載于:https://www.cnblogs.com/zhangqingwang/p/10609987.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278090.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278090.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278090.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

初學Vue.js,從頭來過~

之前上課也學過vue.js&#xff0c;但是一遍過下來&#xff0c;實話&#xff0c;沒有記住什么&#xff0c;所以決定自己在對照著文檔過一遍&#xff0c;所以之后會弄一些基礎的東西&#xff0c;還請大神們莫噴~~~~今天先給平臺打一個預防針&#xff0c;Young C 要來啦~~ 吼吼&…

從Microsoft Teams技術棧看前端技術發展趨勢

在前不久的微軟美國一年一度的Ignite大會上&#xff0c;微軟宣布Microsoft Teams是微軟歷史上發展最快的應用。它將取代Skype for business&#xff0c;成為語音視頻協作的主打產品。 我也有幸在上個月微軟中國年度技術大會Tech Summit 2018上被邀請作為講師講解基于Teams平臺…

LeetCode 最大正方形

在一個由 0 和 1 組成的二維矩陣內&#xff0c;找到只包含 1 的最大正方形&#xff0c;并返回其面積。 示例: 輸入: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0輸出: 4解法&#xff1a;判斷以某個點為正方形右下角時最大的正方形時&#xff0c;那它的上方&#xff0c;左方和左上…

solidity字符串拼接

如果你希望馬上開始學習以太坊DApp開發&#xff0c;可以訪問匯智網提供的出色的在線互動教程&#xff1a; 以太坊DApp實戰開發入門去中心化電商DApp實戰開發當你開始學習使用solidity開發以太坊智能合約之后&#xff0c;很快你會碰到一個問題&#xff1a; 在solidity中該如何拼…

Confluence Cloud的Teams Message Extension

Confluence Cloud的Message Extension現在正式登入Microsoft Teams。 它可用于團隊頻道和私人聊天&#xff0c;使您的對話更具描述性和信息性。 從Microsoft Teams應用商店獲取Confluence Cloud應用程序并連接到Confluence Cloud實例。 連接后&#xff0c;您將能夠搜索Conflue…

45 | 打蛇打七寸:精準測試

轉載于:https://www.cnblogs.com/lmx0621/p/10614966.html

Teams App統計

周末閑來無事&#xff0c;統計了一下Teams的app商店里的app ( Teams App Store )。截至到現在&#xff08;2018年11月&#xff09;一共有145個app。要注意一點&#xff1a;如果app不是公開的&#xff08;即單獨安裝到Office365租戶里&#xff0c;并沒有提交到office store&…

你必須要懂的APK瘦身知識

隨著業務復雜度的逐漸增加&#xff0c;代碼、資源也在不斷的增加&#xff0c;此時你的APP大小也在增加。從用戶層面來說&#xff0c;面對動輒幾十兆的APP來說在非WIFI情況下還是會猶豫要不要下載&#xff0c;不下載你就可能因此失去了一個用戶。從公司層面來講&#xff0c;流量…

DHT網絡

(基礎技術) 現在有一種方法&#xff0c;可以通過磁力鏈接&#xff0c;例如magnet:?xturn:btih:0482e0811014fd4cb5d207d08a7be616a4672daa&#xff0c;就可以獲取BT文件。 這個是通過DHT網絡來實現的。 DHT網絡是一個去中心化的&#xff0c;分布式信息存儲系統。 存儲的信息就…

Java基礎 Day04(個人復習整理)

分支結構 2、switch語句 因為if語句的級聯式最多只會處理三種情況&#xff0c;如果出現多情況 1>可以繼續使用if語句的級聯式&#xff0c;但是可能代碼的可讀性就會變差。  2>采用switch語句來解決。 switch語法格式&#xff1a; switch (存在多種情況的變量) {case 值…

java如何獲取一個double的小數位數

前言 看標題是不是覺得這是一個很簡單的問題&#xff0c;我一開始也是這么認為的&#xff0c;但是實際情況下&#xff0c;在各種情況下我們都進行了測試&#xff0c;發現很多實際情況是無法不盡如人意的。 方法分析 當前能想到的比較容易有下面幾種 1、直接使用double處理 2、將…

Node文件模塊

在上一篇文章中有提到&#xff0c;Node模塊分為核心模塊和文件模塊&#xff0c;接下來就簡單總結一下文件模塊。 文件模塊則是在運行時動態加載&#xff0c;需要完整的路徑分析、文件定位、編譯執行過程、速度相比核心模塊稍微慢一些&#xff0c;但是用的非常多。這些模塊需要我…

PHP GD庫解析一張簡單圖片并輸出

這里只演示一下2種顏色值的圖片&#xff0c;簡單描述下概念。 首先要安裝下GD庫。否則下面的代碼運行不了。 $size getimagesize(2.png); // 獲取圖片大小 $res imagecreatefrompng(2.png); // 獲取指定圖片的資源對象for ($i 0; $i < $size[1]; $i) {for ($j 0; $j &…

Permutations CodeForces - 736D (矩陣逆)

對于刪除每個對(x,y), 可以發現他對答案的貢獻為代數余子式$A_{xy}$ 復習了一下線代后發現代數余子式可以通過伴隨矩陣求出, 即$A_{xy}A^*[y][x]$, 伴隨矩陣$A^*|A|A^{-1}$可以通過高斯消元$O(\frac{n^3}{\omega})$求出 #include <iostream> #include <algorithm> …

開發Teams的messaging extension

什么是Messaging Extension Messaging Extension是微軟Teams的一種十分有用的擴展方式。可以讓用戶發送adaptive cards。具體的說明不在這里展開了。可以閱讀微軟官方的詳細說明&#xff1a; https://docs.microsoft.com/en-gb/microsoftteams/platform/concepts/messaging-e…

歸并排序(轉)

轉載自&#xff1a;https://www.cnblogs.com/chengxiao/p/6194356.html 歸并排序&#xff08;MERGE-SORT&#xff09;是利用歸并的思想實現的排序方法&#xff0c;該算法采用經典的分治&#xff08;divide-and-conquer&#xff09;策略&#xff08;分治法將問題分(divide)成一些…

Site24x7 為Teams提供可智能 DevOps

我們生活在一個云的時代, SaaS 應用程序每天都在推動我們的生產力。作為一個消費者, 很難想象如果你最喜歡的應用無法訪問&#xff0c;即使只是一秒鐘無法訪問。作為 SaaS業務, 更難以想象您的服務面臨停機, 每一分鐘停止服務都會花費大量的資金, 當然還損失客戶的信任。Site24…

XUbuntu22.04之跨平臺容器格式工具:MKVToolNix(二百零三)

簡介&#xff1a; CSDN博客專家&#xff0c;專注Android/Linux系統&#xff0c;分享多mic語音方案、音視頻、編解碼等技術&#xff0c;與大家一起成長&#xff01; 優質專欄&#xff1a;Audio工程師進階系列【原創干貨持續更新中……】&#x1f680; 優質專欄&#xff1a;多媒…

redis集群搭建踩坑筆記

推薦參考教程&#xff1a;https://blog.csdn.net/pucao_cug/article/details/69250101 錯誤&#xff1a; from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in require from /usr/local/redis-3.0.6/src/redis-trib.rb:25:in <main> 解決&#xff1a; g…

Docker 創建鏡像

文章首發自個人網站&#xff1a;https://www.exception.site/docker/docker-create-image 本文中&#xff0c;您將學習 Docker 如何創建鏡像&#xff1f;Docker 創建鏡像主要有三種&#xff1a; 基于已有的鏡像創建&#xff1b;基于 Dockerfile 來創建&#xff1b;基于本地模板…