Kotlin泛型位置规律与设计考量
1. 泛型出现的位置分类
在Kotlin中,泛型可以出现在以下几个主要位置:
1.1 类声明中的泛型
1class ViewRef<T : DeclarativeBaseView<*, *>>( 2 val pagerId: String, 3 val nativeRef: Int 4) { 5 val view: T? 6 get() = PagerManager.getPager(pagerId) 7 .getViewWithNativeRef(nativeRef) as? T 8} 9
规律:
- 泛型参数
<T : DeclarativeBaseView<*, *>>紧跟在类名后面 - 用于定义整个类的类型参数
- 可以在类的任何地方使用这个类型参数
设计考量:
- 类型安全:确保ViewRef只能引用特定类型的视图
- 代码复用:一个类可以处理多种类型,但保持类型安全
- API一致性:所有ViewRef实例都有相同的方法签名,但类型不同
1.2 函数声明中的泛型
1fun <T : DeclarativeBaseView<*, *>> T.ref(ref: (viewRef: ViewRef<T>) -> Unit) { 2 ref(ViewRef<T>(pagerId, nativeRef)) 3} 4
规律:
- 泛型参数
<T : DeclarativeBaseView<*, *>>在fun关键字和函数名之间 - 用于定义函数的类型参数
- 可以在函数的参数、返回值和函数体中使用这个类型参数
设计考量:
- 扩展函数:为特定类型的所有子类提供统一方法
- 类型推断:编译器可以自动推断T的类型
- 灵活性:同一个函数可以处理多种类型,但保持类型安全
1.3 接口声明中的泛型
1interface IViewPublicApi<A : Attr, E : Event> { 2 fun <T : DeclarativeBaseView<*, *>> T.ref(ref: (viewRef: ViewRef<T>) -> Unit) 3 fun attr(init: A.() -> Unit) 4 fun event(init: E.() -> Unit) 5} 6
规律:
- 接口级别的泛型
<A : Attr, E : Event>在接口名后 - 方法级别的泛型
<T : DeclarativeBaseView<*, *>>在方法名前
设计考量:
- 接口泛型:定义接口的类型参数,影响整个接口
- 方法泛型:只影响特定方法的类型参数
2. 泛型位置的设计规律
2.1 作用域规律
1// 类级别泛型:作用域是整个类 2class ViewRef<T : DeclarativeBaseView<*, *>> { 3 val view: T? // T在整个类中可用 4 fun doSomething(): T? { return view } // T在方法中可用 5} 6 7// 函数级别泛型:作用域是整个函数 8fun <T : DeclarativeBaseView<*, *>> T.ref(ref: (viewRef: ViewRef<T>) -> Unit) { 9 // T只在函数内可用 10 ref(ViewRef<T>(pagerId, nativeRef)) 11} 12
2.2 生命周期规律
1// 类泛型:与类实例生命周期相同 2class Container<T> { 3 private val items = mutableListOf<T>() 4 fun add(item: T) { items.add(item) } 5 fun get(index: Int): T = items[index] 6} 7 8// 函数泛型:只存在于函数调用期间 9fun <T> createList(vararg items: T): List<T> { 10 return listOf(*items) // T只在函数执行期间有效 11} 12
3. 泛型约束的位置规律
3.1 上界约束
1// 类泛型约束 2class ViewRef<T : DeclarativeBaseView<*, *>> { // T必须是DeclarativeBaseView的子类 3 // ... 4} 5 6// 函数泛型约束 7fun <T : DeclarativeBaseView<*, *>> T.ref(...) { // T必须是DeclarativeBaseView的子类 8 // ... 9} 10 11// 多重约束 12fun <T> process(item: T) where T : DeclarativeBaseView<*, *>, T : Cloneable { 13 // T必须同时满足两个约束 14} 15
3.2 型变约束
1// 生产者位置(out) 2class Producer<out T> { 3 fun produce(): T { ... } 4} 5 6// 消费者位置(in) 7class Consumer<in T> { 8 fun consume(item: T) { ... } 9} 10 11// 不变位置 12class Container<T> { 13 fun get(): T { ... } 14 fun set(item: T) { ... } 15} 16
4. 泛型在Kotlin中的特殊位置
4.1 扩展函数中的泛型
1// 扩展函数泛型:T是接收者类型 2fun <T : DeclarativeBaseView<*, *>> T.ref(ref: (viewRef: ViewRef<T>) -> Unit) { 3 // T既是泛型参数,也是接收者类型 4} 5 6// 扩展属性中的泛型 7val <T : DeclarativeBaseView<*, *>> T.refCount: Int 8 get() = 1 9
4.2 高阶函数中的泛型
1// 函数类型中的泛型 2fun <T> execute(operation: (T) -> Unit, param: T) { 3 operation(param) 4} 5 6// 函数类型参数中的泛型 7fun <T> ref(ref: (viewRef: ViewRef<T>) -> Unit) { 8 // T在函数类型参数中使用 9} 10
4.3 泛型型变
1// 协变(out) 2interface Producer<out T> { 3 fun produce(): T 4} 5 6// 逆变(in) 7interface Consumer<in T> { 8 fun consume(item: T) 9} 10 11// 星投影 12fun process(list: List<*>) { 13 // List<*> 表示未知类型的List 14} 15
5. 实际项目中的泛型位置选择
5.1 类泛型 vs 函数泛型
1// 使用类泛型的情况:类型需要在整个类中保持一致 2class ViewRef<T : DeclarativeBaseView<*, *>> { 3 val view: T? // 需要在多个地方使用T 4 fun doSomething(): T? { return view } 5} 6 7// 使用函数泛型的情况:类型只在函数中使用 8fun <T : DeclarativeBaseView<*, *>> T.ref(ref: (viewRef: ViewRef<T>) -> Unit) { 9 // T只在函数内使用 10} 11
5.2 多层泛型嵌套
1// 多层泛型嵌套 2class PagerManager { 3 fun <T : DeclarativeBaseView<*, *>> getView(pagerId: String, nativeRef: Int): T? { 4 return PagerManager.getPager(pagerId) 5 .getViewWithNativeRef(nativeRef) as? T 6 } 7} 8 9// 使用时 10val textView: TextView? = PagerManager.getView<TextView>("pager1", 123) 11val buttonView: Button? = PagerManager.getView<Button>("pager1", 456) 12
6. 泛型位置的设计原则
6.1 最小作用域原则
1// 好的设计:泛型作用域最小化 2fun <T> process(item: T): String { 3 return item.toString() 4} 5 6// 避免:不必要的类泛型 7class Processor<T> { 8 fun process(item: T): String { 9 return item.toString() 10 } 11} 12
6.2 类型安全原则
1// 好的设计:明确的类型约束 2class ViewRef<T : DeclarativeBaseView<*, *>> { 3 // 确保T是DeclarativeBaseView的子类 4} 5 6// 避免:无约束的泛型 7class ViewRef<T> { 8 // T可以是任何类型,可能导致运行时错误 9} 10
6.3 可读性原则
1// 好的设计:有意义的泛型参数名 2class ViewRef<TView : DeclarativeBaseView<*, *>> { 3 fun getView(): TView? { ... } 4} 5 6// 避免:无意义的泛型参数名 7class ViewRef<T> { 8 fun getView(): T? { ... } 9} 10
7. 总结
Kotlin中泛型位置的选择遵循以下规律和原则:
- 作用域决定位置:
- 类泛型:需要在整个类中使用
- 函数泛型:只在函数中使用
- 接口泛型:影响整个接口
- 生命周期决定位置:
- 长生命周期:使用类泛型
- 短生命周期:使用函数泛型
- 类型安全决定约束:
- 明确约束:使用上界约束
- 多重约束:使用where子句
- 使用场景决定型变:
- 生产者:使用out
- 消费者:使用in
- 读写操作:不变
在ViewRef的设计中:
- 类泛型
<T : DeclarativeBaseView<*, *>>确保类型安全 - 函数泛型
<T : DeclarativeBaseView<*, *>> T.ref(...)提供扩展能力 - 泛型约束确保只能引用正确的视图类型
- 泛型位置的选择平衡了灵活性、安全性和可读性
这种设计使得ViewRef既类型安全又使用灵活,是Kotlin泛型系统在实际项目中的优秀应用。
《Kotlin泛型位置规律与设计考量》 是转载文章,点击查看原文。