前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin---泛型

Kotlin---泛型

作者头像
None_Ling
发布2019-03-15 10:54:19
9150
发布2019-03-15 10:54:19
举报
文章被收录于专栏:Android相关Android相关

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
        }
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.03.06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Kotlin不变型泛型
  • 泛型的单继承关系
  • 泛型的多继承关系
  • 协变(Covariant)与逆变(Contravariance)
  • Kotlin中的协变与逆变
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档