欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin(八)之泛型

程序员文章站 2024-03-14 18:36:11
...

文章目录

入门

  1. 型变(Variant)有三种基本形态

    • 协变(Covariant): string->object (子类到父类的转换)
    • 逆变(Contravariant):object->string (父类到子类的转换)
    • 不变(Invariant):基于上面两种情况,不可变
  2. 对于简单的泛型类、泛型函数与java差不多。Kotlin泛型的是型变支持,kotlin提供了【声明处型变】和【使用处型变】,java只支持【使用处型变】

  3. 代码

    fun main() {
    
        //无法推断,编译错误
        //var parent0 = Parent()
        var parent0 = Parent<String>()
        println(parent0.info) //null
    
        val parent1 = Parent<String>("苹果")
        println(parent1.info)//苹果
    
        //显示指定类型
        var parent2: Parent<Int> = Parent<Int>(3)
        println(parent2.info)//3
    
        //可省略"菱形语法",自动推断处Apple<Double>
        var parent3 = Parent(3.5)
        println(parent3.info)//3.5
    
        val child = Child()
        println(child.info)//child
    
    }
    
    /**
     * 定义泛型类
     */
    open class Parent<T> {
        open var info: T?
    
        constructor() {
            info = null
        }
    
        constructor(info: T) {
            this.info = info
        }
    }
    
    open class Child : Parent<String> {
    
        constructor() {
            info = "child"
        }
    
        //重写构造器
        constructor(info: String) {
            this.info = info
        }
    }
    

型变

  1. java的泛型是不支持型变的,java采用通配符来解决这个问题。即在java中List<String>并不是List<Object> 的子类,因此不能将List<String>赋值给List<Object>

  2. 如果不支持型变,以下的numSetSet<Number>,则addAll传入的参数也必须是Set<Number>,而Set<Integer> 是无法传入的。但是其实是安全的,java中采用通配符的方式来解决这个问题

    1. 如果addAll定义为这样
    public interface Collection<E> extends Iterable<E> {
        boolean addAll(Collection<E> c)
    }    
    Set<Number> numSet = new HashSet<>();
    Set<Integer> intSet = new HashSet<>();
    numSet.addAll(intSet)
    
    2. java中采用通配符(通配符上限)的方式来支持型变 
    public interface Collection<E> extends Iterable<E> {
        boolean addAll(Collection<? extends E> c)
    }      
    
  3. 泛型存在如下规律

    • 通配符上限(泛型协变)意味着从中取出(out)对象是安全的,但传入对象(in)则是不可靠
    • 通配符下限(泛型逆变)意味着向其中传入(in)对象是安全的,但取出对象(out)则不可靠
    • kotlin利用以上两条规律,抛弃了泛型通配符语法,而是利用in、out来让泛型支持型变
  4. 协变

    fun main() {
        var user = User<String>("jannal")
        println(user.info)
        //因为T支持协变,所以 User<String>可以当作 User<Any>用
        var user1: User<Any> = user
        println(user1.info)
    }
    
    class User<out T> {
        val info: T
    
        constructor(info: T) {
            this.info = info
        }
    
        fun info(): T {
            println(this.info)
            return info
        }
    }
    
    
  5. 逆变: T只能出现在方法的形参声明中,不能出现在方法的返回值声明中。一旦声明了逆变,可以安全的将Person<Any>Person<CharSequence>赋值给Person<String>,只要尖括号中的类型是String的父类即可。

    fun main() {
        var person = Person<Any>()
        person.info(111)
        var person2: Person<String> = person
        person2.info("jannal")
    }
    
    class Person<in T> {
        fun info(t: T) {
            println(t)
        }
    }
    
  6. 如果一个类中有的方法使用泛型声明返回类型,有的方法使用泛型声明形参类型,那么该类就不能使用声明处型变。