1. 程式人生 > >kotlin 泛型學習筆記

kotlin 泛型學習筆記

泛型是什麼:
1.泛型是函式定義的時候未指定具體型別,而在使用的時候可以傳遞多種具體型別。
2.泛型是java和kotlin中的概念,在其他類似程式語言中叫做引數多型(parametric polymorphism)或者模板。

注:本文翻譯自英文書《Programing Kotlin》的第八章,並且進行了一些修改。

kotlin的泛型主要包含以下內容:

函式的泛型引數

fun random(one: Any, two:Any, three: Any): Any{//普通函式
    return one;
}

fun <T> randomGeneric(one: T, two: T, three: T): T{//泛型引數函式
return one; } fun <K,V> put4Study(key: K, value: V):Unit{//多個泛型引數函式 Log.i(TAG,"key=$key,value=$value") } fun studyParameterisedFunction(){ Log.i(TAG,"---studyParameterisedFunction start---") val r:Any = random("a","b","c");//缺點:使用變數r的時候需要自己判斷r的具體型別進行轉換,容易出錯並且程式碼不簡潔 Log.i(TAG
,"r=$r") // val r1:String = random("a","b","c");//無法編譯通過 var r2:String = randomGeneric("a","b","c");//型別推斷出是String型別 Log.i(TAG,"r2=$r2") var r3:Int = randomGeneric(1,2,3);//型別推斷出是Int型別 Log.i(TAG,"r3=$r3") var r4:Any = randomGeneric("a",1,true);//向上型別推斷出是Any型別(Any是所有型別的基類 ) Log.i(TAG
,"r4=$r4") put4Study(1,"a");//多個型別引數示例 Log.i(TAG,"---studyParameterisedFunction end---") }

列印日誌如下:

06-06 14:38:49.609 23448-23448/? I/TAG: ---studyParameterisedFunction start---
06-06 14:38:49.609 23448-23448/? I/TAG: r=a
06-06 14:38:49.609 23448-23448/? I/TAG: r2=a
06-06 14:38:49.609 23448-23448/? I/TAG: r3=1
06-06 14:38:49.609 23448-23448/? I/TAG: r4=a
06-06 14:38:49.609 23448-23448/? I/TAG: key=1,value=a
06-06 14:38:49.609 23448-23448/? I/TAG: ---studyParameterisedFunction end---

類的泛型引數

class Sequence<T>{//一個型別引數的類

}

class Dictionary<K,V>{//兩個型別引數的類

}

fun studyParameterisedType() {
    Log.i(TAG, "---studyParameterisedType start---")

    val seqBoolean = Sequence<Boolean>()//例項化
    val seqString = Sequence<String>()//例項化

    val dict = Dictionary<Int,String>();//例項化

    Log.i(TAG, "---studyParameterisedType end---")
}

有界性

泛型有一定侷限性。例如一個函式是取出兩個值中的最小值。但是兩個泛型引數無法完成比較(因為可能是Any的任何型別)。解決方案就是限制泛型的型別,即泛型的有界性。

上邊界

定義了兩個函式:min是普通的泛型函式,minUpperBounds是存在上界的泛型函式。區別主要是

<T : Comparable<T>>

函式呼叫的實際引數必須實現介面Comparable,並且可以直接使用Comparable的方法compareTo,具體如下:

/**
 * 未使用上界的泛型話
 * 無法比較first和second的大小
 */
fun <T> min(first: T,second: T):T{
    return first;//無法比較first和second的大小
}

/**
 * 存在上界的泛型
 * 可以接收所有Comparable的子類
 */
fun <T : Comparable<T>> minUpperBounds(first: T,second: T):T{
    val result = first.compareTo(second);
    if(result <=0){
        return first;
    }else{
        return second;
    }
}

fun studyUpperBounds() {
    Log.i(TAG, "---studyUpperBounds start---")

    val a:Int = minUpperBounds(3,6);//Int實現了Comparable介面
    Log.i(TAG, "minUpperBounds(3,6)=$a")

    val b:String = minUpperBounds("b","c");//String實現了Comparable介面
    Log.i(TAG, "minUpperBounds(\"b\",\"c\")=$b")

    val c: Any = min("c",1);//普通泛型函式
//    val d: Any = minUpperBounds("d",1);//Any未實現Comparable介面,編譯錯誤,引數必須是同類型的


    Log.i(TAG, "---studyUpperBounds end---")
}

列印日誌如下:

06-06 18:55:25.235 9406-9406/study.caiy.com.kotlinstudy I/TAG: ---studyUpperBounds start---
06-06 18:55:25.235 9406-9406/study.caiy.com.kotlinstudy I/TAG: minUpperBounds(3,6)=3
06-06 18:55:25.235 9406-9406/study.caiy.com.kotlinstudy I/TAG: minUpperBounds("b","c")=b
06-06 18:55:25.235 9406-9406/study.caiy.com.kotlinstudy I/TAG: ---studyUpperBounds end---

如上:minUpperBounds很好的實現了取兩個數中最小值的功能

多個邊界

1.函式的 多個上界的泛型引數

/**
 * 函式 泛型引數 多個上界
 * 關鍵字where
 */
fun<T> minSerializable(first: T, second: T) : T
where T : Comparable<T>, T : Serializable{
    val result = first.compareTo(second);
    if(result <=0){
        return first;
    }else{
        return second;
    }
}

/**
 * 只實現了Comparable<Year>介面
 */
class Year(val value: Int) : Comparable<Year>{
    override fun compareTo(other: Year): Int {
        return this.value.compareTo(other.value)
    }
}

/**
 * 實現了Comparable<SerializableYear>和Serializable兩個介面
 */
class SerializableYear(val value:Int): Comparable<SerializableYear>,Serializable{
    override fun compareTo(other: SerializableYear): Int {
        return this.value.compareTo(other.value)
    }
}

呼叫端程式碼:

    //函式的 有多個上界的泛型引數
//    minSerializable(Year(2017),Year(2018))//編譯錯誤,Year不是Serializable的子類
    val minYear = minSerializable(SerializableYear(2017),SerializableYear(2018))
    Log.i(TAG,"minSerializable(SerializableYear(2017),SerializableYear(2018))結果是${minYear.value}")

列印日誌如下:

minSerializable(SerializableYear(2017),SerializableYear(2018))結果是2017

2.類的 多個上界的泛型引數

/**
 * 類 泛型引數 多個上界
 * 關鍵字 where T:Comparable<T>,T:Serializable
 */
class MultiBoundClass<T>(val value:T) where T:Comparable<T>,T:Serializable{

    fun customCompareTo(other:MultiBoundClass<T>):Int{
        return value.compareTo(other.value);
    }

}

呼叫端程式碼:

//    MultiBoundClass(Year(2017));//編譯錯誤,Year不是Serializable的子類
    val multiBound2017 = MultiBoundClass(SerializableYear(2017))
    val multiBound2018 = MultiBoundClass(SerializableYear(2018))
    val result = multiBound2017.customCompareTo(multiBound2018)
    Log.i(TAG,"multiBound2017.customCompareTo(multiBound2018)結果是$result")

列印日誌如下:

multiBound2017.customCompareTo(multiBound2018)結果是-1

型變

顯而易見,Apple是Fruit的子類。但是Crate是否是Crate的子類,或者父類,還是兩者沒有任何關係。這就取決於泛型的型別-型變。

不變性

程式碼如下:

open class Fruit{

}

class Apple:Fruit(){

}

class Orange:Fruit(){

}

class Crate<T>(val elements:MutableList<T>){
    fun add(t: T){
        elements.add(t)
    }

    fun last(): T{
        return elements.last()
    }

}

fun foo(crate: Crate<Fruit>){
    crate.add(Apple())
}

呼叫端程式碼如下

    var oranges = Crate(mutableListOf(Orange(), Orange()))
//    foo(oranges)//編譯錯誤,Crate<Orange>不是Crate<Fruit>的子型別
    var fruits = Crate(mutableListOf(Fruit(), Fruit()))

可以看到oranges無法作為函式foo的引數。小結如下:

預設情況下:Crate<Orange> 和 Crate<Fruit>是不同的型別,沒有任何關係

協變

關鍵字out 生產者 只能作為函式的返回值,不能作為函式的入參

程式碼如下:

/**
 * 協變
 * 關鍵詞out
 */
class CovariantCrate<out T>(val elements: List<T>){
    //編譯錯誤 T宣告成了out,可以理解成生成者。對於我們只能作為返回值,不能作為入參
//    fun add(t: T){
//        elements.add(t)
//    }

    fun last(): T{
        return elements.last()
    }
}

fun fooCovariant(crate: CovariantCrate<Fruit>){
    //do nothing
}

呼叫端程式碼如下:

val covariantOranges:CovariantCrate<Orange> = CovariantCrate(listOf(Orange(), Orange()))
    fooCovariant(covariantOranges);//因為關鍵字out,CovariantCrate<Orange>被當做了CovariantCrate<Fruit>的子類

kotlin中很多不可變的集合型別都使用到了協變,例如Sets, Lists等

函式返回型變

自我感覺和泛型關係不大
程式碼如下:

open class Animal{

}

class Sheep : Animal(){
    fun onlyInSheep(){

    }
}

open class Farm {
    open fun get(): Animal{
        return Animal()
    }
}
class SheepFarm() : Farm() {
    override fun get(): Sheep{//函式返回值 型變 Sheep是Animal的子類
        return Sheep()
    }
}

呼叫端程式碼如下:

    val farm: Farm = SheepFarm()
    val animal1 = farm.get()
//    animal1.onlyInSheep();//無法編譯通過,因為farm.get()得到的是一個Animal型別,沒有onlyInSheep方法

    val sheepFarm = SheepFarm()
    val animal2 = sheepFarm.get()
    animal2.onlyInSheep();

逆變性

關鍵字in 消費者 只能用作函式的入參,不能作為函式的返回值

未使用逆變性,程式碼如下:

interface Listener<T> {
    fun onNext(t: T): Unit
}
class EventStream<T>(val listener: Listener<T>) {
    fun start(t: T): Unit{
        listener.onNext(t);
    }
    fun stop(): Unit{

    }
}

未使用逆變性,呼叫端程式碼如下:

//非型變示例
    val stringListener = object : Listener<String> {
        override fun onNext(t: String){
            Log.i(TAG,t)
        }
    }
    val stringStream = EventStream<String>(stringListener)//stringListener是Listener<String>型別,Listener<T>的子類一
    stringStream.start("a")
    val dateListener = object : Listener<Date> {
        override fun onNext(t: Date){
            Log.i(TAG,t.toString())
        }
    }
    val dateStream = EventStream<Date>(dateListener)//dateListener是Listener<Date>型別,Listener<T>的子類二
    dateStream.start(Date())

    //可以看到這裡定義了兩個listener:stringListener、dateListener

未使用逆變性,列印日誌如下:

06-07 21:15:32.805 10692-10692/study.caiy.com.kotlinstudy I/TAG: a
06-07 21:15:32.805 10692-10692/study.caiy.com.kotlinstudy I/TAG: Wed Jun 07 21:15:32 GMT+08:00 2017

使用逆變性,程式碼如下:

/**
 * 逆型變 關鍵詞 in 消費者 只能作為函式入參,不能作為返回值
 */
interface Listener4Contra<in T> {
    fun onNext(t: T): Unit
}
class EventStream4Contra<in T>(val listener: Listener4Contra<T>) {
    fun start(t: T): Unit{
        listener.onNext(t);
    }
    fun stop(): Unit{

    }
}

使用逆變性,呼叫端程式碼如下:

/逆型變(關鍵字in)可以只用一個listener實現功能

    val loggingListener = object : Listener4Contra<Any> {
        override fun onNext(t: Any){
            Log.i(TAG,t.toString())
        }
    }
    EventStream4Contra<String>(loggingListener).start("b")//String是Any的子類
    EventStream4Contra<Date>(loggingListener).start(Date())//Date是Any的子類

使用逆變性,列印日誌如下:

06-07 21:15:32.806 10692-10692/study.caiy.com.kotlinstudy I/TAG: b
06-07 21:15:32.806 10692-10692/study.caiy.com.kotlinstudy I/TAG: Wed Jun 07 21:15:32 GMT+08:00 2017

型變小結


如上圖所示:
如果Orange是Fruit的子類,那麼:
不變性(預設情況): Crate<Orange>Crate<Fruit>沒有任何關係
協變性(out): Crate<Orange>Crate<Fruit>的子型別
逆變性(in):Crate<Fruit>Crate<Orange>的子型別

Nothing型別

關鍵字Nothing的定義如下:Nothing是一個類,並且沒有例項

/**
 * Nothing has no instances. You can use Nothing to represent "a value that never exists": for example,
 * if a function has the return type of Nothing, it means that it never returns (always throws an exception).
 */
public class Nothing private constructor()

程式碼如下:

class Box<out T>(){
}

interface Marshaller<out T> {
    fun marshall(json: String): T?//函式返回引數型別為T?
}

object StringMarshaller : Marshaller<String>{
    override fun marshall(json: String): String? {//函式返回引數型別為String?
        return json;
    }
}

object NoopMarshaller : Marshaller<Nothing> {
    override fun marshall(json: String):Nothing? { //函式返回引數Nothing?
        //Nothing不能例項化,只能返回null
        return null
    }
}

呼叫端程式碼如下

/**
 * 關鍵字Nothing Nothing是所有型別的子類
 *
 *
 */
fun studyNothing(){
    Log.i(TAG, "---studyNothing start---")

    //Nothing泛型例項化示例
    Box<String>()//正常例項化
    Box<Nothing>()//Nothing例項化

    //Nothing 函式返回示例
    val a = StringMarshaller.marshall("a")
    Log.i(TAG,"a=$a")
    val b = NoopMarshaller.marshall("b")
    Log.i(TAG,"b=$b")

    //Nothing 空集合示例
    var dateList:List<Date> = emptyList()
    var stringList:List<String> = emptyList()
    var dateSet:Set<Date> = emptySet()
    var stringSet:Set<String> = emptySet()

    Log.i(TAG, "---studyNothing end---")
}

列印日誌如下:

06-08 10:29:38.110 15206-15206/study.caiy.com.kotlinstudy I/TAG: ---studyNothing start---
06-08 10:29:38.110 15206-15206/study.caiy.com.kotlinstudy I/TAG: a=a
06-08 10:29:38.110 15206-15206/study.caiy.com.kotlinstudy I/TAG: b=null
06-08 10:29:38.110 15206-15206/study.caiy.com.kotlinstudy I/TAG: ---studyNothing end---

型別投影

1.out 型別投影示例
程式碼如下:

//第三方提供
class Crate<T>(val elements:MutableList<T>){
    fun add(t: T){
        elements.add(t)
    }

    fun last(): T{
        return elements.last()
    }

}

fun getWithCrate(crate: Crate<Fruit>){
    //do nothing
}

fun getWithCrateWhenProjection(crate: Crate<out Fruit>){
//    crate.add(Fruit())//編譯不通過,因為是out型別,add方法被禁止訪問
    Log.i(TAG,"crate.last()結果是:" + crate.last())
}

呼叫端程式碼如下:

    val oranges = Crate(mutableListOf(Orange(),Orange()))
//    getWithCrate(oranges)//編譯錯誤,Crate<Orange>不能轉換為Crate<Fruit>
    //假定Crate是第三方提供的類,不可修改。
    //方法中加入關鍵字out:Crate<Orange>型別投影成了Crate<Fruit>
    getWithCrateWhenProjection(oranges)

日誌列印如下:

06-08 13:48:54.850 5768-5768/study.caiy.com.kotlinstudy I/TAG: crate.last()結果是:study.caiy.com.kotlinstudy.Orange@69731f7

2.in 型別投影示例
程式碼如下:

//第三方提供
interface Listener<T> {
    fun onNext(t: T): Unit
}

class EventStream<T>(val listener: Listener<T>) {
    fun start(t: T): Unit{
        listener.onNext(t);
    }
    fun stop(): Unit{

    }
}

class EventStream4Projection<T>(val listener: Listener<in T>) {
    fun start(t: T): Unit{
        listener.onNext(t);
    }
    fun stop(): Unit{

    }
}

呼叫端程式碼如下:

    val loggingListener = object :Listener<Any> {
        override fun onNext(t: Any){
            Log.i(TAG,t.toString())
        }
    }
//    EventStream<String>(loggingListener)//無法編譯通過,需要Listener<String>,實際傳遞Listener<Any>
    //假定Listener是第三方提供的介面,無法修改
    //loggingListener一個listener適用於兩個
    EventStream4Projection<String>(loggingListener).start("b")//方法中加入關鍵字in : Listener<Any>型別投影成了Listener<String>
    EventStream4Projection<Date>(loggingListener).start(Date())//方法中加入關鍵字in : Listener<Any>型別投影成了Listener<Date>

日誌列印如下:

06-08 13:48:54.850 5768-5768/study.caiy.com.kotlinstudy I/TAG: b
06-08 13:48:54.850 5768-5768/study.caiy.com.kotlinstudy I/TAG: Thu Jun 08 13:48:54 GMT+08:00 2017

型別擦除

1.函式 型別擦除

fun printInts(list: Set<Int>): Unit {
    for (int in list) Log.i(TAG,int.toString())
}
fun printStrings(list: Set<String>): Unit {
    for (string in list) Log.i(TAG,string)
}

執行命令 javap -c GenericKt (在GenericKt.class檔案所在路徑下執行)
可以看到生成的位元組碼檔案如下:

public static final void printInts(java.util.Set<java.lang.Integer>);//方法printInts
    Code:
       0: aload_0
       1: ldc_w         #404                // String list
       4: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       7: aload_0
       8: invokeinterface #410,  1          // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
      13: astore_2
      14: aload_2
      15: invokeinterface #416,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
      20: ifeq          50
      23: aload_2
      24: invokeinterface #419,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      29: checkcast     #117                // class java/lang/Number
      32: invokevirtual #121                // Method java/lang/Number.intValue:()I
      35: istore_1
      36: invokestatic  #29                 // Method study/caiy/com/kotlinstudy/MainActivityKt.getTAG:()Ljava/lang/String;
      39: iload_1
      40: invokestatic  #422                // Method java/lang/String.valueOf:(I)Ljava/lang/String;
      43: invokestatic  #55                 // Method android/util/Log.i:(Ljava/lang/String;Ljava/lang/String;)I
      46: pop
      47: goto          14
      50: return

  public static final void printStrings(java.util.Set<java.lang.String>);//方法printStrings
    Code:
       0: aload_0
       1: ldc_w         #404                // String list
       4: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       7: aload_0
       8: invokeinterface #410,  1          // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
      13: astore_2
      14: aload_2
      15: invokeinterface #416,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
      20: ifeq          44
      23: aload_2
      24: invokeinterface #419,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      29: checkcast     #107                // class java/lang/String
      32: astore_1
      33: invokestatic  #29                 // Method study/caiy/com/kotlinstudy/MainActivityKt.getTAG:()Ljava/lang/String;
      36: aload_1
      37: invokestatic  #55                 // Method android/util/Log.i:(Ljava/lang/String;Ljava/lang/String;)I
      40: pop
      41: goto          14
      44: return

可以看到關鍵程式碼執行了型別轉換,如下:

29: checkcast     #117                // class java/lang/Number
29: checkcast     #107                // class java/lang/String

生成的位元組碼檔案可以等價於下面的程式碼

fun printInts(list: Set<Any>): Unit {
for (obj in list) {
println(obj as Int)
}
}
fun printStrings(list: Set<Any>): Unit {
for (obj in list) {
println(obj as String)
}
}

可以理解成泛型Set《Int》擦除成了Set《Any》,泛型Set《String》擦除成了Set《Any》
2.函式宣告 型別擦除

fun <T : Comparable<T>>max(list: List<T>): T {
    var max = list.first()
    for (t in list) {
        if (t >max)
            max = t
    }
    return max
}

可以看到生成的位元組碼檔案如下:

public static final <T extends java.lang.Comparable<? super T>> T max(java.util.List<? extends T>);
    Code:
       0: aload_0
       1: ldc_w         #404                // String list
       4: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       7: aload_0
       8: invokestatic  #431                // Method kotlin/collections/CollectionsKt.first:(Ljava/util/List;)Ljava/lang/Object;
      11: checkcast     #168                // class java/lang/Comparable
      14: astore_1
      15: aload_0
      16: invokeinterface #434,  1          // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      21: astore_3
      22: aload_3
      23: invokeinterface #416,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
      28: ifeq          57
      31: aload_3
      32: invokeinterface #419,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      37: checkcast     #168                // class java/lang/Comparable
      40: astore_2
      41: aload_2
      42: aload_1
      43: invokeinterface #172,  2          // InterfaceMethod java/lang/Comparable.compareTo:(Ljava/lang/Object;)I
      48: iconst_0
      49: if_icmple     54
      52: aload_2
      53: astore_1
      54: goto          22
      57: aload_1
      58: areturn

可以看到關鍵程式碼執行了型別轉換,如下:

37: checkcast     #168                // class java/lang/Comparable

可以理解成:型別擦除成了Comparable

防止型別擦除可以使用@JvmName註解,或者使用下面的型別具化

型別具化

關鍵字reified,需要和行內函數配合使用
程式碼如下:

inline fun <T>runtimeTypeNormal(): Unit {
//    println("My type parameter is " + T::class.qualifiedName)//編譯錯誤,無法呼叫::class
}

/**
 *
 * 執行時獲取T的實際型別
 * reified關鍵字,必須和inline配合使用
 */
inline fun <reified T>runtimeType4Refication(): Unit {
    println("My type parameter is " + T::class.qualifiedName)
}

/**
 * 執行時型別檢查
 */
inline fun <reified T>List<Any>.collect(): List<T> {
    return this.filter { it is T }.map { it as T }
}

/**
 * 執行時型別檢查 示例2
 */
inline fun <reified T>printT(any: Any): Unit {
    if (any is T) {
        Log.i(TAG,"I am a T: $any")
    }else{
        Log.i(TAG,"not match")
    }
}

呼叫端程式碼如下:

/**
 * 型別具化
 */
fun studyTypeReification(){
    Log.i(TAG, "---studyTypeReification start---")

    //執行時獲取T的實際型別
    runtimeType4Refication<String>();
    runtimeType4Refication<Int>();

    //執行時型別檢查
    val list = listOf("green", false, 100, "blue")
    val strings = list.collect<String>()
    Log.i(TAG,"strings=$strings")

    //位元組碼示例
    printT<String>("a");
    printT<String>(1);

    Log.i(TAG, "---studyTypeReification end---")
}

列印日誌如下:

06-08 16:34:04.665 13999-13999/study.caiy.com.kotlinstudy I/TAG: ---studyTypeReification start---
06-08 16:34:04.665 13999-13999/study.caiy.com.kotlinstudy I/System.out: My type parameter is kotlin.String
06-08 16:34:04.665 13999-13999/study.caiy.com.kotlinstudy I/System.out: My type parameter is kotlin.Int
06-08 16:34:04.666 13999-13999/study.caiy.com.kotlinstudy I/TAG: strings=[green, blue]
06-08 16:34:04.666 13999-13999/study.caiy.com.kotlinstudy I/TAG: I am a T: a
06-08 16:34:04.666 13999-13999/study.caiy.com.kotlinstudy I/TAG: not match
06-08 16:34:04.666 13999-13999/study.caiy.com.kotlinstudy I/TAG: ---studyTypeReification end---

printT函式生成的位元組碼檔案中關鍵部分如下:

4: instanceof    #110                // class java/lang/String

可以看到使用關鍵字reified,型別在執行期保留了。

巢狀泛型邊界

注:英文是Recursive type bounds,即翻譯成遞迴泛型邊界
關鍵程式碼部分為:<E : Account4<E>>

下面的程式碼示例主要以四種方式分別實現了一個功能:賬號排序(其中賬號有不同的型別,只有相同的型別直接可以排序)。可以看到巢狀泛型約束的方式(第四種方式)實現了效果,且程式碼比較優雅。

程式碼如下

interface Account {
    val balance: BigDecimal
}
data class SavingsAccount(override val balance: BigDecimal,val interestRate: BigDecimal) : Account,Comparable<SavingsAccount> {
    override fun compareTo(other: SavingsAccount): Int =