Kotlin進階知識(十)——運行時的泛型:擦除和實化類型參數

引言:JVM上的泛型通常是經過類型擦除實現的,就是說泛型類實例的類型實參在運行時是不保留的java

1、運行時的泛型:類型檢查和轉換

和Java同樣,Kotlin的泛型在運行時也被擦除了。這意味着泛型實例不會攜帶用於建立它的類型實參的信息。markdown

檢查一個值是否在列表,而不是set或者其餘對象的方式:可使用特殊的星號投影語法來作這種檢查:函數

if (value is List<*>) { ... }
複製代碼

實際上,泛型類型擁有的每一個類型形參都須要一個***[擁有未知類型實參的泛型類型**(或者類比於Java的List<?>)]。ui

  • 對泛型類型作泛型轉換
fun printSum(c: Collection<*>) {
    // 這裏會有警告。Unchecked cast: List<*> to List<Int>
    val intList = c as? List<Int>
        ?: throw IllegalArgumentException("List is expected")

    println(intList.sum())
}

fun printSumTest() {
    // 一切都符合預期要求
    printSum(listOf(1, 2, 3))

    // Set不是列表,因此拋出了異常
    printSum(setOf(1, 2, 3))

    // 類型轉換成功,但後面拋出了另外的異常
    printSum(listOf("a", "b", "c"))
}

// 輸出結果
6

java.lang.IllegalArgumentException: List is expected

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
複製代碼

由於編譯器沒有辦法判斷實參listOf("a", "b", "c")是否是一個List,於是類型轉換會成功,可是iniList.sum()調用時把StringNumber用的嘗試會致使運行時的ClassCastExceptionthis

注意:Kotlin編譯器是足夠智能的,在編譯期它已經知道相應的類型信息時,is檢查是容許的spa

  • 對已知類型實參作類型轉換
fun printSum(c: Collection<Int>) {
    // 此次檢查是合法的
    if (c is List<Int>) {
        println(c.sum())
    }
}

>>> printSum(listOf(1, 2, 3))
6
複製代碼

上述例子中,c是否擁有類型List<Int>的檢查是可行的,由於在編譯器就肯定了集合(無論它是列表仍是其餘類型的集合)包含的是整型數字code

2、聲明帶實化類型參數的函數

本小節展現inline函數的另外一種場景:它們的類型參數能夠被實化,使用到的關鍵字——reifiedorm

// 如今代碼能夠編譯
fun <T> isA(value: Any) = value is T
Error: Cannot check for instance of erased type: T

// 如今代碼能夠編譯
inline fun <reified T> isA(value: Any) = value is T

fun isATest() {
    println(isA<String>("abc"))

    println(isA<String>(123))
}

// 輸出結果
true
false
複製代碼
  • filterIsInstance的簡化實現
// 「reified」聲明瞭類型參數不會再運行時被擦除
public inline fun <reified T> 
          Iterable<*>.filterIsInstance(): List<T> {
    val destination = mutableListOf<T>()
    for (element in this) 
         // 能夠檢查元素好似不是指定爲類型實參的類的實例
         if (element is T) 
              destination.add(element)
    return destination
}
複製代碼

爲何實化只對內聯函數有效 編譯器把實現內聯函數的字節碼插入每一次調用發生的地方。由於生成的字節碼引用了具體類,而不是類型參數,它不會被運行時發生的類型參數擦除影響。對象

注意:reified類型參數的inline函數 不能Java代碼中調用。普通的內聯函數能夠像常規函數那樣在Java中調用——它們能夠被調用而不能被內聯。element

3、實化類型參數的限制

具體來講,能夠按下面的方式**使用實化類型參數**:

  • 用在類型檢查和類型轉換中(is、!is、as、as?
  • 使用Kotlin反射API::class
  • 獲取相應的**java.lang.Class**(::class.java
  • 做爲調用其餘函數的類型實參

**不能**作下面這些事情:

  • 建立指定爲類型參數的類的實例
  • 調用類型參數的伴生對象的方法
  • 調用帶實化類型參數的時候使用非實化類型形參爲類型實參
  • 類、屬性或者非內聯類型參數標記成**reified**
相關文章
相關標籤/搜索