Kotlin:泛型杂谈(上)

作者: 泪已无痕 | 来源:发表于2018-06-18 21:53 被阅读53次

    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独有的特性,那么除此之外是否还有别的特性呢?且听下回分解。^ _ ^

    相关文章

      网友评论

        本文标题:Kotlin:泛型杂谈(上)

        本文链接:https://www.haomeiwen.com/subject/znvneftx.html