kotlin中泛型中in和out的区别

  1. 概念含义

    • in关键字(逆变)
      • 在Kotlin泛型中,in关键字主要用于定义逆变(Contravariance)。它表示一个泛型类型参数可以是指定类型或者它的超类型。简单来说,就是对于类型AB,如果AB的子类型,那么Consumer<B>Consumer<A>的子类型。这里的Consumer是一个具有in关键字修饰泛型参数的类型,例如接口或者类。
    • out关键字(协变)
      • out关键字用于定义协变(Covariance)。它意味着一个泛型类型参数可以是指定类型或者它的子类型。也就是说,对于类型AB,如果AB的子类型,那么Producer<A>Producer<B>的子类型。这里的Producer是一个带有out关键字修饰泛型参数的类型,像接口或者类。
  2. 使用场景和位置限制

    • in关键字
      • 主要用于函数参数类型in关键字在泛型类型参数用于函数参数时体现逆变特性。例如,考虑一个函数接口AnimalHandler<in T>,其中T是用in修饰的泛型参数。
        interface AnimalHandler<in T> {
            fun handle(animal: T): Unit
        }
        
      • 假设DogAnimal的子类型。那么一个AnimalHandler<Animal>类型的变量可以接收一个AnimalHandler<Dog>类型的值,因为AnimalHandlerin位置(函数参数位置)是逆变的。
        val animalHandler: AnimalHandler<Animal> = object : AnimalHandler<Dog> {
            override fun handle(dog: Dog): Unit {
                println("Handling a dog")
            }
        }
        
      • 不能用于返回值类型:如果在应该返回Tin修饰的泛型参数)的地方使用,会导致编译错误。因为in规定这个类型参数主要用于接收超类型的值,而不是返回子类型的值。
    • out关键字
      • 主要用于函数返回值类型out关键字在泛型类型参数用于函数返回值时体现协变特性。例如,有一个接口Producer<out T>,它用于产生某种类型T的对象。
        interface Producer<out T> {
            fun produce(): T
        }
        
      • 假设StringAny的子类型。一个Producer<String>可以被当作Producer<Any>来使用,因为Producerout位置(函数返回值位置)是协变的。
        val producer: Producer<Any> = object : Producer<String> {
            override fun produce(): String {
                return "Hello"
            }
        }
        
      • 不能用于函数参数类型:如果试图将out修饰的泛型参数用于函数参数,编译器会报错。因为out规定这个类型参数主要用于返回子类型的值,而不是接收子类型的值作为参数。
  3. 目的和效果

    • in关键字的目的和效果
      • 目的是实现更灵活的函数参数类型匹配,特别是在处理超类型和子类型关系时。它允许在一个更通用的类型(超类型)的消费者(如接口方法接收超类型参数)中使用更具体的类型(子类型)的实现。这样可以使代码在处理函数参数时,能够以一种逆变的方式利用类型层次结构,增强代码的通用性和灵活性。
    • out关键字的目的和效果
      • 主要目的是在处理返回值类型时,能够利用协变特性,使得代码更加灵活。通过允许子类型的生产者(如接口方法返回子类型的值)可以被当作更通用类型(超类型)的生产者来使用,方便在不同层次的类型之间进行转换和赋值,同时保证类型安全。这在构建具有多态性的返回值类型的接口和类时非常有用,可以让调用者以更灵活的方式处理返回值。
Kotlin中的是一种类变量的机制,它允许我们在不确定具体类的情况下编写通用代码。Kotlin中的支持协变逆变,以及inout的关键字。 首先,我们来看一个简单的例子,实现一个容器类: ```kotlin class Container<T>(var item: T) { fun getItem(): T { return item } } fun main() { val container = Container<String>("Hello") println(container.getItem()) } ``` 在这个例子中,我们定义了一个名为Container的类,它有一个类参数T。我们可以创建一个Container实例,并将其实例化为一个具体类。我们使用getItem方法来获取这个容器中的item。 接下来,我们来介绍一下协变逆变。假设我们有两个类: ```kotlin open class Animal { fun makeSound() { println("Making animal sound") } } class Cat: Animal() { fun meow() { println("Meow") } } ``` 我们可以通过一个简单的示例来说明协变逆变: ```kotlin fun main() { val animals: List<Animal> = listOf(Cat(), Animal()) makeSounds(animals) } fun makeSounds(animals: List<Animal>) { for (animal in animals) { animal.makeSound() } } ``` 在这个例子中,我们定义了一个List<Animal>类的变量animals,它包含了一个Cat一个Animal实例。我们将这个变量传递给了makeSounds函数,该函数接受一个List<Animal>类的参数。 在makeSounds函数中,我们使用for循环遍历animals列表,并对其中的每个Animal实例调用makeSound方法。由于Cat是Animal的子类,因此它也可以被视为Animal类,因此我们可以将其添加到List<Animal>类的变量中。 这里的List<Animal>类就是协变的,因为我们可以将它的子类(如Cat)作为参数传递给makeSounds函数。 现在我们来看一下逆变。假设我们有一个接受Animal类的参数的函数: ```kotlin fun takeAnimal(animal: Animal) { animal.makeSound() } ``` 我们可以将这个函数传递给另一个函数,该函数期望一个Cat类的参数。在这种情况下,我们可以使用in关键字来表示逆变: ```kotlin fun main() { val cat: Cat = Cat() takeCat(cat) } fun takeCat(cat: Cat) { takeAnimal(cat) } fun takeAnimal(animal: Animal) { animal.makeSound() } ``` 在这个例子中,我们定义了一个takeCat函数,它接受一个Cat类的参数。我们将这个函数传递给了takeAnimal函数,该函数期望一个Animal类的参数。由于Cat是Animal的子类,因此我们可以将Cat类的参数传递给takeAnimal函数。这里的takeAnimal函数是逆变的,因为它可以接受其超类(如Animal)的参数。 最后,我们来看一下outin关键字。我们可以在定义参数时使用这些关键字来限制参数的使用方式。out关键字用于声明参数是协变的,in关键字用于声明参数是逆变的。 例如,我们可以定义一个只允许读取的接口: ```kotlin interface ReadOnlyContainer<out T> { fun getItem(): T } ``` 在这个例子中,我们使用out关键字来声明参数T是协变的。这意味着我们只能从ReadOnlyContainer接口中获取T类的值,而不能修改它。这样做的好处是可以使我们更加安全地使用参数。 类似地,我们可以定义一个只允许写入的接口: ```kotlin interface WriteOnlyContainer<in T> { fun setItem(item: T) } ``` 在这个例子中,我们使用in关键字来声明参数T是逆变的。这意味着我们只能向WriteOnlyContainer接口中设置T类的值,而不能获取它。这样做的好处是可以避免意外修改参数的值。 总结一下,Kotlin中的支持协变逆变,以及inout关键字。使用协变逆变可以使我们更加灵活地使用参数,而使用inout关键字可以帮助我们更加安全地使用参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值