Kotlin:泛型杂谈(上)

Kotlin泛型抛弃语法上的差异,在一般情况下与Java都是相近的,所以在这里简单介绍一下Kotlin泛型中独有的东西。

  1. 泛型扩展属性:
val <T>  List<T>.penultimate: T
    get() = this[size - 2]   

>>> println(listof(1, 2, 3, 4).penultimate) 

通过上面的例子可以看出,泛型扩展属性的实现相当简单,这里就不做过多的讲解,但是有一点需要注意的是:由于不能在一个类的属性中存储多个不同类型的值,因此普通属性(即类的非扩展属性)不能声明为泛型。

  1. 非空约束:
class A<T>  {
    fun doSomething(value: T) {
        value?.hashCode()
    }
}

value?.hashCode()中我们可以得知,参数value是可空的,所以我们需要对它进行安全调用,也就是说类型T的默认上界为Any?,那如果我们想要保证类型T永远为非空类型怎么办呢?我们可以对上述代码做以下改造:

class A<T : Any> {
    fun doSomething(value: T) {
        value.hashCode()
    }
}

对,很简单,我们只要将类型T的默认上界改为Any即可。

  1. 实例化类型参数

相信大家都知道,在Java中,泛型类实例的类型参数在运行时是不保留的,也就是说对于如List<Integer>List<String>这样的类型声明在运行时得到的都是同样的List,我们无法从List中识别出它里面包含的究竟时Integer还是String,这样的现象我们称之为类型擦除。也正是因为此,我们想要用Java实现下面的功能是不可能的:

class TypeCheck<T> {

    boolean isMe(Object value) {
        return value instanceof T;
    }
}

public class Demo {

    public static void main(String[] args) {
        TypeCheck<String> stringCheck = new TypeCheck<>();
        System.out.println(stringCheck.isMe("HELLO WORLD"));
    }
}

上面是Java的表现,那么在Kotlin又是如何呢?让我们把上面的例子翻译为Kotlin:

class TypeCheck<T> {
    fun isMe(value: Any): Boolean {
        return value is T
    }
}

fun main(args: Array<String>) {
    val stringCheck = TypeCheck<String>()
    println(stringCheck.isMe("HELLO WORLD"))
}

如果我们尝试编译上面的代码,我们将得到以下错误:

Error:(3, 25) Kotlin: Cannot check for instance of erased type: T

通过对比,我们发现不论在Java中还是在Kotlin中,泛型类实例的类型参数在运行时都是不保留的(即发生了类型擦除),那这是否意味着在Kotlin中我们也无法实现这种类型检查的功能呢?答案当然是否定的,要想在Kotlin的运行时中引用泛型参数实际的类型实参,唯一的方式是使用内联函数。首先让我们首先看下面的例子:

inline fun <reified T> isMe(value: Any): Boolean {
    return value is T
} 

fun main(args: Array<String>) {
    println(isMe<String>("HELLO WORLD"))
}

编译运行,我们发现这次成功运行了,并且输出为true。那这究竟是什么鬼呢?在Kotlin:关于内联函数的一些理解中,我们可以得知,编译器会用内联函数的实际代码来替换每一次的函数调用,每次我们调用带实化类型参数(即在内联函数中被reified关键字修饰的类型参数)的函数时,编译器能够获知该次调用中类型参数的具体类型,因此上述main函数实际被编译成以下样子:

fun main(args: Array<String>) {
    println("HELLO WORLD" is String)
}

很简单对不对?让我们对实例化类型参数进行一个简单的总结:

  • 通过上面的描述,我们可以得知实例化类型参数只能作用在内联函数中,而类、属性、非内联函数中的类型参数是不能通过reified进行修饰的。
  • 我们一般会在类型检查、类型转换、获取::class::java中使用实例化类型参数

上面我们从:泛型扩展属性、非空约束、实例化类型参数三个方面简单介绍了一下Kotlin中泛型相对于Java独有的特性,那么除此之外是否还有别的特性呢?且听下回分解。^ _ ^

    原文作者:泪已无痕
    原文地址: https://www.jianshu.com/p/011a3cafc706
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞