网站的 规划与建设,汽车网站模板下载,广东个人 网站备案,软件外包公司怎么经营1 泛型
泛型即 “参数化类型”#xff0c;将类型参数化#xff0c;可以用在类#xff0c;接口#xff0c;函数上。与 Java 一样#xff0c;Kotlin 也提供泛型#xff0c;为类型安全提供保证#xff0c;消除类型强转的烦恼。 1.1 泛型优点
类型安全#xff1a;通用允许…1 泛型
泛型即 “参数化类型”将类型参数化可以用在类接口函数上。与 Java 一样Kotlin 也提供泛型为类型安全提供保证消除类型强转的烦恼。 1.1 泛型优点
类型安全通用允许仅保留单一类型的对象。泛型不允许存储其他对象。不需要类型转换不需要对对象进行类型转换。编译时检查在编译时检查泛型代码以便在运行时避免任何问题。
1.2 泛型声明
1.2.1 泛型类
interface ListT {fun get(index: Int): T
}泛型参数可在类中当普通类型使用。
1.2.2 泛型函数
fun T lastElement(list: ListT): T { ... }在 fun 关键字后声明 泛型形参可在参数和返回值处声明使用。高阶函数的例子
fun T ListT.filter(pridicate: (T) - Boolean): ListT { ... }1.2.3 泛型属性
val T ListT.last: Tget() {return last()}不管是泛型类、泛型函数还是泛型属性在使用之前必然已经确定了类型。如泛型类在实例化时需要指定泛型类型泛型函数在调用时必然已推导出泛型类型并替换为确定的类型实参泛型属性同理。
1.3 泛型约束
泛型类型参数是有边界的可以给泛型设置边界
interface NumberListT: Number {fun get(index: Int): T
}不指定边界则默认上边界为 Any?。如果希望非空需要显示指定为 T: Any 。 2 泛型擦除
和 Java 一样Kotlin 中的类型参数也会在运行时被擦除就是说泛型实例的类型实参在运行时是不保留的。不过 Kotlin 可以通过类型参数实化的方式保留类型信息需要使用内联函数。
由于泛型擦除下面的普通方法是无法编译的
fun T isA(obj: Any): Boolean {return obj is T
}通过内联下面代码可以通过编译
inline fun reified T isA(obj: Any): Boolean {return obj is T
}注意带 reified 类型参数的内联函数不能在 Java 代码中使用普通内联函数在 Java 中可以像常规函数一样调用而 reified 的类型参数需要额外处理将类型实参替换到字节码是永远需要内联的。 实化类型参数也是有限制的具体可以做 类型转换和检查 如 is 、as使用反射 T::class获取 java class T::class.java作为调用其他函数类型的实参。 3 变型
变型描述的是具有相同基础类型和不同类型参数的泛型类型之间的关系。这种关系可以是 协变的 或 逆变的。
先说说不变型一个泛型类如 MutableList 对任意两种类型实参 A、BMutableListA 既不是 MutableListB 的子类型也不是它的超类型则称 该类在该类型参数上是不变型的。Java 中的泛型类对所有类型参数都是不变型的。比如 Java 中你不能把一个 ListIngeter 实例传给形参是 ListNumber 的函数即使 Integer 是 Number 的子类。
前面说的子父类型关系类似于类的子父类关系比如 A 是 B 的子类那么 A 是 B 的子类型任一非空类型是其可空类型的子类型比如 Person 是 Person? 的子类型下面会说到 Kotlin 中借助协变使得 ListInt 能够成为 ListNumber 的子类型注意区分 子类 和 子类型。
Kotlin 中比如上面自定义的 List 接口也是不变型的ListInt 并不是 ListNumber 的子类型因此你不能把一个 ListInt 实例传给形参是 ListNumber 的函数。
注意 Kotlin 标准库中的 List 接口是可以的因为是协变的别和这里自定义的 List 搞混了
3.1 Out (协变)
对于 out 泛型我们能够将使用子类泛型的对象赋值给使用父类泛型的对象。如果将上面的 List 接口定义改为
interface Listout T {fun get(index: Int): T
}则称该 List 接口是协变的如果基础类型间有子类型关系则泛型类也具有相同的子类型关系。如 Int 是 Number 的子类型则 上面定义的 ListInt 也是ListNumber 的子类型。这样就可以将 一个 ListInt 实例传给形参是 ListNumber 的函数了简单来说协变——父类引用指向子类
当然out 也不可以滥用 因为不安全比如将一个 ListInt 实例传入形参是 ListAny 的函数该函数像实例中添加 Any 类型的数据显然是错误的
fun addMore(list: ListAny) {list.add(abc)
}
addMore(listOf(1, 2, 3))为了防止这种风险如果类在该类型参数上是协变的那么该类型参数只能出现在返回值位置我们称之为 out 位置即该泛型参数只读编译器也会做这种检查。Kotlin 中的 List 接口就是协变的。集合可读、不可写集合泛型协变。
3.2 In (逆变)
和协变相反对于 in 泛型我们可以将使用父类泛型的对象赋值给使用子类泛型的对象。如果一个泛型类 MyClass 是逆变的则对于 有子类型关系的 A 和 BA是B的子类型则 MyClassA 是 MyClassB 的超类型。逆变——子类引用指向父类 例如 Comparable 接口
public interface Comparablein T {public operator fun compareTo(other: T): Int
}那么ComparableAny 是 ComparableInt 的子类型。可以尝试理解成“能对 Any 类型进行比较”的比较器也能比较 Int 类型“。
类似的这里的泛型参数只能出现在函数参数位置我们称之为 in 位置。集合可读 Any 、可写集合泛型逆变。
3.3 声明点变型
即声明泛型的地方产生变型。前面说的变型是针对类的所有实例的。而声明点变型则可以只针对某一实例变型。如
val list: MutableListout Int MutableList()
list.add(1,2) //报错上面代码会将 list 变为只读的。
在 Java 中没有类的变型声明只通过声明点产生型变如
public interface StreamT {R StreamR map(Function? super T, ? extends R mapper);
}与 Kotlin 不同的是Java 通过 ? super T 产生逆变? extends R 产生协变。
3.4 星号投影
List* 对应与 Java 中的 List? 表示不确定的任意类型类型实参。可能是 Int可能是Any是确定的某种类型但对使用者是未知的因而不能生产该值只能访问当作 Any? 访问。注意和 ListAny? 作区分。
4 小结
Kotlin 的泛型概念和声明和 Java 相当接近。Kotlin 的类型实参和 Java 一样会在运行期擦除。Kotlin 可以通过类型参数实化保留运行时类型实参需要借助内联函数。变型指的是具有相同基础类型和不同类型参数的泛型类型间的子类型关系。他指出了如果一个泛型类型的类型参数是另一个泛型类型类型参数的子类型那么这个泛型类就是另一个泛型类的子类型或超类型。如果某个类在一个类型参数上声明成协变的那么该类型参数只能出现在 out 位置上。逆变相反。Java 的泛型类都是不变型的。声明点泛型只在声明处产生变型Java 的变型都是该方式。Kotlin 既可以使用声明点变型也可以在整个泛型类上声明变型。如果确切的类型实参是未知的或不重要的时候可以使用星号投影。