【泛型】T extends Comparable<? super T>

背景

    看跳表的实现代码1时看到T extends Comparable<? super T>,不太理解其含义。

理解

    参考知乎2并自己测试后比较理解了。
    java中向上转型是转为父类,向下转型是转为子类。extends确定了类型的上限,super确定了类型的下限。
    T extends Comparable<? super T>的作用是什么呢?知乎这篇通过Demo<GregorianCalendar>的例子讲的很清楚了:

  • Demo<T extends Comparable<? super T>>为类型Demo的泛型声明时,其泛型可以接收GregorianCalendar
  • Demo<T extends Comparable<T>>为类型Demo的泛型声明时,则不可以接收GregorianCalendar作为泛型。原因是GregorianCalendar并未直接实现Comparable接口,其父类Calendar实现了Comparable<Calendar>接口,在其equals方法中通过调用super.compareTo(xx)方法来使用父类的compareTo。

    结论,通过将泛型类型声明为T extends Comparable<? super T>,可以接收真正实现了接口的父类,以及继承了父类的子类。因为子类可以自动向上转型。

额外收获

    做实验的两个类Animal、Cat是之前用于测试静态方法和实例方法会使用父类还是子类时创建的,只用再创建个Demo泛型类即可。
    之前的多态实验,有如下结论:

  • 子类实例赋值给声明为父类的变量时,调用静态方法调用的是父类的,调用实例方法调用的是子类的。
  • 将上面的变量强转为子类型并调用静态方法,调用的是子类的。

    突发其想,如果让子类也直接实现接口会怎样呢?
    实验一:

  1. Animal implements Comparable<Animal>
  2. Cat extends Animal implements Comparable<Cat>报错,Comparable的泛型不同,不能被继承。

    实验二:

  1. Animal implements Comparable<? super Animal>报no wildcard expected。

    实验一说明了子类实现父类已实现的带泛型的接口时,不能改变泛型类型。实验二说明了被实现的接口的泛型不能有通配符。这两个实验所说明的约束与Demo<T extends Comparable<? super T>>相配合,使得若T要使用compareTo,使用的就是其父类的。
    实验三:

  1. Animal implements Comparable<Animal>
  2. Cat implements Comparable<Animal>,重写的compareTo方法将入参强转为Cat再处理。
  3. Demo<T extends Comparable<? super T>>,有一个TreeSet属性。
  4. main方法中创建一个Animal实例并add到demo的TreeSet。(demo是Demo<Animal>的实例)
  5. main方法中创建一个Cat实例并add到demo的TreeSet。
  6. 打印demo的TreeSet

    结果报类型转换错误。但若注释掉4或5两者中的一个,又运行正常。跟踪异常栈对应的源码,可了解到TreeSet底层默认使用的TreeMap,在存储一个新值时,会拿新值.compareTo(rootKey)。由之前列出的实例方法调用的是真正的类型的,这里调用的就是Cat的,由于我重写的compareTo方法会先进行强转,但由于rootKey是之前存储的Animal类型,无法强转为Cat类型,所以报错。
    确有必要重写时,避免在子类覆盖的方法里进行父类转子类的操作,可能会转换报错。有必要重写的原因,可能是要使用覆盖的属性,不重写则父类里才有该方法,父类方法里使用的是父类自己定义的未覆盖的属性,虽然直接通过子类.属性名的方式能得到覆盖后的属性,但父类方法使用this是指向父类的,导致拿到的是父类本身定义的属性。代替重写方法的方案有,采用comparator对属性进行比较,属性可被子类重写。最简便的,不覆盖父类属性,直接需要时设置其值,再使用comparator对属性进行比较。
    TreeSet、NavigableMap、TreeMap这些java api还要有时间去读源码,数据结构还要去读各种经典书籍以了解红黑树、B+树等。
    实验四:
    受我的文章【泛型】自限定的类型启发。

  1. public class Animal<T extends Animal> implements Comparable
  2. public class Cat extends Animal<Cat>并重写Animal的age域,Animal和Cat均设置了@Getter、@Setter、@AllArgsConstructor、@NoArgsConstructor
  3. Animal的compareTo方法体为this.age-t.age时
  4. main方法打印animal.compareTo(cat)
        打印效果为cat的age取的父类值。因为animal协变的泛型为Animal,cat自动向上转型后取得的age为父类的。可参考Java子类父类属性的覆盖。将步骤3改为this.age-t.getAge()则取的是子类的,原因是实例方法与变量类型无关,与实际类型有关。将步骤4改为cat.compareTo(animal)报错,因为animal转不了cat,强转也不行。所以协变的方法所属类与其方法入参类型应该针对同类型去用,不应多种类型混用。

  1. https://www.jianshu.com/p/04c79b131e3e
    跳跃表原理及java实现跳跃表(原理讲的并不清楚,但跳表与其它数据结构相比的优点讲的很好,具体原理请参考百度百科或知乎"跳表") ↩︎

  2. https://www.zhihu.com/question/25548135 ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值