Kotlin Reference (十一) 泛型、数组型变、泛型型变、泛型约束,及与Java泛型对比


KotLin 相关文档

官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android


前言

本文建于17年初学kotlin时。当时也看过很多文章,写过例子,包括之前多年也在使用java泛型;然后直到现在19年底了,在应用java、kotlin泛型时还是有些混乱。
可能多数人和我一样,用的最熟悉的就是 类似
List<String> list = new ArrayList<String>;
<T> T update(T t);
<T> T find(List<? extends T> list, int index) { return list.get(index); }
class Clz<T> { }
class Clz<T extends OtherClz> { }
其它
<? super T><?>,貌似印象中没有使用实际场景使用过。

写例子时,发现,如 编译没问题,运行报错了;感觉用的没问题,编译报错了。

java 和 kotlin 的泛型型变原理是一样的吗?关键字都对得上吗?关键字能出现的地方是一样的吗?
这要是面试问题,我估计我已经挂了 (-_-。)

晚上两点多了,先休息了,后面慢慢修改。 updated on 2019-11-18


Java 泛型的简单示例

class Box<T> {
	T t;
	set(T t) {this.t = t;}
	T get(String key) { return t }
}

Box 关联 T 类型,实际就是任意类型,或者说是任意的 Object 及其子类型。


Kotlin 泛型的简单示例

class Box<T>(t: T) { //构造函数有T类型的参数
	var value = t
}

定义一个使用泛型参数的实例:

val box: Box<Int> = Box<Int>(1)

如果,省略泛型参数,类型会自动推测:

val box = Box(1) //T 是 Int 类型

什么时候用泛型

一个类、接口、函数、参数、返回值在要指明所关联的类型为某一特定类型或者就是任意类型,或者是 其子类或父类时,可以使用泛型。所以,泛型形参出现的位置,不是固定的。
当要写个sdk、工具类啥的,在调用(特别是其他开发人员来调用)的时候有类型的限定,以免出现错误的使用,或者在编译期直接就报错,而非运行时或产品上了线,才发现错误。


型变

型变特性,分 变和不变;变又分 协变与逆变

简单的描述 协变与逆变,它们有一个共同的前提,即出现在"有继承或实现"关系的一组类型中。

协变:父类出现的地方,可以用子类代替(符合面向对象的基本原则,里氏替换原则,或者说多态)。
逆变:子类出现的地方,可以用父类代替。

不变:声明的是什么类型,使用或传递的就要是什么类型。

注:以上只是协变与逆变的基本概念,在泛型中使用这两个特性,还有其它要注意的地方。

java中数组是协变的

  • 数组在java中,默认就是协变的

如下代码,在java中允许的:

String[] iAry = new String[] {"abc"}; //这是不变的
Object[] objects = iAry; //这里是协变的

再加一句:

objects[0] = 3; //能编译,运行报错了

运行后,会发生异常。因objects[0] 实际存储的是一个String类型的元素,而这里被赋值为一个Integer型。
这个例子,说明了,数组的协变使用是可能会发生问题的。使用数组的协变特性就要小心了,或者最好不要用这个特性。

Kotlin中的数组型变

在Kotlin中的数组,sdk定义了一个Array<T>这样的数组类型,任何kotlin数组,都继承了它。所以,Kotlin中数组型变,遵循泛型型变的特性。

泛型不变

比如,java代码:

List<String> = new ArrayList<String>();
Box<Integer> = new Box<Integer>();

声明泛型参数类型,和赋值的泛型参数类型需要相同。

List<String> = new ArrayList<Integer>();
Box<Integer> = new Box<String>();

这样是编译不过的,因为这是不变式的应用

java泛型协变

例如

// Java
interface ACollection<E>  {
	void addAll(ACollection<E> items);
}
class Test {
	void copyAll(ACollection<Object> to, ACollection<String> from) {
		to.addAll(from); //error
	}
}

根据泛型不变特性,这里的 to.addAll(from);是无法编译通过的。

现在,做如下修改:

// Java
interface ACollection<E>  {
	void addAll(ACollection<? extends E> items);
}

这里方法中,声明泛型参数时,加上了 ? extends 这样的通配符与关键字。
直译就是,E的子类/实现类。 匹配到copyAll方法的 to.addAll方法中,就是

ACollection<Object> {
	void addAll(ACollection<String extends Object> items);
}

加上了 “? extends” 这样的通配符定义后,就为对应的泛型参数的类型,指定了上限

再来看个例子:

List<? extends Object> list = new ArrayList<>();
list.add("abc");//error

这里的list.add()的完全语义:list.add(? extends Object),示例中的类型完全是符合的,但是在编译器中,直接报错了。

本例,与上一个例子的区别在哪里呢:
上例

// Java
interface ACollection<E>  {
	void addAll(ACollection<? extends E> items);
}

在接口中的方法定义上使用了协变语法,而在后期使用addAll()的对象,是已经确定泛型参数类型的对象:ACollection<Object>ACollection<String>

而本例中的list对象,它直接就是一个协变语法定义的对象:List<? extends Object> list;对应的,它的add()的语义:add(? extends Object)
这里的理解:list不知道它的具体泛型参数类型是Object,还是Object的某个子类型,而这样一个不知道它关联哪种具体类型的集合对象list,又要add一个不知道类型的对象,所以这是不支持的。

所以,在定义函数时,参数类型上 使用 协变式 泛型参数,

Kotlin泛型协变

Kotlin中协变语义的声明位置与java中不完全相同,可声明在 类或接口 的定义上,
如:

class Box3<E> {
    fun copy(from: Array<out E>, to: Array<in E>) {
        for (index in from.indices) {
            to[index] = from[index]
        }
    }
}

kotlin中,引用了生产者-消费者的概念来对应 泛型协变与逆变。

协变,对应生产者。即相关函数调用,是产生一个与泛型类型同类型的对象。使用关键字 out

如果将上面to: Array<E> 定义成 to: Array<out E> 那就会报错了。
to[index]在等式的左边,它不是一个被直接用来产生对象的函数。

结论:协变 — out — 生产者 — 直接产生泛型类型对象

java中泛型逆变

例,

void add(List<? super Integer> foo) {
    foo.add(3); //只能是integer
//  foo.add(new Object());//报错, 只能是integer
}

这里理解:foo对象不知道要放什么样的Integer的超类型对象,所以只能放Integer对象。
泛型逆变,确定泛型参数类型的下限

如上例,再加一句代码:

Integer a = foo.get(0);//error, 编译 不通过

因foo的定义,它不知道泛型参数对应Integer的何种基类型,所以调用get(),就不知道返回的是哪种类型了。

Kotlin泛型逆变

例,

fun copy2(from: Array<in Any>, to: Array<Any>) {
    for (index in from.indices) {
//        to[index] = from[index] //error
        from[index] = to[index]
    }
}

这里的 <in T> 关键字,就表示 这是一个逆变的泛型声明;类中所有用到T类型的地方,都要遵循逆变的特性。即支持消费对象,而不支持生产对象

结论:逆变 — in — 消费者 — 用于消费对象或者写入对象

泛型参数定义在其它位置

  • 在top级函数中使用泛型

即在kotlin-file中的,最外层使用泛型,如
文件Test.kt下:

fun <T> get(t: T): T {
    return t
}

直接在top级函数中使用泛型, 这里的泛型不能 定义为 in 或 out

  • 直接定义在类或接口声明中

如下,

interface AG<T> {} //这时是可以带型变关键字的

若无型变关键字,表示,对与 协变与逆变 都支持


星号类型推断

interface MyFun<in T, out U> {
    fun testp(t: T): U
    fun inParam(t: T)
    fun outValue(): U
}

fun test11(m: MyFun<*, String>) {//in 声明成*, 表示 in Nothing  即不能写入任何东西,因为此时不知道*是什么类型的
//    m.testp()
//    m.inParam()
    m.outValue()
}
fun test12(m: MyFun<Int, *>) {//out 声明成*,表示 out U
    val result = m.testp(33)
    println("test12: " + result)
    m.inParam(34)
    m.outValue()
}
fun test13(m: MyFun<*, *>) {// <in Nothing, out U>
//    m.testp()
//    m.inParam()
    m.outValue()
}

当不知道in的类型时,用 *表示 in类型, 可以安全地 防止 写入
out是读取的动作,用*表示,与原意一样


泛型函数

如下定义一个普通的泛型函数:

fun <T> singletonList(item: T): List<T> { 
	// ...
}

利用泛型参数,进行函数扩展:

fun <T> T.basicToString() : String { // extension function 
	// ...
}

调用泛型函数:

val l = singletonList<Int>(1)
val s = singletonList(1)//泛型类型自动推断

泛型类型约束

  • 最常见的一种约束是一个上边界约束, 对应于Java的extends关键字:
fun <T : Comparable<T>> sort(list: List<T>) { 
	// ...
}

<T : Comparable<T>>表示T要继承或实现自Comparable<T>

sort函数调用:

sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
  • 指定空类型上边界
fun <T: Any?> test():T? {
    return null //即使上边界为Any?,在使用时也要加上 ?
}
  • 对同一个泛型参数,指定多个上边界
    在声明泛型参数时,只能指定一个上边界,若要指定多个,需要用where关键字:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T> 
	where T : Comparable,
		  T : Cloneable {
	return list.filter { it > threshold }.map { it.clone() }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值