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 =