1.java泛型是java se 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的資料類型被指定為一個參數。這種參數類型可以用在類、接口和方法的建立中,分别稱為泛型類、泛型接口、泛型方法。
2.java泛型可以讓你消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人将錯誤類型的鍵或值儲存在集合中。這就是泛型所做的工作。
先來看看以下代碼,
public static void main(string[] args) {
list arraylist = new arraylist();
arraylist.add("abc");
arraylist.add(123);
arraylist.add(23.32);
string abc = (string) arraylist.get(0);------------------------------------------1
integer abd = (integer) arraylist.get(0);---------------------------------------2
}
注意看看位置1,2的代碼出現的問題。這段代碼編譯時沒錯,但運作時報錯
exception in thread "main" java.lang.classcastexception: java.lang.string cannot be cast to java.lang.integer
at shipin38.test1.main(test1.java:28)
arraylist集合在沒有用泛型前,它什麼都能裝,當調用時就要進行強制轉換,如果不注意就會出現将string類元素,轉化為intege元素,但是編譯器又不會報錯,運作時才會報錯。這樣存在安全隐患。是以我們需要泛型。
java語言中引入泛型是一個較大的功能增強。不僅語言、類型系統、編譯器有了較大的變化,用以支援泛型,而且類庫也進行了大翻修,是以許多重要的類,比如集合架構,都已經成為泛型化的了。
1.類型安全。泛型的主要目标是提高java程式的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就隻存在于程式員的頭腦中(如果幸運的話,在存在于代碼注釋中)。
2.消除強制類型轉換。泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,并且減少了出錯機會。(更利于團隊的合作開發。)
3.潛在的性能收益。泛型為較大的優化帶來可能。在泛型的初始實作中,編譯器将強制類型轉換(沒有泛型的話,程式員會指定這些強制類型轉換)插入生成的位元組碼中。但是更多類型資訊可用于編譯器這一事實,為未來版本的jvm(虛拟機)的優化帶來可能。由于泛型的實作方式,支援泛型(幾乎)不需要更改jvm或類檔案。所有工作都在編譯器中完成,編譯器生成類似于沒有泛型(和強制類型轉換)時所寫的代碼,隻是更能確定類型安全而已。
總結:java語言引入泛型的好處是安全簡單。泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隐式的,提高代碼的重用率。
泛型是給javac(編譯器)使用的,泛型可以限定集合中的輸入類型,讓編譯器擋住源程式中的非法輸入,(比如向arraylist<string>集合中添加一個數字這樣的錯誤操作),但是當javac編譯帶類型說明的集合時,會去掉類型資訊,(類型資訊:類型資訊我的了解就是泛型數組<>中的元素,不如<string><integer><object>等)。擦除類型資訊是為了讓程式的運作效率不受影響。為了更直覺的了解擦除類型資訊,你可以在程式中用arraylist<string>和arraylist<integer>,并調用他們的getclass()方法,然後對比這2個的傳回值,如果相同,那說明類型資訊被擦除了。
由于編譯器編譯檔案後,會擦除類型資訊,是以如果我們想辦法跳過編譯器,就可以向arraylist<string>中添加數字。是以泛型是給編譯器使用的。
示例代碼:
public static void main(string[] args) throws illegalargumentexception, securityexception, illegalaccessexception, invocationtargetexception, nosuchmethodexception {
//一般的做法,無法跳過編譯器,編譯報錯。
arraylist<string> a = new arraylist();
a.add("abc");
a.add(123);//這點編譯報錯
//用反射的方法,跳過編譯器,并向字元串數組添加數字
class temp = a.getclass();
temp.getmethod("add", object.class).invoke(a, 123);
for (object string : a) {
system.out.println(string);
}
}
列印輸出:
abc
123
術語(terminology)是在特定學科領域用來表示概念的稱謂的集合,術語是通過語音或文字來表達或限定科學概念的約定性語言符号,是思想和認識交流的工具。
了解術語是為了便于交流,同一樣事物,a同學有a同學的叫法,b同學有b同學的叫法,當讨論同一樣事物時會有多種叫法,那樣大家到底在說的什麼就不是很明确,但是當統一了術語以後,大家就友善交流和讨論。
整個arraylist<e>表達式稱着什麼? 泛型類型
整個arraylist<string>表達式稱着什麼? 參數化的泛型類型
arraylist<e>表達式中e稱着什麼? 泛型類型參數或泛型類型變量
arraylist<string>表達式中的string稱着什麼? 泛型類型參數的執行個體或實際泛型類型參數
arraylist稱着什麼? 原始類型
collection<string> c = new vector();//小問題:這裡為什麼不是,collection<string> c = new collection<string>();呢?因為collection是接口,接口不能執行個體化。
解釋:在jdk1.5以前,是沒有泛型概念的,程式員們都是用原始類型來寫程式的,到了jdk1.5和以後出現了泛型概念,為了讓以前的程式能運作,是以就支援了上面的寫法。(題外話:可不可以,還不是編譯器說了算,泛型是給編譯器使用的。)
collection c = new vector(string);
同理,這種寫法是可以的。
3-1、vector<string> a = new vector<object>();//寫法錯誤,如果不寫右邊的<object>,那右邊就變成了原始類型,寫法就正确。
原因:先單獨看指派符号"="的左邊,vector<string> a,表示a集合中隻能裝string類型的元素。再單獨看指派符号"="的右邊,vector<object>();,表示集合裡面什麼元素都能裝,那我裝個數字23進去也是可以的。現在把指派符号"="兩邊聯系起來看,整個表達式就是把vector<object>();裡面的元素指派給vector<string>,而vector<object>()裡面又裝得有數字23,如果上面的寫法可以的話,就會發生想字元串集合vectro<string> a中放入數字23的錯誤,是以上面的寫發編譯器不讓通過。
3-2、vector<object> v = new vector<string>();//也錯誤
原因:vector<object>會指向new vector<string>()的位址,如果可以會發生integer(說integer對象是因為vector<object>集合中可以裝integer對象)對象元素指向string對象元素的錯誤。是以上面的寫法編譯器同樣不讓編譯通過-----------(關于這個推理我是很不明白。)
vector<integer>[] vec = new vector<integer>[10];//這個寫法知道會錯就行,就算不知道,寫出來後編譯器會報錯,(自從有了編譯器,java變成了一門安全的程式語言)。
vector v = new vector<string>();//不會報錯,原始類型引用一個參數化類型對象
vector<object> v1 = v; //不會報錯,參數化類型引用一個原始類型對象
還有一點,javac(編譯器)是一行一行的編譯代碼的。
要求把下面的方法
public static void printcollection(collection<string> collection){
for (object string : collection) {
}
}
改寫,使之能列印出任意參數化類型的集合中的所有資料。還有個要求就是隻能改collection<string>中的<>裡面的參數類型的執行個體,其他地方不能改。
有的人會改成
public static void printcollection(collection<object> collection){
}
如果這樣是可以的話,那我們在main方法中來調用看看
public static void main(string[] args) {
collection<integer> a = new arraylist<integer>();
a.add(1);
a.add(2);
printcollection(a);-----------------5
當我在-----5那調用方法時,程式相當于把a集合指派給collection,就像這樣 collection<object> collection = collection<integer>() a;這樣就犯了上面所說的(指向型)錯誤,是以編譯報錯。
為了解決上面的問題,jdk1.5提供了泛型通配符"?",正确改寫
public static void printcollection(collection<?> collection){
?代表了,?可以指向任意類型,你不管傳什麼過來,collection都能接收。
完整的示例代碼:
collection<integer> a = new arraylist<integer>();
a.add(1);
a.add(2);
printcollection(a);
collection<string> b = new arraylist<string>();
b.add("a");
b.add("b");
printcollection(b);
列印輸出
1
2
a
b
public static void printcollection(collection<?> collection){
collection.add("abc"); --------------1
collection.add(123); --------------2
collection.size(); --------------3
上面1,2和3位置的代碼對嗎?為什麼?
1,2不對,由于?表示不管調用者傳什麼類型的集合,它都接受,就産生了不确定因素,如果調用者傳integer類型的集合呢?你用integer類型的集合去添加string對象的元素,那位置1的代碼肯定是錯的。如果調用者傳string類型的集合呢?同理,位置2的代碼也是錯的。3是對的,因為3位置的方法和參數化無關。不管什麼樣的類型都有個size()方法。
在幫助文檔中collection的add()方法和size()方法對比。

public static void main(string[] args) {
printcollection(a,"string");-----------1
printcollection(b,"abc");--------------2
public static <t> void printcollection(collection<t> collection,t a){
for (t string : collection) {
collection.add(a);
看看位置1和2的代碼的不同,為什麼位置1的代碼編譯報錯。
因為:位置1中的a集合,裡面隻能裝integer對象的元素,而位置1添加的卻是string對象的元素,是以編譯報錯。
collection = new hashset<string>();----------1
collection = new collection<string>();-------2
位置1和位置2的代碼對嗎?
1是對的,因為“?”接受任意類型,你重新讓他指向string類的集合是可以的,調用者傳遞東西進來也等于是指派。2是錯的,因為collection是一個接口,接口是不能執行個體化的。接口沒有構造方法。
使用“?”通配符可以引用其他各種參數化的類型,?通配符定義的變量主要用作引用,可以調用與參數化無關的方法,不能調用與參數化有關的方法。
vector<? extends number> x = new vector<number>();//正确
vector<? extends number> x = new vector<integer>();//正确
vector<? extends number> x = new vector<double>();//正确
vector<? extends number> x = new vector<string>();//錯誤
extends代表number和number的子類
vector<? super number> x = new vector<number>();//正确
vector<? super number> x = new vector<object>();//正确
vector<? super number> x = new vector<byte>();//錯誤
super代表number和number的父類
提示:限定通配符總是包括自己
問題一:class<string> x = class.forname("java.lang.string");這樣的寫法為什麼錯。
class.forname("java.lang.string");的傳回值是class<?>;
上面的表達式類似于class<string> x = class<?>;//指向型錯誤
map集合用得已經很熟悉了,老師說的東西我在看幫助文檔的情況下也能弄明白,就不再做筆記了。
在c++中想要達到一個加法運算,并且能實作任意number類元素的相加,在不使用c++模闆函數的情況下要這樣寫
int add(int x ,int y){
return x+y;
}
float add(float x ,float y){
double add(double x ,double y){
這樣寫很麻煩,會産生許多方法。代碼量也很大。
于是c++用模闆函數來解決這一問題,隻寫一個通用的方法,就可以适應各種類型。
template<class t>
t add(t x,t y){
return(t)(x+y);
t代表類型不詳,調用的時候,你傳什麼類型過來,t就是什麼類型。
java的泛型借鑒了上面的方法
1.java中的泛型類型(或者泛型)類似于c++中的模闆。但是這種相似性僅限于表面,java語言中的泛型基本上完全是在編譯器中實作,用于編譯器執行類型檢查和類型判斷,然後生成普通的非泛型的位元組碼,這種實作技術稱為擦除(erasure)(編譯器使用泛型類型資訊保證類型安全,然後在生成位元組碼之前将其清除)。這是因為要擴充虛拟機指令集來支援泛型被認為是無法接受的,工作量巨大,是以,java的泛型采用了可以完全在編譯器中實作的擦除方法。
一個泛型類(generic class)就是具有一個或多個類型變量的類。如
/**
* 泛型類
*
* @author terry
* @date 2014-5-19
*/
public class genericclass<t> {
/**
* @param args
*/
// todo auto-generated method stub
genericclass類引入了一個類型變量t,用尖括号(<>)括起來,并放在類名的後面。泛型類可以有多個類型變量。例如,
public class genericclass<t,u>{...}
類型變量使用大寫形式,且比較短,這是很罕見的。在java類庫中,使用變量e表示集合的元素類型,k和v分别表示表的關鍵字與值的類型。t(需要時還可以用臨近的字母u和s)表示“任意類型“。
* crud(增删查改)
* @date 2014-5-21
public class genericsdao {
public <t> void add(t a){
public <t> t findbyid(object id){
return null;
genericsdao dao = new genericsdao();
dao.add(23);//在這裡我添加一個數字進去--------------------------------1
string find = dao.findbyid(1);//把剛剛填進去的23找出來--------------2
在這裡我添加了一個數字進去,并把該數字查找出來,前面用的是string類來轉載的,編譯沒報錯,但運作的時候就會報錯。此時add方法中的t和findbyid中的t實際上是兩個相對獨立的泛型變量,為了讓他們之間有聯系着時候就需要泛型類來完成。例:
public class genericsdao<t> {
public void add(t a){
public t findbyid(object id){
}
public static void add2(t a){ }---------------------------------------------------這裡編譯報錯,因為此靜态方法比genericsdao先存在,而泛型變量t又和genericsdao相關。
public static <t> void add3(t a){ }---------------------------------------------這裡編譯通過,因為此時的泛型參數t是獨立的,它和genericsdao類無關。
dao.add(23);//在這裡我添加一個數字進去
string find = dao.findbyid(1);//把剛剛填進去的23找出來------------------1這裡編譯報錯,錯誤提示:type mismatch: cannot convert from object to string
類型不比對:不能轉換的對象為string
object find1 = dao.findbyid(1);----------------------------------------------------這裡編譯通過
genericsdao<integer> dao1 = new genericsdao<integer>();
dao1.add("string");------------------------------------------------------------------2這裡編譯報錯,錯誤提示:the method add(integer) in the type genericsdao<integer> is not
applicable for the arguments (string)
dao1.add(13);--------------------------------------------------------------------------這裡編譯通過
string find2= dao1.findbyid(1);---------------------------------------------------3這裡編譯報錯,錯誤提示:type mismatch: cannot convert from integer to string
integer find3 = dao1.findbyid(1);--------------------------------------------------這裡編譯通過
從這裡可以看出當泛型類genericsdao,沒有指定泛型參數時,泛型類裡面的t,編譯器把他看成object了。這裡是從上面位置1的錯誤提示推斷出來的。
示例:
* 泛型方法
* @date 2014-5-20
public class genericclass {
* 泛型方法
* @param a
* @return
public static <t> t getmiddle(t[] a){
return a[a.length/2];
這個方法是在普通類中定義的,而不是在泛型類中定義的,然而,這是一個泛型方法,可以從尖括号和類型變量看出這一點。注意,類型變量放在修飾符的後面,傳回類型的前面。泛型方法可以定義在普通類中,也可以定義在泛型類中。
問題一、
public static void main(string[] args) throws exception {
int[] c = new int[]{1,2,3,4,5};
string[] d = new string[]{"a","b","c","d","e"};
exchange(c,1,2);-------------------------------------1
exchange(d,1,2);
為什麼位置1的代碼編譯報錯?
答:因為位置一傳入的類型為基本資料類型,而泛型的類型參數隻能是類類型(包括自定義類),不能是簡單類型。是以報錯
* 自動将object類型的對象轉換成其他類型
* @param a:要轉的object對象
* @return 轉好後的對象
public static <t> t automaticconversion(object a) {
return (t) a;
* 可以将任意類型的數組中的所有元素填充為相應類型的某個對象。
* @param a:要填充的數組
* @param b:要填充的元素
public static <t,u> void filltheelements(list a,u b){
for (int i = 0; i < a.size(); i++) {
a.remove(i);
a.add(i, b);
* 列印任意元素或任意數組中的元素的方法
* @param a:可以是數組,也可以是單個元素
public static <t extends exception,u> void print(collection<u> a)throws t{
for (u t : a) {
system.out.print(t+"\t");
try {
} catch (exception e) {
// todo: handle exception
list<string> b = new arraylist<string>();
b.add("1");
b.add("2");
filltheelements(b,"hah");
print(b);
hah hah
在這種情況下,前面的?通配符方案要比泛型方法更有效。當一個類型變量用來表達兩個參數之間或參數和傳回值之間的關系時,既同一個類型變量在方法簽名的兩處被使用,或者類型變量在方法體代碼中也被使用而不是僅在簽名的時候使用,才需要使用泛型方法。
* 列印任意元素或任意數組中的元素的方法,最後在添加一個t(任意類型)類型的元素,
* 未用通配符。添加成功。
*
public static <t> void print1(collection<t> a,t b){
for (t t : a) {
a.add(b);//--------------編譯成功
* 用通配符。添加失敗。
public static <t> void print2(collection<?> a,t b){
for (object t : a) {
//a.add(b);//-------------編譯報錯
* 把任意參數類型的集合中的資料安全地複制到相應類型的數組中。
* @param a:要複制的集合
* @param b:相應的數組
* @return 裝好的數組
* @throws exception
public static <t> t[] copy(collection<t> a,t[] b) throws exception{
int i = 0;
b[i] = (t) t;
i++;
return b;
public static <t extends exception,u> void print(u[] b)throws t{
for (u t : b) {
system.out.println(t);
arraylist<string> a = new arraylist<string>();
a.add("a");
a.add("b");
string[] a1 = new string[a.size()];
print(copy(a, a1));
arraylist<integer> b = new arraylist<integer>();
b.add(1);
b.add(2);
b.add(3);
integer[] b1 = new integer[b.size()];
print(copy(b, b1));
運作列印輸出:
3
關于這點,看了視訊也不是很明白,我感覺這應該不是很重要吧,為了節約時間先不管了,若以後覺得重要的話,再回來看看。這裡隻做個引子好了。(相關視訊:張孝祥_java基礎加強_第二部分視訊41_12分)
// todo auto-generated method stub
applyvector(new vector<date>());
* 通過反射來拿到泛型裡面的實際類型(vector<date> 中的date)
* 擷取參數化泛型中的泛型參數的執行個體(vector<date> 中的date)
* @param v1
public static void applyvector(vector<date> v1) throws exception{
//獲得applyvector方法
method applymethod = generics.class.getmethod("applyvector", vector.class);
//獲得方法運作時,傳過來的參數
type[] types = applymethod.getgenericparametertypes();
//parameterizedtype 表示參數化類型,如 collection<string>。
//parameterized:參數化的
//type:類型
parameterizedtype ptype = (parameterizedtype)types[0];
system.out.println(ptype.getactualtypearguments()[0]);
//小細節:注意方法的傳回值
public void a(vector<integer> a){}
public void a(vector<string> a){}
這兩個方法是重載嗎?
答:不是,因為編譯過後會查出類型資訊,查處後就是這樣
public void a(vector a){}
而這2個方法不屬于重載。
1.泛型的類型參數隻能是類類型(包括自定義類),不能是簡單類型。
2.同一泛型可以對應多個版本(因為參數類型是不确定的),不同版本的泛型類型執行個體是不相容的。
3.泛型的類型參數可以有多個。
4.泛型的參數類型可以使用extends語句,例如<t extends superclass>。習慣上稱為”有界類型“。
5.泛型的參數類型還可以是通配符類型。例如class<?> classtype = class.forname(java.lang.string);
小技巧:
1、如果要使用t extends a & b,或t super a & b,為了提高效率,應該将标簽(tagging)接口放在邊界清單的末尾。-----------來至書:java核心技術卷一,p531的注釋。
2、如果一個類中的多個方法使用泛型,那就用類級别的泛型。------來至張孝祥_java基礎加強_第二部分_視訊42_17分54秒