SelfBounded的神秘
在《Java编程思想》中关于泛型的讲解中,提到了自限定类型:
class SelfBounded<T extends SelfBounded<T>>{}
作者对其的定义是 “我在创建一个新类,它继承自一个泛型类型,这个泛型类型接收自己的类名作为参数”。
是不是一头雾水?鉴于《Java编程思想》的理解层次和深度,相信大多数读者都不能一窥其究竟。JAVA为什么要有这个东西?它解决了啥问题?等等诸如此类的。
下面我会基于我自己的理解,采用一步一步递进的讲解方式。来说明这个问题,当然基于笔者知识水平的限制,如果有理解错误和含糊不清的地方,希望大家不吝指教。
到底什么是自限定?
在某些场景之下,我们需要编写一个类(基类),在这个基类中编写若干方法,这些方法的参数或者是返回值都要求是这个类本身;而且当有子类继承这个基类之后,这些方法的参数或者是返回值都要随着子类的变化而变化。
现在通过上面的描述,我们大概理解了什么是自限定;我们可以发现,自限定是站在继承的场景之下来讨论问题的。
然后我们通过最原始的方式来实现这个需求:
public class GenericDemo013 {
public static void main(String[] args) {
Derived base = new Derived();
base.set(new Derived());
System.out.println(base.get().getClass().getSimpleName());
}
}
class Base{
Base t;
public void set(Base t){
this.t = t;
}
public Base get(){
return t;
}
}
class Derived extends Base{
Derived t;
//重载了set(子类的set还存在)
public void set(Derived t){
this.t = t;
}
//重写了get(子类的get不存在)
public Derived get(){
return t;
}
}
从表面上看,我们似乎采用上面的方式完美的解决了这个需求。稍加思索就会发现这种写法完全无法体现"继承"的优势,而且代码量也似乎比较臃肿。
下面来看一种比较优雅的实现方法:
package com.java.basic.generic;
public class GenericDemo013 {
public static void main(String[] args) {
Derived base = new Derived();
base.set(new Derived());
//输出是Derived
System.out.println(base.get().getClass().getSimpleName());
}
}
class Base<T>{
T t;
public void set(T t){
this.t = t;
}
public T get(){
return t;
}
}
class Derived extends Base<Derived>{ }
哇,相当nice。这里充分使用到了继承的优势。且在子类中不用重写任何方法;可以说是相当完美了。但是我们要知道的是,Base类没有做任何限制,所以它也可以这样继承:
class Derived extends Base<String>{ }
很明显,这样的话,set(T t) 和 T get() 方法中的T就变成了String,而不是子类本身。这违背了自限定的原则。
正真的自限定:
package com.java.basic.generic;
public class GenericDemo013 {
public static void main(String[] args) {
Derived base = new Derived();
base.set(new Derived());
//输出是Derived
System.out.println(base.get().getClass().getSimpleName());
}
}
class Base<T extends Base<T>>{
T t;
public void set(T t){
this.t = t;
}
public T get(){
return t;
}
}
class Derived extends Base<Derived>{ }
class Derived1 extends Base<String>{ } //编译错误
OK,这里不在过多解释,一个简单的限定而已。
自限定(SelfBounded)的价值
综上所述,我们可以看到自限定类型的价值在于可以产生协变参数类型——方法参数类型会随着子类的变化而变化。
参考
<<java编程思想(第四版)>>