网站seo是什么意,小程序如何制作开发,建站属于什么行业,响应式设计以下内容摘自郭霖《第一行代码》第三版
泛型的协变
一个泛型类或者泛型接口中的方法#xff0c;它的参数列表是接收数据的地方#xff0c;因此可以称它为in位置#xff0c;而它的返回值是输出数据的地方#xff0c;因此可以称它为out位置。 先定义三个类#xff1a;
op…以下内容摘自郭霖《第一行代码》第三版
泛型的协变
一个泛型类或者泛型接口中的方法它的参数列表是接收数据的地方因此可以称它为in位置而它的返回值是输出数据的地方因此可以称它为out位置。 先定义三个类
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)定义一个SimpleData类
class SimpleDataT {private var data: T? nullfun set(t: T?) {data t}fun get(): T? {return data}
}那么以下代码就会存在编译问题
fun main() {val student Student(Tom, 19)val data SimpleDataStudent()data.set(student)handleSimpleData(data) // 实际上这行代码会报错val studentData data.get()
}
fun handleSimpleData(data: SimpleDataPerson) {val teacher Teacher(Jack, 35)data.set(teacher)
}即使Student是Person的子类SimpleDataStudent并不是SimpleDataPerson的子类。
问题发生的主要原因是我们在handleSimpleData()方法中向SimpleDataPerson里设置了一个Teacher的实例。如果SimpleData在泛型T上是只读的话肯定就没有类型转换的安全隐患了。
泛型协变的定义假如定义了一个MyClassT的泛型类其中A是B的子类型同时MyClassA又是MyClassB的子类型那么我们就可以称MyClass在T这个泛型上是协变的。
要实现一个泛型类在其泛型类型的数据上是只读则需要让MyClassT类中的所有方法都不能接收T类型的参数。换句话说T只能出现在out位置上而不能出现在in位置上。
修改SimpleData类的代码
class SimpleDataout T(val data: T?) {fun get(): T? {return data}
}在泛型T的声明前面加上了一个out关键字。这就意味着现在T只能出现在out位置上而不能出现在in位置上同时也意味着SimpleData在泛型T上是协变的。
由于泛型T不能出现在in位置上因此我们也就不能使用set()方法为data参数赋值了所以这里改成了使用构造函数的方式来赋值。由于这里我们使用了val关键字所以构造函数中的泛型T仍然是只读的因此这样写是合法且安全的。另外即使我们使用了var关键字但只要给它加上private修饰符保证这个泛型T对于外部而言是不可修改的那么就都是合法的写法。
fun main() {val student Student(Tom, 19)val data SimpleDataStudent(student)handleMyData(data)val studentData data.get()
}
fun handleMyData(data: SimpleDataPerson) {val personData data.get()
}由于SimpleData类已经进行了协变声明那么SimpleDataStudent自然就是SimpleDataPerson的子类了所以这里可以安全地向handleMyData()方法中传递参数。
然后在handleMyData()方法中去获取SimpleData封装的数据虽然这里泛型声明的是Person类型实际获得的会是一个Student的实例但由于Person是Student的父类向上转型是完全安全的所以这段代码没有任何问题。
Kotlin已经默认给许多内置的API加上了协变声明其中就包括了各种集合的类与接口。
List简化版源码
public interface Listout E : CollectionE {override val size: Intoverride fun isEmpty(): Booleanoverride fun contains(element: UnsafeVariance E): Booleanoverride fun iterator(): IteratorEpublic operator fun get(index: Int): E
}原则上在声明了协变之后泛型E就只能出现在out位置上可是在contains()方法中泛型E仍然出现在了in位置上。这么写本身是不合法的因为在in位置上出现了泛型E就意味着会有类型转换的安全隐患。但是contains()方法的目的非常明确它只是为了判断当前集合中是否包含参数中传入的这个元素而并不会修改当前集合中的内容因此这种操作实质上又是安全的。那么为了让编译器能够理解我们的这种操作是安全的这里在泛型E的前面又加上了一个UnsafeVariance注解这样编译器就会允许泛型E出现在in位置上了。
泛型的逆变
假如定义了一个MyClassT的泛型类其中A是B的子类型同时MyClassB又是MyClassA的子类型那么我们就可以称MyClass在T这个泛型上是逆变的。
逆变与协变的区别 先定义一个Transformer接口用于执行一些转换操作
interface TransformerT {fun transform(t: T): String
}现在尝试对Transformer接口进行实现
fun main() {val trans object : TransformerPerson {override fun transform(t: Person): String {return ${t.name} ${t.age}}}handleTransformer(trans) // 这行代码会报错
}
fun handleTransformer(trans: TransformerStudent) {val student Student(Tom, 19)val result trans.transform(student)
}这段代码从安全的角度来分析是没有任何问题的因为Student是Person的子类使用TransformerPerson的匿名类实现将Student对象转换成一个字符串也是绝对安全的并不存在类型转换的安全隐患。但是实际上在调用handleTransformer()方法的时候却会提示语法错误原因也很简单TransformerPerson并不是TransformerStudent的子类型。
那么这个时候逆变就可以派上用场了它就是专门用于处理这种情况的。修改Transformer接口中的代码
interface Transformerin T {fun transform(t: T): String
}这里我们在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上而不能出现在out位置上同时也意味着Transformer在泛型T上是逆变的。
Kotlin在提供协变和逆变功能时就已经把各种潜在的类型转换安全隐患全部考虑进去了。只要严格按照其语法规则让泛型在协变时只出现在out位置上逆变时只出现在in位置上就不会存在类型转换异常的情况。虽然UnsafeVariance注解可以打破这一语法规则但同时也会带来额外的风险。
逆变比较典型的例子就是Comparable的使用。Comparable是一个用于比较两个对象大小的接口其源码定义如下
interface Comparablein T {operator fun compareTo(other: T): Int
}