Kotlin 泛型 | 01. 基础

一、泛型

1.1 含义

        参数化类型,用尖括号这种方式表示,如<T>、<E>、<?>等。比如:方法的参数一般指定具体类型,如果把参数的类型也参数化,那这就是泛型本尊了。

interface List<out E> : Collection<E> {

    override fun contains(element: @UnsafeVariance E): Boolean

}

        总的来说,泛型本质就是参数化类型,这种类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

1.2 好处

        让类型更加安全。

  1. 编译时类型检查。将错误暴露在编译期,不用等到运行时(防止在运行时出现 ClassCastException)。
  2. 运行时自动类型转换。不用类型强制转换的烦琐操作。
  3. 更加语义化(比如:List<String>清楚知道存储的是 String 对象)和能写出更加通用化的代码(引入泛型后并未增加代码的冗余性)。

1.3 类型约束

        泛型本身就有类型约束的作用,那么这里的类型约束实际上指的是一种缩小范围的约束。

目标:理解3个问题——上界约束是什么?类型是否为空?多个条件约束怎么办?

1.3.1 上界约束

        最常见的约束就是与 Java 的 extends 关键字对应的上界约束,如:class FruitPlate<T : Fruit>(val t: T)。

        冒号之后指定的类型 Fruit 是上界:只有 Fruit 的子类型可以替代 T。

class Demo {

    class Noodles(weight: Double) // 面条

    open class Fruit(val weight: Double) // 水果

    class Apple(weight: Double) : Fruit(weight) // 苹果

    class Banana(weight: Double) : Fruit(weight) // 香蕉

    class FruitPlate<T : Fruit>(val t: T) // 上界约束

    fun demo() {

        FruitPlate(Apple(1.6))

        FruitPlate(Banana(1.6))

        FruitPlate(Noodles(1.6)) // 报错

    }

}

        如果没有声明,默认的上界是 Any?。声明上界后也可以使用 ? 手动指定可空。

class Demo {

    open class Fruit(val weight: Double) // 水果

    class FruitPlate<T : Fruit?>(val t: T) // 上界约束

    fun demo() {

        FruitPlate(null// 正确

    }

}

 

        在尖括号中只能指定一个上界。 如果同一类型参数需要多个上界,那么需要一个单独的 where 子句,如:fun <T> cut(t: T) where T : Fruit, T : Ground {}

class Demo {

    interface Ground // 土地

    class Noodles(weight: Double) // 面条

    open class Fruit(val weight: Double) // 水果

    class Banana(weight: Double) : Fruit(weight) // 香蕉

    class Watermelon(weight: Double) : Fruit(weight), Ground // 西瓜:长在土地的水果

    fun <T> cut(t: T) where T : Fruit, T : Ground {}

    fun demo() {

        cut(Watermelon(1.6)) // 所传递的类型 T 必须同时满足 where 子句的所有条件

        cut(Noodles(1.6)) // 报错

        cut(Banana(1.6)) // 报错

    }

}

        多个上界助记语:where关键字,逗号分隔开。

二、泛型的背后:类型擦除

2.1 泛型与数组对比

2.1.1 Java 中泛型与数组对比

        泛型:Java 泛型是不变的(Fruit 是 Apple 的父类,List<Fruit> 不是 List<Apple> 的父类),是类型擦除的,可以看做伪泛型,无法在程序运行时获取到一个对象的具体类型。保证类型安全。

        数组:Java 数组是协变的(Fruit 是 Apple 的父类,Fruit[] 是 Apple[] 的父类),在程序运行时可以获取自身的类型。保证类型安全。

public class JavaDemo {

    Apple[] appleArray = new Apple[10];

    Fruit[] fruitArray = appleArray; // 允许

    List<Apple> appleList = new ArrayList<>();

    List<Fruit> fruitList = appleList; // 不允许

    public static class Apple implements Fruit { }

    public interface Fruit { }

}

2.1.2 Kotlin 中泛型与数组对比

        泛型:kotlin 中的泛型机制和Java一样

        数组:kotlin 数组支持泛型,不再协变(Java 数组不支持泛型,支持协变)

class Demo {

    open class Fruit(val weight: Double) // 水果

    class Apple(weight: Double) : Fruit(weight) // 苹果

    private val appleArray = arrayOfNulls<Apple>(5)

    val anyArray: Array<Fruit?> = appleArray // 不允许

}

2.2 小思考

1、Java 为什么使用类型擦除来实现泛型,并且怎么满足泛型应该具有的特性(类型检查、类型自动转换)?

        Tips:向后兼容,强制类型转换

2、为什么 Java 中数组不支持泛型?

        Tips:不再保证类型安全。数组内的元素必须是“物化”的,因为类型被擦除后,虚拟机建立数组时不知道类型,而虚拟机要求数组建立时必须明确类型和长度。

        反证法:假如给数组加上泛型后,将无法满足数组协变的原则,因为在运行时无法知道数组的类型。

3、类型擦除后,Kotlin 如何在运行时知道泛型参数的类型?(其实并不是真的将全部的类型信息都擦除,还是会将类型信息放在对应 class 的常量池中)

        Tips:匿名内部类、内联函数 reified

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值