入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

Kotlin Reference (十一) 泛型、数组型变、泛型型变、泛型约束

创建时间:2017-07-09 投稿人: 浏览次数:2216

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

 


Kotlin作为一门基于 JVM 的新的编程语言,它也支持泛型类型:

class Box<T>(t: T) { 
    var value = t
}

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

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

如果,声明的是一个类型自动推测的表达式,那么可以省略泛型参数:

val box = Box(1)

 


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

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

协变:父类出现的地方,可以用子类代替(符合面向对象的基本原则,里氏替换原则;比如方法返回类型是一个基类型,返回值是一个子类型对象,这就是一种协变)
逆变:子类出现的地方,可以用父类代替

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

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

java中数组是协变的

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

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

Integer[] iAry = new Integer[] {33};
Object[] objects = iAry;

若将iAry改一下:

List<String>[] iAry = new List[1];
Object[] objects = iAry
objects[0] = 3;

运行后,会发生异常。因objects[0] 实际存储的是一个List类型的元素,而这里被赋值为一个Integer型。

Kotlin中的数组

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

泛型不变

比如,

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<in T, E> {
    fun copy(from: Array<out E>, to: Array<E>) {
        for (index in from.indices) {
            to[index] = from[index]
        }
    }
}

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

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

如果将上面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() }
}
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。