📜  Kotlin 泛型

📅  最后修改于: 2022-05-13 01:54:32.557000             🧑  作者: Mango

Kotlin 泛型

泛型是强大的特性,它允许我们定义可以使用不同数据类型访问的类、方法和属性,同时保持对编译时类型安全性的检查。

创建参数化类 –
泛型类型是在类型上参数化的类或方法。我们总是使用尖括号 ( ) 来指定程序中的类型参数。

泛型类定义如下:

class MyClass(text: T) {
    var name = text
}

要创建此类的实例,我们需要提供类型参数:

val my : MyClass = Myclass("GeeksforGeeks")

如果参数可以从构造函数的参数中推断出来,则可以省略类型参数:

val my = MyClass("GeeksforGeeks") 

在这里,GeeksforGeeks 的类型为 String,因此编译器发现我们正在谈论 Myclass

通用的优点——

  1. 类型转换是不可避免的——不需要对对象进行类型转换。
  2. 类型安全- 泛型一次只允许单一类型的对象。
  3. 编译时安全- 在编译时检查泛型代码的参数化类型,以避免运行时错误。

在我们的程序中通用-

在下面的示例中,我们使用具有单个参数的主构造函数创建了一个Company类。现在,我们尝试将 Company 类的对象中不同类型的数据作为 String 和 Integer 传递。 Company 类的主构造函数接受字符串类型( “GeeskforGeeks” ),但在传递整数类型( 12 )时会出现编译时错误。

没有泛型类的 Kotlin 程序-

class Company (text: String) {
    var x = text
    init{
        println(x)
    }
}
fun main(args: Array){
    var name: Company = Company("GeeksforGeeks")
    var rank: Company = Company(12)// compile time error
}

输出:

Error:(10, 33) Kotlin: The integer literal does not conform to the expected type String

为了解决上述问题,我们可以创建一个用户定义的泛型类型类,它在单个类中接受不同类型的参数。类公司类型是一个通用类型类,它接受 Int 和 String 类型的参数。

使用泛型类的 Kotlin 程序-

class Company (text : T){
    var x = text
    init{
        println(x)
    }
}
fun main(args: Array){
    var name: Company = Company("GeeksforGeeks")
    var rank: Company = Company(12)
} 

输出:

GeeksforGeeks
1234

差异——

与Java不同,Kotlin 默认使数组保持不变。通过扩展,泛型类型在 Kotlin 中是不变的。这可以通过outin关键字进行管理。不变性是已经为特定数据类型定义的标准泛型函数/类不能接受或返回另一种数据类型的属性。 Any是所有其他数据类型的超类型。
方差有两种类型——

  1. 声明站点差异(使用输入和输出)
  2. 使用地点差异:类型投影

Kotlin 出入关键字——

out关键字——

在 Kotlin 中,我们可以在泛型类型上使用out关键字,这意味着我们可以将此引用分配给它的任何超类型。 out值只能由给定类产生,但不能消耗:

class OutClass(val value: T) {
    fun get(): T {
        return value
    }
}

上面,我们定义了一个可以产生 T 类型值的OutClass类。然后,我们可以将 OutClass 的一个实例分配给它的超类型引用:

val out = OutClass("string")
val ref: OutClass = out    

注意:如果我们没有在上面的类中使用out类型,那么给定的语句将产生编译错误。

in关键字 –

如果我们想将它分配给它的子类型的引用,那么我们可以在泛型类型上使用in关键字。 in关键字只能用于使用的参数类型,而不是生成的参数类型:

class InClass {
    fun toString(value: T): String {
        return value.toString()
    }
}

在这里,我们声明了一个 toString() 方法,它只使用 T 类型的值。然后,我们可以将 Number 类型的引用分配给它的子类型 Int 的引用:

val inClassObject: InClass = InClass()
val ref = inClassObject

注意:如果我们没有在上面的类中使用 in 类型,那么给定的语句将产生编译器错误。

协方差——

协方差意味着替换子类型是可以接受的,但超类型不是,即泛型函数/类可以接受它已经定义的数据类型的子类型,例如为 Number 定义的泛型类可以接受 Int,但为 Int 定义的泛型类不能接受数字。这可以在 Kotlin 中使用out关键字实现,如下所示 -

fun main(args: Array) {
    val x: MyClass = MyClass()        // Error: Type mismatch
    val y: MyClass = MyClass() // Works since String is a subtype of Any
    val z: MyClass = MyClass() // Error since Any is a supertype of String
}
class MyClass

我们可以通过将 out 关键字附加到声明站点来直接允许协方差。下面的代码工作得很好。

fun main(args: Array) {
        val y: MyClass = MyClass() // Compiles without error
}
class MyClass

协方差——

它用于替换子类型中的超类型值,即泛型函数/类可以接受已经为其定义的数据类型的超类型,例如为 Number 定义的泛型类不能接受 Int,但为 Int 定义的泛型类可以接受数字。它是在 Kotlin 中使用in关键字实现的,如下所示 -

fun main(args: Array) {
        var a: Container = Container()  //compiles without error
        var b: Container = Container()  //gives compilation error
}
open class Animal
class Dog : Animal()
class Container

类型预测 –

如果我们想将某个类型的数组的所有元素复制到 Any 类型的数组中,那么这是可能的,但是为了让编译器能够编译我们的代码,我们需要使用out关键字注释输入参数。这使得编译器推断输入参数可以是 Any 的子类型的任何类型:

Kotlin 程序将一个数组的元素复制到另一个数组中——

fun copy(from: Array, to: Array) {
    assert(from.size == to.size)
    // copying (from) array to (to) array
    for (i in from.indices)
        to[i] = from[i]
    // printing elements of array in which copied
    for (i in to.indices) {
    println(to[i])
    }
}
fun main(args :Array) {
    val ints: Array = arrayOf(1, 2, 3)
    val any :Array = Array(3) { "" }
    copy(ints, any)
  
}

输出:

1
2
3

星星投影——

当我们不知道值的具体类型并且只想打印数组的所有元素时,我们使用 star(*) 投影。

使用星形投影的 Kotlin 程序 –

// star projection in array
fun printArray(array: Array<*>) {
    array.forEach { print(it) }
}
fun main(args :Array) {
    val name  = arrayOf("Geeks","for","Geeks")
    printArray(name)
}

输出:

GeeksforGeeks