欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java泛型詳解

 更新時(shí)間:2016年03月18日 08:56:21   投稿:hebedich  
本文給大家匯總介紹了下java中的泛型的相關(guān)資料,包括引入泛型機(jī)制的原因,泛型類,泛型方法,泛型的實(shí)現(xiàn)以及泛型的注意事項(xiàng),非常的詳細(xì),有需要的小伙伴可以參考下

1. Why ——引入泛型機(jī)制的原因

    假如我們想要實(shí)現(xiàn)一個(gè)String數(shù)組,并且要求它可以動(dòng)態(tài)改變大小,這時(shí)我們都會(huì)想到用ArrayList來(lái)聚合String對(duì)象。然而,過(guò)了一陣,我們想要實(shí)現(xiàn)一個(gè)大小可以改變的Date對(duì)象數(shù)組,這時(shí)我們當(dāng)然希望能夠重用之前寫(xiě)過(guò)的那個(gè)針對(duì)String對(duì)象的ArrayList實(shí)現(xiàn)。

    在Java 5之前,ArrayList的實(shí)現(xiàn)大致如下:

public class ArrayList {
  public Object get(int i) { ... }
  public void add(Object o) { ... }
  ...
  private Object[] elementData;
}

    從以上代碼我們可以看到,用于向ArrayList中添加元素的add函數(shù)接收一個(gè)Object型的參數(shù),從ArrayList獲取指定元素的get方法也返回一個(gè)Object類型的對(duì)象,Object對(duì)象數(shù)組elementData存放這ArrayList中的對(duì)象, 也就是說(shuō),無(wú)論你向ArrayList中放入什么類型的類型,到了它的內(nèi)部,都是一個(gè)Object對(duì)象。

    基于繼承的泛型實(shí)現(xiàn)會(huì)帶來(lái)兩個(gè)問(wèn)題:第一個(gè)問(wèn)題是有關(guān)get方法的,我們每次調(diào)用get方法都會(huì)返回一個(gè)Object對(duì)象,每一次都要強(qiáng)制類型轉(zhuǎn)換為我們需要的類型,這樣會(huì)顯得很麻煩;第二個(gè)問(wèn)題是有關(guān)add方法的,假如我們往聚合了String對(duì)象的ArrayList中加入一個(gè)File對(duì)象,編譯器不會(huì)產(chǎn)生任何錯(cuò)誤提示,而這不是我們想要的。

    所以,從Java 5開(kāi)始,ArrayList在使用時(shí)可以加上一個(gè)類型參數(shù)(type parameter),這個(gè)類型參數(shù)用來(lái)指明ArrayList中的元素類型。類型參數(shù)的引入解決了以上提到的兩個(gè)問(wèn)題,如以下代碼所示:

ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); //無(wú)需進(jìn)行強(qiáng)制轉(zhuǎn)換
s.add(123); //編譯錯(cuò)誤,只能向其中添加String對(duì)象
...

  在以上代碼中,編譯器“獲知”ArrayList的類型參數(shù)String后,便會(huì)替我們完成強(qiáng)制類型轉(zhuǎn)換以及類型檢查的工作。

2. 泛型類

    所謂泛型類(generic class)就是具有一個(gè)或多個(gè)類型參數(shù)的類。例如:

public class Pair<T, U> {
  private T first;
  private U second;

  public Pair(T first, U second) {
    this.first = first;
    this.second = second;
  }

  public T getFirst() {
    return first;
  }

  public U getSecond() {
    return second;
  }

  public void setFirst(T newValue) {
    first = newValue;
  }

  public void setSecond(U newValue) {
    second = newValue;
  }
}

   上面的代碼中我們可以看到,泛型類Pair的類型參數(shù)為T(mén)、U,放在類名后的尖括號(hào)中。這里的T即Type的首字母,代表類型的意思,常用的還有E(element)、K(key)、V(value)等。當(dāng)然不用這些字母指代類型參數(shù)也完全可以。

    實(shí)例化泛型類的時(shí)候,我們只需要把類型參數(shù)換成具體的類型即可,比如實(shí)例化一個(gè)Pair<T, U>類我們可以這樣:

Pair<String, Integer> pair = new Pair<String, Integer>();

3. 泛型方法

    所謂泛型方法,就是帶有類型參數(shù)的方法,它既可以定義在泛型類中,也可以定義在普通類中。例如:

public class ArrayAlg {
  public static <T> T getMiddle(T[] a) {
    return a[a.length / 2];
  }
}

    以上代碼中的getMiddle方法即為一個(gè)泛型方法,定義的格式是類型變量放在修飾符的后面、返回類型的前面。我們可以看到,以上泛型方法可以針對(duì)各種類型的數(shù)組調(diào)用,在這些數(shù)組的類型已知切有限時(shí),雖然也可以用過(guò)重載實(shí)現(xiàn),不過(guò)編碼效率要低得多。調(diào)用以上泛型方法的示例代碼如下:

String[] strings = {"aa", "bb", "cc"};
String middle = ArrayAlg.getMiddle(names);

4. 類型變量的限定

    在有些情況下,泛型類或者泛型方法想要對(duì)自己的類型參數(shù)進(jìn)一步加一些限制。比如,我們想要限定類型參數(shù)只能為某個(gè)類的子類或者只能為實(shí)現(xiàn)了某個(gè)接口的類。相關(guān)的語(yǔ)法如下:

    <T extends BoundingType>(BoundingType是一個(gè)類或者接口)。其中的BoundingType可以多于1個(gè),用“&”連接即可。

5. 深入理解泛型的實(shí)現(xiàn)

    實(shí)際上,從虛擬機(jī)的角度看,不存在“泛型”概念。比如上面我們定義的泛型類Pair,在虛擬機(jī)看來(lái)(即編譯為字節(jié)碼后),它長(zhǎng)的是這樣的:

public class Pair {
  private Object first;
  private Object second;

  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;
  }
}

    上面的類是通過(guò)類型擦除得到的,是Pair泛型類對(duì)應(yīng)的原始類型(raw type)。類型擦除就是把所有類型參數(shù)替換為BoundingType(若未加限定就替換為Object)。

     我們可以簡(jiǎn)單地驗(yàn)證下,編譯Pair.java后,鍵入“javap -c -s Pair”可得到:

    上圖中帶“descriptor”的行即為相應(yīng)方法的簽名,比如從第四行我們可以看到Pair構(gòu)造方法的兩個(gè)形參經(jīng)過(guò)類型擦除后均已變?yōu)榱薕bject。

    由于在虛擬機(jī)中泛型類Pair變?yōu)樗膔aw type,因而getFirst方法返回的是一個(gè)Object對(duì)象,而從編譯器的角度看,這個(gè)方法返回的是我們實(shí)例化類時(shí)指定的類型參數(shù)的對(duì)象。實(shí)際上, 是編譯器幫我們完成了強(qiáng)制類型轉(zhuǎn)換的工作。也就是說(shuō)編譯器會(huì)把對(duì)Pair泛型類中g(shù)etFirst方法的調(diào)用轉(zhuǎn)化為兩條虛擬機(jī)指令:

    第一條是對(duì)raw type方法getFirst的調(diào)用,這個(gè)方法返回一個(gè)Object對(duì)象;第二條指令把返回的Object對(duì)象強(qiáng)制類型轉(zhuǎn)換為當(dāng)初我們指定的類型參數(shù)類型。

    類型擦除也會(huì)發(fā)生于泛型方法中,如以下泛型方法:

public static <T extends Comparable> T min(T[] a)
    編譯后經(jīng)過(guò)類型擦除會(huì)變成下面這樣:

public static Comparable min(Comparable[] a)
    方法的類型擦除會(huì)帶來(lái)一些問(wèn)題,考慮以下的代碼:

class DateInterval extends Pair<Date, Date> {
  public void setSecond(Date second) {
    if (second.compareTo(getFirst()) >= 0) {
      super.setSecond(second);
    }
  }
  ...
}

    以上代碼經(jīng)過(guò)類型擦除后,變?yōu)椋?/p>

class DateInterval extends Pair {
  public void setSecond(Date second) { ... }
  ...
}

    而在DateInterval類還存在一個(gè)從Pair類繼承而來(lái)的setSecond的方法(經(jīng)過(guò)類型擦除后)如下:

public void setSecond(Object second)
    現(xiàn)在我們可以看到,這個(gè)方法與DateInterval重寫(xiě)的setSecond方法具有不同的方法簽名(形參不同),所以是兩個(gè)不同的方法,然而,這兩個(gè)方法不應(yīng)該是不同的方法(因?yàn)槭莖verride)??紤]以下的代碼:

DateInterval interval = new DateInterval(...);
Pair<Date, Date> pair = interval;
Date aDate = new Date(...);
pair.setSecond(aDate);

    由以上代碼可知,pair實(shí)際引用的是DateInterval對(duì)象,因此應(yīng)該調(diào)用DateInterval的setSecond方法,這里的問(wèn)題是類型擦除與多態(tài)發(fā)生了沖突。

    我們來(lái)梳理下為什么會(huì)發(fā)生這個(gè)問(wèn)題:pair在之前被聲明為類型Pair<Date, Date>,該類在虛擬機(jī)看來(lái)只有一個(gè)“setSecond(Object)”方法。因此在運(yùn)行時(shí),虛擬機(jī)發(fā)現(xiàn)pair實(shí)際引用的是DateInterval對(duì)象后,會(huì)去調(diào)用DateInterval的“setSecond(Object)",然而DateInterval類中卻只有”setSecond(Date)"方法。

    解決這個(gè)問(wèn)題的方法是由編譯器在DateInterval中生成一個(gè)橋方法:

public void setSecond(Object second) {
  setSecond((Date) second);
}

6. 注意事項(xiàng)

(1)不能用基本類型實(shí)例化類型參數(shù)

    也就是說(shuō),以下語(yǔ)句是非法的:

Pair<int, int> pair = new Pair<int, int>();
 不過(guò)我們可以用相應(yīng)的包裝類型來(lái)代替。

(2)不能拋出也不能捕獲泛型類實(shí)例

    泛型類擴(kuò)展Throwable即為不合法,因此無(wú)法拋出或捕獲泛型類實(shí)例。但在異常聲明中使用類型參數(shù)是合法的:

public static <T extends Throwable> void doWork(T t) throws T {
  try {
    ...
  } catch (Throwable realCause) {
    t.initCause(realCause);
    throw t;
  }
}

(3)參數(shù)化類型的數(shù)組不合法

  在Java中,Object[]數(shù)組可以是任何數(shù)組的父類(因?yàn)槿魏我粋€(gè)數(shù)組都可以向上轉(zhuǎn)型為它在定義時(shí)指定元素類型的父類的數(shù)組)??紤]以下代碼:

String[] strs = new String[10];
Object[] objs = strs;
obj[0] = new Date(...);

  在上述代碼中,我們將數(shù)組元素賦值為滿足父類(Object)類型,但不同于原始類型(Pair)的對(duì)象,在編譯時(shí)能夠通過(guò),而在運(yùn)行時(shí)會(huì)拋出ArrayStoreException異常。

  基于以上原因,假設(shè)Java允許我們通過(guò)以下語(yǔ)句聲明并初始化一個(gè)泛型數(shù)組:

Pair<String, String>[] pairs = new Pair<String, String>[10];
  那么在虛擬機(jī)進(jìn)行類型擦除后,實(shí)際上pairs成為了Pair[]數(shù)組,我們可以將它向上轉(zhuǎn)型為Object[]數(shù)組。這時(shí)我們?nèi)敉渲刑砑覲air<Date, Date>對(duì)象,便能通過(guò)編譯時(shí)檢查和運(yùn)行時(shí)檢查,而我們的本意是只想讓這個(gè)數(shù)組存儲(chǔ)Pair<String, String>對(duì)象,這會(huì)產(chǎn)生難以定位的錯(cuò)誤。因此,Java不允許我們通過(guò)以上的語(yǔ)句形式聲明并初始化一個(gè)泛型數(shù)組。

   可用如下語(yǔ)句聲明并初始化一個(gè)泛型數(shù)組:

Pair<String, String>[] pairs = (Pair<String, String>[]) new Pair[10];

(4)不能實(shí)例化類型變量

    不能以諸如“new T(...)", "new T[...]", "T.class"的形式使用類型變量。Java禁止我們這樣做的原因很簡(jiǎn)單,因?yàn)榇嬖陬愋筒脸?,所以類似?new T(...)"這樣的語(yǔ)句就會(huì)變?yōu)椤眓ew Object(...)", 而這通常不是我們的本意。我們可以用如下語(yǔ)句代替對(duì)“new T[...]"的調(diào)用:

arrays = (T[]) new Object[N];
 

(5)泛型類的靜態(tài)上下文中不能使用類型變量

     注意,這里我們強(qiáng)調(diào)了泛型類。因?yàn)槠胀愔锌梢远x靜態(tài)泛型方法,如上面我們提到的ArrayAlg類中的getMiddle方法。關(guān)于為什么有這樣的規(guī)定,請(qǐng)考慮下面的代碼:

public class People<T> {
  public static T name;
  public static T getName() {
    ...
  }
}

    我們知道,在同一時(shí)刻,內(nèi)存中可能存在不只一個(gè)People<T>類實(shí)例。假設(shè)現(xiàn)在內(nèi)存中存在著一個(gè)People<String>對(duì)象和People<Integer>對(duì)象,而類的靜態(tài)變量與靜態(tài)方法是所有類實(shí)例共享的。那么問(wèn)題來(lái)了,name究竟是String類型還是Integer類型呢?基于這個(gè)原因,Java中不允許在泛型類的靜態(tài)上下文中使用類型變量。

7. 類型通配符

    介紹類型通配符前,首先介紹兩點(diǎn):

(1)假設(shè)Student是People的子類,Pair<Student, Student>卻不是Pair<People, People>的子類,它們之間不存在"is-a"關(guān)系。

(2)Pair<T, T>與它的原始類型Pair之間存在”is-a"關(guān)系,Pair<T, T>在任何情況下都可以轉(zhuǎn)換為Pair類型。

    現(xiàn)在考慮這樣一個(gè)方法:

public static void printName(Pair<People, People> p) {
  People p1 = p.getFirst();
  System.out.println(p1.getName()); //假設(shè)People類定義了getName實(shí)例方法
}

    在以上的方法中,我們想要同時(shí)能夠傳入Pair<Student, Student>和Pair<People, People>類型的參數(shù),然而二者之間并不存在"is-a"關(guān)系。在這種情況下,Java提供給我們這樣一種解決方案:使用Pair<? extends People>作為形參的類型。也就是說(shuō),Pair<Student, Student>和Pair<People, People>都可以看作是Pair<? extends People>的子類。

    形如”<? extends BoundingType>"的代碼叫做通配符的子類型限定。與之對(duì)應(yīng)的還有通配符的超類型限定,格式是這樣的:<? super BoundingType>。

    現(xiàn)在我們考慮下面這段代碼:

Pair<Student> students = new Pair<Student>(student1, student2);
Pair<? extends People> wildchards = students;
wildchards.setFirst(people1); 

    以上代碼的第三行會(huì)報(bào)錯(cuò),因?yàn)閣ildchards是一個(gè)Pair<? extends People>對(duì)象,它的setFirst方法和getFirst方法是這樣的:

void setFirst(? extends People)
? extends People getFirst()

    對(duì)于setFirst方法來(lái)說(shuō),會(huì)使得編譯器不知道形參究竟是什么類型(只知道是People的子類),而我們?cè)噲D傳入一個(gè)People對(duì)象,編譯器無(wú)法判定People和形參類型是否是”is-a"的關(guān)系,所以調(diào)用setFirst方法會(huì)報(bào)錯(cuò)。而調(diào)用wildchards的getFirst方法是合法的,因?yàn)槲覀冎浪鼤?huì)返回一個(gè)People的子類,而People的子類“always is a People”。(總是可以把子類對(duì)象轉(zhuǎn)換為父類對(duì)象)

    而對(duì)于通配符的超類型限定的情況下,調(diào)用getter方法是非法的,而調(diào)用setter方法是合法的。

    除了子類型限定和超類型限定,還有一種通配符叫做無(wú)限定的通配符,它是這樣的:<?>。這個(gè)東西我們什么時(shí)候會(huì)用到呢?考慮一下這個(gè)場(chǎng)景,我們調(diào)用一個(gè)會(huì)返回一個(gè)getPairs方法,這個(gè)方法會(huì)返回一組Pair<T, T>對(duì)象。其中既有Pair<Student, Student>,  還有Pair<Teacher, Teacher>對(duì)象。(Student類和Teacher類不存在繼承關(guān)系)顯然,這種情況下,子類型限定和超類型限定都不能用。這時(shí)我們可以用這樣一條語(yǔ)句搞定它:

Pair<?>[] pairs = getPairs(...);

相關(guān)文章

  • Java中ThreadLocal?導(dǎo)致內(nèi)存?OOM?的原因分析

    Java中ThreadLocal?導(dǎo)致內(nèi)存?OOM?的原因分析

    這篇文章主要介紹了Java中ThreadLocal導(dǎo)致內(nèi)存OOM的原因分析,文章基于Java的相關(guān)內(nèi)容展開(kāi)ThreadLocal導(dǎo)致內(nèi)存OOM的原因分析,需要的小伙v阿布可以參考一下
    2022-05-05
  • 升級(jí)dubbo2.7.4.1版本平滑遷移到注冊(cè)中心nacos

    升級(jí)dubbo2.7.4.1版本平滑遷移到注冊(cè)中心nacos

    這篇文章主要為大家介紹了2.7.4.1的dubbo平滑遷移到注冊(cè)中心nacos的兩種版本升級(jí)方案,以及為什要升級(jí),有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • mybatis-plus 處理大數(shù)據(jù)插入太慢的解決

    mybatis-plus 處理大數(shù)據(jù)插入太慢的解決

    這篇文章主要介紹了mybatis-plus 處理大數(shù)據(jù)插入太慢的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Spring基于Aop實(shí)現(xiàn)事務(wù)管理流程詳細(xì)講解

    Spring基于Aop實(shí)現(xiàn)事務(wù)管理流程詳細(xì)講解

    這篇文章主要介紹了Spring基于Aop實(shí)現(xiàn)事務(wù)管理流程,事務(wù)管理對(duì)于企業(yè)應(yīng)用來(lái)說(shuō)是至關(guān)重要的,即使出現(xiàn)異常情況,它也可以保證數(shù)據(jù)的一致性,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05
  • Spring基礎(chǔ)篇之初識(shí)DI和AOP

    Spring基礎(chǔ)篇之初識(shí)DI和AOP

    這篇文章主要為大家詳細(xì)介紹了Spring基礎(chǔ)篇之初識(shí)DI和AOP,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • 解決SpringBoot在IDEA中熱部署失效問(wèn)題

    解決SpringBoot在IDEA中熱部署失效問(wèn)題

    熱部署是指程序運(yùn)行過(guò)程中實(shí)時(shí)更新或替換其組件的技術(shù),即項(xiàng)目正在啟動(dòng)中,修改了配置文件中某個(gè)值或者添加了某個(gè)方法或者修改了某個(gè)方法參數(shù),本文給大家介紹了解決SpringBoot在IDEA中熱部署失效問(wèn)題,需要的朋友可以參考下
    2024-01-01
  • Java實(shí)現(xiàn)大數(shù)運(yùn)算的實(shí)例代碼

    Java實(shí)現(xiàn)大數(shù)運(yùn)算的實(shí)例代碼

    這篇文章主要介紹了Java實(shí)現(xiàn)大數(shù)運(yùn)算的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Java中File文件操作類的基礎(chǔ)用法

    Java中File文件操作類的基礎(chǔ)用法

    這篇文章主要給大家介紹了關(guān)于Java中File文件操作類基礎(chǔ)用法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用File類具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • java分頁(yè)之假分頁(yè)實(shí)現(xiàn)簡(jiǎn)單的分頁(yè)器

    java分頁(yè)之假分頁(yè)實(shí)現(xiàn)簡(jiǎn)單的分頁(yè)器

    這篇文章主要介紹了java分頁(yè)之假分頁(yè)實(shí)現(xiàn)簡(jiǎn)單的分頁(yè)器的相關(guān)資料,需要的朋友可以參考下
    2016-04-04
  • java?JVM-clinit指令實(shí)現(xiàn)原理面試精講

    java?JVM-clinit指令實(shí)現(xiàn)原理面試精講

    這篇文章主要介紹了java?JVM-clinit指令實(shí)現(xiàn)原理面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10

最新評(píng)論