1. 类型投影(out
和 in
)基础概念
out
投影(协变):使用 out
关键字声明泛型类型参数,表明该类型参数是协变的。这意味着如果 A
是 B
的子类型,那么 GenericClass<out A>
就是 GenericClass<out B>
的子类型。
- 例如,假设有类继承关系
class Dog : Animal
,定义泛型类 class Box<out T>
。那么 Box<Dog>
可以被当作 Box<Animal>
使用。
- 从类型安全性角度,
out
投影限制只能从泛型类中读取数据,不能写入数据。因为写入操作可能破坏类型安全,例如在 Box<out Animal>
中写入 Dog
,但它实际可能被当作 Box<Animal>
使用,之后读取时会导致类型错误。
in
投影(逆变):使用 in
关键字声明泛型类型参数,表明该类型参数是逆变的。即如果 A
是 B
的子类型,那么 GenericClass<in B>
就是 GenericClass<in A>
的子类型。
- 例如,定义泛型类
class Processor<in T>
,若有类继承关系 class Dog : Animal
,则 Processor<Animal>
可以被当作 Processor<Dog>
使用。
- 从类型安全性角度,
in
投影限制只能向泛型类中写入数据,不能读取数据。因为读取操作可能破坏类型安全,例如从 Processor<in Dog>
读取数据,它实际可能被当作 Processor<in Animal>
使用,读取的数据类型可能不是预期的 Dog
。
2. 解决类型安全性和灵活性之间的矛盾
- 类型安全性:通过
out
和 in
投影对泛型类的读写操作进行限制,确保在使用泛型时不会发生类型错误。例如,Box<out T>
不能写入数据,防止将错误类型的数据放入容器,保证读取的数据类型安全。
- 灵活性:通过协变和逆变规则,使得泛型类型之间具有更灵活的子类型关系。例如,
Box<Dog>
可以赋值给 Box<Animal>
(当 Box
使用 out
投影时),在需要 Box<Animal>
的地方可以传入 Box<Dog>
,增加了代码的灵活性。
3. 复杂泛型类和函数设计中的应用
- 泛型类设计:
- 假设有一个用于读取数据的泛型类
Reader<out T>
,如下:
class Reader<out T>(private val data: T) {
fun read(): T {
return data
}
}
- 这里使用
out
投影,确保只能读取数据,保证类型安全。如果有 class Cat : Animal
,可以这样使用:
val catReader: Reader<Cat> = Reader(Cat())
val animalReader: Reader<Animal> = catReader
val animal: Animal = animalReader.read()
- 对于一个用于处理数据的泛型类
Processor<in T>
,如下:
class Processor<in T> {
fun process(data: T) {
// 处理逻辑
}
}
- 这里使用
in
投影,确保只能写入数据。如果有类继承关系 class Dog : Animal
,可以这样使用:
val animalProcessor: Processor<Animal> = Processor()
val dogProcessor: Processor<Dog> = animalProcessor
dogProcessor.process(Dog())
- 函数设计:考虑一个函数,接收一个
Reader
并处理其中的数据:
fun processReader(reader: Reader<out Animal>) {
val animal = reader.read()
// 处理动物逻辑
}
- 这样定义函数,可以接受
Reader<Dog>
或 Reader<Cat>
等作为参数,提高了代码复用性。同时,由于 Reader
使用了 out
投影,保证了类型安全。
- 再看一个写入数据的函数:
fun writeToProcessor(processor: Processor<in Dog>, dog: Dog) {
processor.process(dog)
}
- 这里
Processor
使用 in
投影,使得函数可以接受 Processor<Animal>
作为参数(因为 Processor<Animal>
是 Processor<Dog>
的子类型,逆变关系),提高了代码的灵活性和复用性,并且保证了类型安全。
4. 提高代码复用性和可维护性
- 复用性:通过合理运用类型投影,使得泛型类和函数可以接受更广泛的类型参数,而不需要为每个具体类型都编写重复的代码。例如上述的
processReader
函数可以处理不同动物类型的 Reader
,writeToProcessor
函数可以处理不同层次的 Processor
。
- 可维护性:类型投影保证了类型安全,减少了运行时类型错误的可能性,使得代码更易于理解和维护。当修改泛型类或函数的类型参数时,由于类型投影的规则,不会轻易破坏代码的类型安全,降低了维护成本。