简述: 这是泛型型变最后一篇文章了,也是泛型介绍的最后一篇文章。顺便再扯点别的,上周去北京参加了JetBrains 2018开发者日,主要是参加Kotlin专场。个人感觉收获还是挺多的,bennyHuo和彦伟老师精彩演讲确实传递很多干货啊,当然还有Hali布道师大佬带来了的Kotlin1.3版本的新特性以及Google中国技术推广负责人钟辉老师带来的Coroutines在Android开发中的应用。所以准备整理如下几篇文章为后续发布:
1、Kotlin中1.3版本新特性都有哪些?
2、Kotlin中的Coroutine(协程)在Android上应用(协程学前班篇)
3、Ktor异步框架初体验(Ktor学前班篇)
4、Kotlin中data class的使用(benny大佬在大会上讲的很清楚了,也很全面。主要讲下个人之前踩过的坑,特别是用于后端开发坑更多)
那么今天这篇文章主要是为了给上篇型变文章两个尾巴以及泛型型变是如何被应用到实际开发中的去。并且我会用上篇博客如何去选择相应型变的方法一步步确定最终我们该使用协变、逆变、还是不变,我会用一个实际例子来说明。这篇文章比较简单主要就以下四点:
1、Kotlin声明点变型与Java中的使用点变型进行对比
2、如何使用Kotlin中的使用点变型
3、Kotlin泛型中的星投影
4、使用泛型型变实现可用于实际开发中的Boolean扩展
一、Kotlin声明点变型与Java中的使用点变型进行对比
1、声明点变型和使用点变型定义区别
首先,解释下什么是声明点变型和使用点变型,声明点变型顾名思义就是在定义声明泛型类的时候指明型变类型(协变、逆变、不变),在Kotlin上表现形式就是在声明泛型类时候在泛型形参前面加in或out修饰。使用点变型就是在每次使用该泛型类的时候都要去明确指出型变关系,如果你对Java中型变熟悉的话,Java就是使用了使用点变型.
2、两者优点对比
声明点变型:
有个明显优点就是只需要在泛型类声明时定义一次型变对应关系就可以了,那么之后不管在任何地方使用它都不用显示指定型变对应关系,而使用点变型就是每处使用的地方都得重复定义一遍特别麻烦(又找到一处Kotlin优于Java的地方)。
使用点变型:
实际上使用点变型也是有使用场景的,可以使用的更加灵活;所以Kotlin并没有完全摒弃这个语法点,下面会专门介绍它的使用场景。
3、使用对比
刚刚说使用点变型特别麻烦,一起来看看到底有多麻烦。这里就是以Java为代表,我们都知道Java中要使用型变,是利用?通配符加(super/extends)来达到目的,例如: Function<? super T, ? extends E>, 其中的? extends E就是对应了协变,而? super T对应的是逆变。这里以Stream API中的flatMap函数源码为例
@FunctionalInterface
public interface Function<T, R> {//声明处就不用指定型变关系...
}
//可以看到使用点变型非常麻烦,定义一个mapper的Function泛型类参数时,还需要指明后面一大串Function<? super T, ? extends Stream<? extends R>><R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
声明点变型到底有多方便,这里就以Kotlin为例,Kotlin使用in, out来实现型变对应规则。这里以Sequences API中的flapMap函数源码为例
public interface Sequence<out T> {//Sequence定义处声明了out协变/** * Returns an [Iterator] that returns the values from the sequence. * * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time. */public operator fun iterator(): Iterator<T>
}
public fun <T, R> Sequence<T>.flatMap(transform: (T) -> Sequence<R>): Sequence<R> {//可以看到由于Sequence声明了协变,所以flatMap函数Sequence中的泛型实参R就不用再次指明型变类型了return FlatteningSequence(this, transform, { it.iterator() })
}
通过以上源码对比,明显看出Kotlin中的声明点变型要比Java中的使用点变型要简单得多吧。但是呢使用点变型并不是一无是处,它在Kotlin中还是有一定的使用场景的。下面即将揭晓
二、如何使用Kotlin中的使用点变型
实际上使用点变型在Kotlin中还是有一定的使用场景,想象一下这样一个实际场景,尽管某个泛型类是不变的,也就是具有可读可写的操作,可是有时候在某个函数中,我们一般仅仅只用到只读或只写操作,这时候利用使用点变型它能使一个不变型的缩小型变范围蜕化成协变或逆变的。是不是突然懵逼了,用源码来说话,你就明白了,一起来看个源码中的例子。
Kotlin中的MutableCollection<E>是不变的,一起来看了下它的定义
public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {//没有in和out修饰,说明是不变override fun iterator(): MutableIterator<E>public fun add(element: E): Booleanpublic fun remove(element: E): Booleanpublic fun addAll(elements: Collection<E>): Booleanpublic fun removeAll(elements: Collection<E>): Booleanpublic fun retainAll(elements: Collection<E>): Booleanpublic fun clear(): Unit
}
然后我们接着看filter和filterTo函数的源码定义
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate)
}
//注意: 这里<T, C : MutableCollection<in T>>, MutableCollection<in T>声明成逆变的了,是不是很奇怪啊,之前明明有说它是不变的啊,怎么这里就声明逆变了
public inline fu