主要內(nèi)容
(1)能夠定義簡單的泛型類、泛型方法
(2)知曉類型變量的限定規(guī)則和應(yīng)用
(3)理解虛擬機(jī)是如何解析泛型代碼
(4)明白在使用泛型的時候需要注意的一些限制
(5)理解泛型類型的繼承規(guī)則
(6)學(xué)會使用通配符類型
(7)知道反射和泛型之間的關(guān)系以及如何利用反射操作泛型代碼
上面(1)-(5)屬于泛型的初級知識也是這篇文章的主要內(nèi)容,而(6)-(7)屬于泛型的高級知識,留著下次說。
1 泛型存在的意義(敲黑板)
泛型存在的意義就是:代碼可以被很多不同類型的對象所重用。
例如:
List list = new ArrayList();
list.add("CSDN_SEU_Calvin");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); //取出Integer時,運(yùn)行時出現(xiàn)異常
System.out.println("name:" + name);
}
例子中向list類型集合中加入了一個字符串類型的值和一個Integer類型的值(這樣是合法的,因?yàn)榇藭rlist默認(rèn)的類型為Object類型)。但是在取值的時候就會出現(xiàn)問題,運(yùn)行時會出現(xiàn)java.lang.ClassCastException。
泛型提供了一種解決方案:類型參數(shù)。例如下面代碼:
List<String>list = new ArrayList<String>();
這樣編譯器就知道存入的值是String類型的,那么在存入的值的時候就會提示類型不匹配,從而避免上述問題。使得代碼具有更好的可讀性和安全性
好了,在了解的泛型存在的意義之后,下面我們開始由表及里的去了解泛型。
2 定義簡單泛型類
一個泛型類就是具有一個或多個類型變量的類。
public class Pair<T>
{
public Pair() { first = null; second = null; }
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
}
在Pair類中, <T>就是類型變量,放在類名后面。泛型類可以有很多個類型變量。例如,
public class Pair<T,U>{......}
重點(diǎn):類型變量指定方法的返回類型以及域和局部變量的類型。
例如上面的Pair類中,返回值類型就是<T>,域和局部變量的類型都是<T>。當(dāng)然在實(shí)際運(yùn)用中<T> 會用實(shí)際類型代替。
Pair<String>
則Pair類中的返回值類型,域和局部變量的類型都是String類型的。
其實(shí),泛型類就是普通類的工廠。
下面看如何使用這個泛型類,重點(diǎn)??!
public class PairTest1
{
public static void main(String[] args)
{
String[] words = { "1", "2", "3", "4", "5" };
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
class ArrayAlg
{
public static Pair<String> minmax(String[] a)
{
if (a == null || a.length == 0) return null;
String min = a[0];
String max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<String>(min, max);
}
}
上面的PairTest1類是比較得出"1", "2", "3", "4", "5"這些字符中最大的值和最小值。

對于上面的類,有一下幾點(diǎn)需要掌握:
1、通過利用泛型類Pair<T>完成了對于傳入類型參數(shù)的比較。這個里面可以是String也可以是Double等類型。
2、該類通過指定顯示的類型參數(shù)String,對參數(shù)值進(jìn)行了比較,用Pair對象返回了兩個String類型的結(jié)果。
3、String類實(shí)現(xiàn)了Comparable接口,所以可以直接調(diào)用compareTo方法對String進(jìn)行比較。
3 泛型方法
如何定義一個帶有類型參數(shù)的簡單方法呢?
public static <T> T getMiddle(T a){
..................
}
首先,這是一個泛型方法,從尖括號和類型變量可以看出。注意,類型變量<T>放在修飾符(public static)的后面,在返回值類型 T 的前面。
泛型方法可以定義在普通的類中,也可以定義在泛型類中。
4 類型變量的限定
很多時候,類或方法需要對類型變量加以約束。
class ArrayAlg
{
public static <T extends Comparable> Pair<T> minmax(T[] a)
{
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<T>(min, max);
}
}
這里有一個問題:變量min和max類型是T,也就是說T可以是任意一個類的對象,那編譯器怎么知道T所屬的類有compareTo方法呢?
解決這個問題的方法就是將T限制為實(shí)現(xiàn)了Comparable接口的類。這就需要通過對類型變量T設(shè)置限定來實(shí)現(xiàn):
public static <T extends Comparable> T min(T[] a)
這里T表示的是綁定類型的子類型。T和綁定類型可以是類,也可以是接口。
下面的類更詳細(xì)的說明了這個問題
public class PairTest2
{
public static void main(String[] args)
{
GregorianCalendar[] birthdays =
{
new GregorianCalendar(1906, Calendar.DECEMBER, 9),
new GregorianCalendar(1815, Calendar.DECEMBER, 10),
new GregorianCalendar(1903, Calendar.DECEMBER, 3),
new GregorianCalendar(1910, Calendar.JUNE, 22),
};
Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays);
System.out.println("min = " + mm.getFirst().getTime());
System.out.println("max = " + mm.getSecond().getTime());
}
}
class ArrayAlg
{
public static <T extends Comparable> Pair<T> minmax(T[] a)
{
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<T>(min, max);
}
}
通過指定< T extends Comparable > 表明類型參數(shù)需要是實(shí)現(xiàn)Comparable接口的類。從而實(shí)現(xiàn)了對于GregorianCalendar的比較。
5 虛擬機(jī)如何解析泛型代碼
重點(diǎn):虛擬機(jī)泛型對象-----所有的對象都屬于普通類。
無論何時定義一個泛型類型,虛擬機(jī)都會提供一個相應(yīng)的原始類型。
例如,Pair類在虛擬機(jī)中會被識別為:
public class Pair<T>
{
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
}
//虛擬機(jī)識別為
public class Pair
{
public Pair() { first = null; second = null; }
public Pair(Object first, Object second) { this.first = first; this.second = second; }
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
private Object first;
private Object second;
}
在這里原始類型的名字Pair就是Pair<T>類型擦除之后的泛型類型名稱。將類型變量替換為限定類型(< T extends Comparable >替換為Comparable ),無限定類型就用Object。這里T是一個無限定的變量,所以直接用Object替換。
擦除的概念很重要,貫穿在泛型代碼的編譯過程。
5.1 翻譯泛型表達(dá)式
當(dāng)調(diào)用泛型方法時,如果擦除返回類型。編譯器將插入強(qiáng)制類型轉(zhuǎn)換。例如:
Pair<Person> p = ...
Person p = p.getName();
編譯器會把這個方法翻譯為兩條虛擬機(jī)指令:
調(diào)用p.getName()得到Object類型返回值
將返回的Object類型強(qiáng)制轉(zhuǎn)化為Person類型
5.2 翻譯泛型方法(難點(diǎn))
類型擦除也出現(xiàn)在泛型方法中。
public static <T extends Comparable> T min(T[] a)
類型擦除之后變?yōu)?/p>
public static Comparable min(Comparable[] a)
這種方法擦除在有些時候會帶來一些問題。
class DateInterval extends Pair<Date>{
public void setSecond(Date second){
if(second.CompareTo(getFirst())>=0){
.......
}
}
}
這個類在擦除之后變?yōu)?/p>
class DateInterval extends Pair{
public void setSecond(Date second){.........}
}
但是,DateInterval類繼承了Pair類,該類本身就有一個setSecond方法
public void setSecond(Object second)
這時,如果調(diào)用下面的語句
DateInterval interval = new DateInterval(......);
Pair<Date> pair = inteval;
pair.setecond(aDate);
編譯器是會調(diào)用自身的setSecond(Date second),還是繼承自Pair類中的setSecond(Object second)呢?
這里,setSecond的調(diào)用是具有多態(tài)性的。由于pair引用了DateInterval 對象,所以就應(yīng)該是調(diào)用DateInterval.setSecond。問題就在于,類型的擦除與多態(tài)之間發(fā)生了沖突。
為了解決這個問題,編譯器在DateInterval 類中生成了一個橋方法
public void setSecond(Object second){
setSecond((Date) second);
}
那么,編譯器的調(diào)用過程就是:首先pair已經(jīng)聲明為類型Pair<Date>,這個類型的方法就是setSecond(Object second)。虛擬機(jī)用pair引用的對象調(diào)用這個方法,但是這個對象是DateInterval 類型的,所以會調(diào)用DateInterval.setSecond(Object)方法。這個方法就是合成的橋方法。該方法會調(diào)用DateInterval .setSecond(Date)(見上面代碼)。如此,完成了我們希望的調(diào)用。
咳咳,好了,今天先到這兒了!
總結(jié)一下上面所說的重點(diǎn):
- 虛擬機(jī)中沒有泛型,只有普通的類和方法。
- 所有的類型參數(shù)都用它們的限定類型替換。
- 為保持類型安全性,必要時插入強(qiáng)制類型轉(zhuǎn)換。
- 橋方法被用來合成多態(tài)。
未完待續(xù)......
O(∩_∩)O哈哈~