Kotlin---泛型

2019-03-15 10:54:19 浏览数 (1)

Kotlin不变型泛型

Kotlin的不变型泛型和Java一样,通过声明泛型类型来使用泛型类。而该种泛型声明后,则无法使用父类方法与属性。在编译时候,会将泛型擦除。

代码语言:javascript复制
fun test() {
     val m = Noob<Int>()
     val n = Noob<String>()
}

class Noob<T> {
     var a: T? = null

     fun print() {
         println(a)
     }
}

泛型的单继承关系

如果使用单继承关系的话,也和Java相同,在泛型定义时,使用继承即可

代码语言:javascript复制
fun test() {
     val m = Noob<Double>()
     val n = Noob<Float>()
}

// 指定泛型T是Number的子类
class Noob<T : Number> { 
     var a: T? = null

     fun print() {
         println(a?.toInt())
     }
}

泛型的多继承关系

当泛型需要使用多继承关系的话,则可以使用where子句来约束该泛型的每一个子类。

代码语言:javascript复制
class Noob<T> where T : Number, T : Comparable<T> {
      lateinit var a: T

      fun compare(c: T?): Int {
          print(c?.toInt())
         return c?.compareTo(a) ?: 0
      }
}

协变(Covariant)与逆变(Contravariance)

在Java中同样也有这两种类型变换。对于Java而言,也就是:

  • 协变:List<? extends String>
  • 逆变:List<? super String>

通过继承,我们可以让A继承B,而协变可以理解成A->B,而逆变则可以理解成B->A。

在Java中,可能会出现这种情况:

代码语言:javascript复制
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // 编译出错!!!即将来临的问题的原因就在这里。Java 禁止这样!
objs.add(1); // 这里我们把一个整数放入一个字符串列表
String s = strs.get(0); // !!! ClassCastException:无法将整数转换为字符串

而一般认为,String是Object的子类,而List<Object> objs = strs理论上是正确的,但是编译会出错,因为List的泛型是不变型的,也就是定义的泛型是Object就必须是Object,而不能是它的子类。

而这极大的限制了程序的灵活性。于是就产生了协变与逆变。

首先看一下协变:

代码语言:javascript复制
class A{}
class B extends A{}

List<B> listB = new ArrayList();
List<? extends B> objList = listB; // 正常编译
objList.add(new B()); // 编译出错
B m = objList.get(0); // 正常编译

通过List<? extends B>定义了协变,允许从列表中获取的对象都可以转换成B的引用,但是不允许往该列表中添加对象。

在看一下逆变:

代码语言:javascript复制
class A{}
class B extends A{}

List<A> listA = new ArrayList();
List<? super A> objList = listA; // 正常编译
objList.add(new B()); // 正常编译
A m = objList.get(0); // 编译出错

通过List<? super A>定义了逆变,允许向列表中添加以A为父类的B类对象,而不允许从列表中获取对象。

通过协变与逆变的方式,在保证代码灵活性的同时,也定义了代码的上下边界,保证代码的安全性。

Kotlin中的协变与逆变

前人总结过使用协变与逆变的时机,即:PECS。也就是:

  • Product Extends,Consumer Super

也就是,当你使用它来向外输出数据时,可认为它是Productor,则需要使用Extends,而当使用它来接收外部数据时,则可认为它是Consumer,则需要使用Super

从上例可以看到,协变可以从objList中成功获取B对象,说明此时objList则是作为Productor向外部输出数据,所以需要使用extends。而逆变可以允许objList中添加B对象,则可认为此时objList是作为Consumer来消费外部传入的数据。

而在Kotlin中使用outin来实现协变与逆变。

首先定义三个类,递增继承。

代码语言:javascript复制
open class Man(var age: Int)

open class YoungMan(age: Int, var school: String) : Man(age)

class OldMan(age: Int, school: String, var company: String) : YoungMan(age, school)

Kotlin的协变,也就是Productor,使用out来限制不允许输入:

代码语言:javascript复制
val youngManList: MutableList<out YoungMan> = ArrayList()
youngManList.add(YoungMan(10, "")) // 编译出错,限制往列表中添加元素
val x: YoungMan = youngManList.get(0) // 编译通过

而逆变,也就是Consumer,则使用in来限制不允许输出:

代码语言:javascript复制
val youngManList: MutableList<in YoungMan> = ArrayList()
youngManList.add(YoungMan(10, "")) //编译通过
val x: YoungMan = youngManList.get(0) // 编译出错,限制从列表中获取元素

而在普通的类中使用也是同样的效果,当使用in时:

代码语言:javascript复制
class MyClass<in T> where T : Number {

        fun printT(x: T) {  // 编译通过
            x.toLong()
        }

        fun getT(): T? {  //  编译错误,
            return null
        }
    }

当使用out时:

代码语言:javascript复制
class MyClass<out T> where T : Number {

        fun printT(x: T) {  // 编译错误
            x.toLong()
        }

        fun getT(): T? {   // 编译通过
            return null
        }
    }

0 人点赞