天天看點

【泛型】T extends Comparable<? super T>

背景

    看跳表的實作代碼1時看到T extends Comparable<? super T>,不太了解其含義。

了解

    參考知乎2并自己測試後比較了解了。

    java中向上轉型是轉為父類,向下轉型是轉為子類。extends确定了類型的上限,super确定了類型的下限。

    T extends Comparable<? super T>的作用是什麼呢?知乎這篇通過Demo<GregorianCalendar>的例子講的很清楚了:

  • Demo<T extends Comparable<? super T>>為類型Demo的泛型聲明時,其泛型可以接收GregorianCalendar
  • Demo<T extends Comparable<T>>為類型Demo的泛型聲明時,則不可以接收GregorianCalendar作為泛型。原因是GregorianCalendar并未直接實作Comparable接口,其父類Calendar實作了Comparable<Calendar>接口,在其equals方法中通過調用super.compareTo(xx)方法來使用父類的compareTo。

    結論,通過将泛型類型聲明為T extends Comparable<? super T>,可以接收真正實作了接口的父類,以及繼承了父類的子類。因為子類可以自動向上轉型。

額外收獲

    做實驗的兩個類Animal、Cat是之前用于測試靜态方法和執行個體方法會使用父類還是子類時建立的,隻用再建立個Demo泛型類即可。

    之前的多态實驗,有如下結論:

  • 子類執行個體指派給聲明為父類的變量時,調用靜态方法調用的是父類的,調用執行個體方法調用的是子類的。
  • 将上面的變量強轉為子類型并調用靜态方法,調用的是子類的。

    突發其想,如果讓子類也直接實作接口會怎樣呢?

    實驗一:

  1. Animal implements Comparable<Animal>
  2. Cat extends Animal implements Comparable<Cat>報錯,Comparable的泛型不同,不能被繼承。

    實驗二:

  1. Animal implements Comparable<? super Animal>報no wildcard expected。

    實驗一說明了子類實作父類已實作的帶泛型的接口時,不能改變泛型類型。實驗二說明了被實作的接口的泛型不能有通配符。這兩個實驗所說明的限制與Demo<T extends Comparable<? super T>>相配合,使得若T要使用compareTo,使用的就是其父類的。

    實驗三:

  1. Animal implements Comparable<Animal>
  2. Cat implements Comparable<Animal>,重寫的compareTo方法将入參強轉為Cat再處理。
  3. Demo<T extends Comparable<? super T>>,有一個TreeSet屬性。
  4. main方法中建立一個Animal執行個體并add到demo的TreeSet。(demo是Demo<Animal>的執行個體)
  5. main方法中建立一個Cat執行個體并add到demo的TreeSet。
  6. 列印demo的TreeSet

    結果報類型轉換錯誤。但若注釋掉4或5兩者中的一個,又運作正常。跟蹤異常棧對應的源碼,可了解到TreeSet底層預設使用的TreeMap,在存儲一個新值時,會拿新值.compareTo(rootKey)。由之前列出的執行個體方法調用的是真正的類型的,這裡調用的就是Cat的,由于我重寫的compareTo方法會先進行強轉,但由于rootKey是之前存儲的Animal類型,無法強轉為Cat類型,是以報錯。

    确有必要重寫時,避免在子類覆寫的方法裡進行父類轉子類的操作,可能會轉換報錯。有必要重寫的原因,可能是要使用覆寫的屬性,不重寫則父類裡才有該方法,父類方法裡使用的是父類自己定義的未覆寫的屬性,雖然直接通過子類.屬性名的方式能得到覆寫後的屬性,但父類方法使用this是指向父類的,導緻拿到的是父類本身定義的屬性。代替重寫方法的方案有,采用comparator對屬性進行比較,屬性可被子類重寫。最簡便的,不覆寫父類屬性,直接需要時設定其值,再使用comparator對屬性進行比較。

    TreeSet、NavigableMap、TreeMap這些java api還要有時間去讀源碼,資料結構還要去讀各種經典書籍以了解紅黑樹、B+樹等。

    實驗四:

    受我的文章【泛型】自限定的類型啟發。

  1. public class Animal<T extends Animal> implements Comparable
  2. public class Cat extends Animal<Cat>并重寫Animal的age域,Animal和Cat均設定了@Getter、@Setter、@AllArgsConstructor、@NoArgsConstructor
  3. Animal的compareTo方法體為this.age-t.age時
  4. main方法列印animal.compareTo(cat)

        列印效果為cat的age取的父類值。因為animal協變的泛型為Animal,cat自動向上轉型後取得的age為父類的。可參考Java子類父類屬性的覆寫。将步驟3改為this.age-t.getAge()則取的是子類的,原因是執行個體方法與變量類型無關,與實際類型有關。将步驟4改為cat.compareTo(animal)報錯,因為animal轉不了cat,強轉也不行。是以協變的方法所屬類與其方法入參類型應該針對同類型去用,不應多種類型混用。

  1. https://www.jianshu.com/p/04c79b131e3e

    跳躍表原理及java實作跳躍表(原理講的并不清楚,但跳表與其它資料結構相比的優點講的很好,具體原理請參考百度百科或知乎"跳表") ↩︎

  2. https://www.zhihu.com/question/25548135 ↩︎