Java泛型 --(1)泛型基礎(chǔ)篇

主要內(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"這些字符中最大的值和最小值。

image.png

對于上面的類,有一下幾點(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哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容