Android開發者快速上手Kotlin(四) 之 泛型、反射、註解和正則

《Android開發者快速上手Kotlin(三) 之 高階函數和SAM轉換》文章繼續。

8 泛型

8.1 泛型的聲明

Kotlin中的泛型基本上跟Java是一個思路的,只是在使用上有一點點區別。如:

fun <T> func1(a: T, b: T): T {       // 單個泛型參數的方法的聲明
    return a
}

fun <T, R> func2(a: T, b: R): R {     // 多個泛型參數的方法的聲明
    return b
}

class A<T>(var t:T) {                   // 泛型類的聲明
    fun a() :T {
        return t
    }
}
// 調用
val a:Int = func1(1,2)
val b:Long = func2(1,2L)

val aObj = A(10)
val c:Int = aObj.a()

8.2 泛型的約束

泛型可以對傳入的T進行相應的約束,如果是多條件約束,可以使用關鍵字where。如:

fun <T : Comparable<T>> maxOf1(a: T, b: T): T {                     // 單約束
    return if (a > b) a else b
}
fun <T> maxOf2(a: T, b: T): T where T : Comparable<T>, T : Number { // 多約束
    return if (a > b) a else b
}

8.3 泛型的型變

泛型的型變就是描述泛型參數本身有繼承關係的時候泛型類型的繼承關係。泛型變分:協變逆變不變(沒有繼承關係)三種。

8.3.1 協變

協變:就是繼承關係保持一致。在泛型類作爲泛型參數類實例的生產者時,一般需要提供的泛型類要跟泛型參數類型保持繼承關係的一致。

協變點:描述在類裏有一函數,它的返回值類型爲泛型參數。

以下示例中,泛型Class A實現的就是協變情況,T前面加out關鍵字,getValue方法返回參數就是協變點。如:

class A<out T>(val number: T) {
    fun getValue(): T {
        return number
    }
}
// 調用
var number: Number = 1
var int: Int = 1

val intObj: A<Int> = A<Int>(1)
val numberObj: A<Number> = intObj      // 因爲T前有out,所以是允許的,number是int的父類,A<Number>當然也應該是A<Int>的父類

int = intObj.getValue()
number = intObj.getValue()		// 想要一個number,返回一個int是合理。意思如:我要買一輛車,然後買了輛寶馬
// int = numberObj.getValue()           // 這句會報錯,因爲是number父型的,不一定是int類型
number = numberObj.getValue()

總結:

  1. 子類兼容父類。
  2. 子類參數的泛型生產者兼容父類參數的泛型生產者。
  3. 存在協變點的類的泛型參數必須聲明爲協變或不變
  4. 當泛型類作爲泛型參數類實例的生產者時用協變

8.3.2 逆變

逆變:就是繼承關係相反。在泛型類作爲泛型參數類實例的消費者時,一般需要提供的泛型類要跟泛型參數類型的繼承關係相反。

逆變點:描述在類裏有一函數,它的參數類型爲泛型參數。

以下示例中,泛型Class A實現的就是逆變情況,T前面加in關鍵字,compare方法接收參數就是逆變點。如:

class A<in T : Number>() {
    fun compare(t:T) {
        // TODO...
    }
}
// 調用
val number:Number = 1
val int:Int = 1

val numberObj: A<Number> = A<Number>()
val intObj:A<Int> = numberObj                     // T前有in,所以是允許的,適用於父類的出現可以代替子類做事

numberObj.compare(number)
numberObj.compare(int)                            // 能比較number的,一定能比較int。意思如:我會開車當然也會開寶馬

// intObj.compare(number)                          // 這句會報錯,因爲能比較int的,不一定能比較number
intObj.compare(int)

總結:

  1. 子類兼容父類。
  2. 父類參數的泛型消費者兼容子類參數的泛型消費者。
  3. 存在逆變點的類的泛型參數必須聲明爲逆變或不變
  4. 當泛型類作爲泛型參數實現的消費者時用逆變

8.4 星投影

星投影是Kotlin中的一個新概念,可用在變量類型聲明的位置用於描述一個未知的類型。通地 * 來表示,它所替換的類型在協變點返回泛型參數上限類型;在逆變點接收泛型參數下限類型。使用如:

class B<out K : Number, out V : Any>(val bKey: K, val bValue: V) {
    fun getKey(): K {
        return bKey
    }
    fun getValue(): V {
        return bValue
    }
}
class C<in K : Number, in V : Any>() {
    fun setKey(cKey: K) {
    }
    fun setValue(cValue: V) {
    }
}
// 調用
val b: B<*, *> = B<Long, String>(1L, "壹") 
val bKey = b.getKey()				// 返回的是K類型的上限,也就是Number
val bValue = b.getValue()			// 返回的是V類型的上限,也就是Any

val c: C<*, *> = C<Long, String>()
// c.setKey(1L)                 	        // 不允許傳入值,因爲參數下限類型是Nothing
// c.setValue("壹")             	        // 不允許傳入值,因爲參數下限類型是Nothing

注意:

在給星投影聲明創建的對象進行類型判斷時,不允許進行對實例創建時類型的判斷。因爲泛型類型只存在於編譯期,在運行時泛型類型是會被擦除的,如:

if (b is B) {                    // 允許進行判斷
}
if (b is B<*,*>) {               // 允許進行判斷
}
if (b is B<Number, Any>) {       // 允許進行判斷,這是運行時行爲
    b as B<Int, String>)         // 允許進行強轉,因爲這是編譯期行爲
}
// if (b is B<Long, String>) {   // 不允許進行判斷,因爲泛型類型只存在於編譯期,在運行時泛型類型是會被擦除
// }

適用範圍:

  1. 適用於作爲類型描述的場景,如:val b<*,*>、if (b is B<*,*>)、HashMap<String, List<*>>()。
  2. 不能直接或間接應用在屬性或函數上,如下面是不允許的:B<Long,*>()、maxOf<*>(1,3)。

8.5 內聯特化

Kotlin中的泛型原理跟Java是一樣的都是類型擦除。即在編譯時擦除類型,運行時無實際類型生成。這種類型擦除的泛型也有人叫作“僞泛型”。類型擦除會導致泛型類型無法當作真實類型來使用,就如以下注釋掉的代碼是編譯不通過:

fun<T> a(t:T) {
//    val t = T()                           // 無法知道該類是否在無參構造函數
//    val ts = Array<T>(3) {TODO()}         // 數組其實並不是真正的泛型,它是需要編譯器確定的類型,只是寫法跟泛型像
//    val tClass = T::class.java            // 因爲編譯期會將類型擦除,所以不能當爲一個真實類型獲得它的類
    val list = ArrayList<T>()               // ArrayList本身在編譯期是會被擦除,所以傳啥都可以
}

在Kotlin中可以使用內聯特化來解決語言上僞泛型所帶來的一些限制。因爲函數是一個內聯函數,所以代碼會在調用處替換過去,一旦替換後類型就是肯定的能確定的。如:

inline fun<reified T> b(t:T) {
//    val t = T()                            // 仍然不可以,因爲還是不知道你的類型是否存在無參構造函數
    val ts = Array<T>(3) {TODO()}          // 內聯特化,允許 
    val tClass = T::class.java             // 內聯特化,允許
    val list = ArrayList<T>()
}

9 反射

Kotlin中使用反射跟Java中使用反射的概念和思想是一樣的。都是允許程序在運行時訪問類、接口、方法、屬性等語法的一類特性。在Kotlin中反射的信息主要是通過類中的Metadata註解獲取的。Metadata註解內會帶着本類、父類、成員等信息。使用上Kotlin依然可以使用Java的那套Type、Class、Field、Method反射對象;但其實Kotlin自身語法也支持了一套新的,它們對應KType、KClass、KProperty、KFunction。兩套在Kotlin中是可以混着使用。如:

class A {
    private var property = 123
    private fun func(index: Int): String {
        return if (index == 1) "Hello"
        else property.toString()
    }
}
// Java版的反射
val jClass: Class<A> = A::class.java
val constructor = jClass.getConstructor()
val obj = constructor.newInstance()

val field = jClass.getDeclaredField("property")
field.isAccessible = true
field.setInt(obj, 999)

val method = jClass.getDeclaredMethod("func", Int::class.java)
method.isAccessible = true
val result = method.invoke(obj, 2)

println(result)             // 輸出結果:999

// Kotlin版的反射
var kClass: KClass<A> = A::class
val kConstructor:KFunction<A>? = kClass.primaryConstructor
val kObj = kConstructor?.call()

val properties= kClass.declaredMemberProperties.filter { it.name == "property" }    // 直接通過filter過濾返回的List只有一個
properties.forEach {
    it.isAccessible = true
    val a = it as KMutableProperty1<A?, Int>
    a.set(kObj, 888)
}

val functions:Collection<KFunction<*>> = kClass.declaredMemberFunctions                // 不過濾,在forEach中才去判斷也是可以的
functions.forEach {
    if ("func".equals(it.name)) {
        it.isAccessible = true
        val result = it.javaMethod?.invoke(kObj, 2)
        println(result)     // 輸出結果:888
    }
}

10 註解

註解是對類、函數、函數參數、屬性等做附加信息說明。Kotlin中使用註解跟Java中使用註解的概念和思想是一樣的。Kotlin中要聲明註解,要使用 annotation 關鍵字放在類的前面:

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class API(val say: String)
// 使用
class A(@API("Hello") var name: String)

上述代碼聲明瞭一個叫“API”的註解類,它的構造方法中定義了一個叫“say”的屬性,它的標註對象是屬性、作用時機是運行時,並將它在Class A的構造方法中使用。

通過反射使用註解:

如果定義的註解的作用時機是運行時,那麼就可以使用反射來對它進行邏輯性處理。如:

val aObj = A("子云心")

val kClass = A::class
val properties = kClass.declaredMemberProperties.filter { it.annotations.isNotEmpty() }
properties.forEach {
    var say = it.annotations.filterIsInstance<API>().first().say
    val name = it as KMutableProperty1<A?, String>
    name.set(aObj, say + name.get(aObj))
}

println(aObj.name)      // 輸出結果:Hello子云心

11 正則

在Kotlin中完全可以照着Java中的正則表達式的寫法使用Pattern類完成正則的匹配,但其實Kotlin自身語法也存在一個Regex的類,使用它可以寫出很Kotlin風格的代碼。兩套在Kotlin中是可以混着使用。如:

// Java版的正則
val source = "Hello, This my phone number:0756-1234567"
val pattern = ".*(\\d{4}-\\d{7}).*"

val matcher = Pattern.compile(pattern).matcher(source)
while (matcher.find()) {
    println(matcher.group())
    println(matcher.group(1))
}

// Kotlin版的正則
val source = "Hello, This my phone number:0756-1234567"
val pattern = """.*(\d{4}-\d{7}).*"""
Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::println)

 

未完,請關注後面文章更新

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章