简介
java泛型里会有class SelfBounded> { }这种写法,泛型类有一个类型参数T,但这个T有边界SelfBounded。这边界里有两个疑问:
SelfBounded已经在左边出现,但SelfBounded类还没定义完这里就用了;
同样,T也在左边出现过了,这是该泛型类的类型参数标识符。
这两点确实挺让人疑惑,思考这个类定义时容易陷入“死循环”。
注意,自限定的要点实际就是这两个“疑问”。
普通泛型类——构成自限定
class BasicHolder {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
class Subtype extends BasicHolder {}
public class CRGWithBasicHolder {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();
st1.set(st2);
st2.set(st3);
Subtype st4 = st1.get().get();
st1.f();
}
} /* Output:
Subtype
*///:~
BasicHolder泛型类的类型参数并没有什么边界,在继承它的时候,你本可以class A extends BasicHolder {}这样普普通通的用。
但class Subtype extends BasicHolder {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。
虽然class Subtype extends BasicHolder {}这样用,看起来是类定义还没有结束,就把自己的名字用到了边界的泛型类的类型参数。虽然感觉稍微有点不合理,但这里就强行理解一下吧。自限定的用法:父类作为一个泛型类或泛型接口,用子类的名字作为其类型参数。
以上两点,就解释了简介里的第二个“疑问”。正因为class Subtype extends BasicHolder这样用可以让Subtype对象只允许和Subtype对象交互,这里再把Subtype抽象成类型参数T,不就刚好变成了T extends SelfBounded这样的写法。
在主函数里,根据自限定的重要作用,且由于BasicHolder泛型类有个成员变量和set方法,所以st1.set(st2); st2.set(st3);可以像链表一样,节点的后继指向一个节点,后者又可以指向另外的节点。
自限定类型的泛型类
下面是自限定类型的标准用法。
class SelfBounded> {//自限定类型的标准用法
//所有
T element;
SelfBounded set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A extends SelfBounded {}
public class SelfBounding {
public static void main(String[] args) {
A a = new A();//a变量只能与A类型变量交互,这就是自限定的妙处
SelfBounded b = new SelfBounded();
}
} ///:~
抛开自限定类型的知识点,观察SelfBounded泛型类,发现该泛型类的类型参数T有SelfBounded的边界要求。根据上个章节的讲解,一个普通的泛型类我们都可以继承它来做到自限定,且因为要使用SelfBounded泛型类之前,我们必须有一个实际类型能符合SelfBounded的边界要求,所以这里就模仿上一章,创建一个新类来符合这个边界,即class A extends SelfBounded {},这样新类A便符合了SelfBounded的边界。
这时你觉得终于可以使用SelfBounded泛型类了,于是你便SelfBounded b = new SelfBounded();,但是这个b变量本身的类型是SelfBounded,成员函数的形参或返回值的类型却是A,这个效果看起来不是我们想要的自限定的效果。(b变量不可以和别的SelfBounded对象交互,因为它继承来的成员函数的类型限定是A,这样把别的SelfBounded对象传给成员函数会造成ClassCastException,这属于父类对象传给子类引用,肯定不可以。所以说没有达到自限定。)
其实,这里是我们多此一举了,新类class A extends SelfBounded {}创建的时候就已经一举三得了。1.出现SelfBounded尖括号里面的A需要满足边界SelfBounded,它自己的类定义已经满足了。2.给了SelfBounded泛型类的定义是为了使用它,新类A的对象也能使用到它,只不过这里是继承使用。3.根据上一章的讲解,新类A的类定义形成了自限定。
可能一般我们以为要使用SelfBounded泛型类要有两步(1.创建新类型以符合边界 2.以刚创建的新类型的名字来创建SelfBounded泛型类对象),但由于class SelfBounded>类定义中,SelfBounded作为了自己的泛型类型参数的边界,这样,想创建一个新类作为T类型参数以符合边界时,这个新类就必须继承到SelfBounded的所有成员(这也是我们想要的效果)。所以就可以class A extends SelfBounded {}这样一步到位。这也解释了简介里的第一个“疑问”。
对了,对于第一个疑问,你可能想看一下,如果边界里的泛型类不是自己,会是什么情况:
class testSelf {
//假设这里也有一些成员变量,成员方法
}
class SelfBounded> {//类型参数的边界不是自己的名字SelfBounded
T element;
SelfBounded set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class testA extends testSelf {}//这个新类可作为SelfBounded的类型参数T,因为符合了边界
public class SelfBounding {
public static void main(String[] args) {
SelfBounded a = new SelfBounded();
}
} ///:~
按照一般使用SelfBounded泛型类的两个步骤,首先需要创建新类class testA extends testSelf {}来符合边界,然后新类型作为类型参数使用来创建SelfBounded对象,即SelfBounded a = new SelfBounded()。但a变量的效果却不是我们想要的自限定的效果,总之看起来很奇怪。
一旦你把class SelfBounded>的边界改成>,那么新类testA,那么它的定义就应该是class testA extends SelfBounded {},然后正因为testA继承了SelfBounded,所以testA就获得了父类SelfBounded的成员方法且这些成员方法的形参或返回值都是testA。
通过这个反例便进一步解释了简介的第一个“疑问”。
对本章第一个例子作进一步的拓展吧:
class SelfBounded> {
T element;
SelfBounded set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A extends SelfBounded {}
class B extends SelfBounded {} // Also OK
class C extends SelfBounded {
C setAndGet(C arg) { set(arg); return get(); }
}
class D {}
// Can't do this:
// class E extends SelfBounded {}
// Compile error: Type parameter D is not within its bound
// Alas, you can do this, so you can't force the idiom:
class F extends SelfBounded {}
public class SelfBounding {
public static void main(String[] args) {
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();//最终a是null
C c = new C();
c = c.setAndGet(new C());//c换成这行新new出来的C对象了
}
} ///:~
class B extends SelfBounded这样使用也是可以的,毕竟继承SelfBounded时,给定的具体类型A确实满足了边界。不过B对象没有自限定的效果了。
class C extends SelfBounded展示了:在自己新增的成员方法里,去调用继承来的成员方法。注意,继承来的方法被限定类型为C即自身了,这就是自限定的效果。
class E extends SelfBounded无法通过编译,因为给定的具体类型A不符合边界。
class F extends SelfBounded,你可以继承原生类型,此时T会作为它的上限SelfBounded(边界)来执行。如下图:
也可以将自限定用于泛型方法:
//借用之前定义好的SelfBounded
class testNoBoundary {}//我自己新加的
public class SelfBoundingMethods {
static > T f(T arg) {
return arg.set(arg).get();
}
static > T f1(T arg) {
return arg;
}
public static void main(String[] args) {
A a = f(new A());
class selfBound extends testNoBoundary {}
selfBound b = f1(new selfBound());
}
} ///:~
f静态方法要求T自限定,且边界是SelfBounded。那么之前定义的A类型就符合要求了。
我加了个f1静态方法,它也要求T自限定,且边界是testNoBoundary。注意testNoBoundary泛型类对类型参数T没有边界要求。class selfBound extends testNoBoundary {}这里用了局部内部类创建了一个符合边界要求的新类型。
JDK源码里自限定的应用——enum
java中使用enum关键字来创建枚举类,实际创建出来的枚举类都继承了java.lang.Enum。也正因为这样,所以enum不能再继承别的类了。其实enum就是java的一个语法糖,编译器在背后帮我们继承了java.lang.Enum。
下面就是一个枚举类的使用:
public enum WeekDay {
Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri( "Friday"), Sat("Saturday"), Sun("Sunday");
private final String day;
private WeekDay(String day) {
this.day = day;
}
public static void printDay(int i){
switch(i){
case 1: System.out.println(WeekDay.Mon); break;
case 2: System.out.println(WeekDay.Tue);break;
case 3: System.out.println(WeekDay.Wed);break;
case 4: System.out.println(WeekDay.Thu);break;
case 5: System.out.println(WeekDay.Fri);break;
case 6: System.out.println(WeekDay.Sat);break;
case 7: System.out.println(WeekDay.Sun);break;
default:System.out.println("wrong number!");
}
}
public String getDay() {
return day;
}
public static void main(String[] args) {
WeekDay a = WeekDay.Mon;
}
}
发现通过idea看WeekDay.class文件时看不出继承java.lang.Enum的。只有通过javap命令才能看出来。先看一下java.lang.Enum的定义,Enum>是自限定类型的标准写法:
public abstract class Enum> implements Comparable, Serializable { }
截取部分汇编来看:
public final class WeekDay extends java.lang.Enum {
public static final WeekDay Mon;
public static final WeekDay Tue;
public static final WeekDay Wed;
public static final WeekDay Thu;
public static final WeekDay Fri;
public static final WeekDay Sat;
public static final WeekDay Sun;
public static WeekDay[] values();
public static WeekDay valueOf(java.lang.String);
发现确实WeekDay做到了自限定,因为继承来的成员和方法的类型都被限定成WeekDay它自己了。
分析一下java.lang.Enum这么设计的好处:
Enum作为一个抽象类,我们使用enum关键字创建出来的枚举类实际都是Enum的子类,因为class Enum>的类定义是这种标准的自限定类型,所以编译器直接生成的类必须是WeekDay extends java.lang.Enum(即本文中讲的:需先创建一个符合边界条件的实际类型,但创建的同时又继承Enum本身,所以就一步到位了)。
正因为编译器生成的枚举类都是Enum的子类,结合上条分析,每种Enum子类的自限定类型都是Enum子类自身。这样WeekDay的实例就只能和WeekDay的实例交互(星期几和星期几比较),Month的实例就只能和Month的实例交互(月份和月份比较)。
JDK源码里自限定的应用——Integer
Integer的类定义是:
public interface Comparable {
public int compareTo(T o);
}
public final class Integer extends Number implements Comparable {//省略}
可以看到Integer实现了Comparable,这也是自限定,这样,从Comparable接口继承来的compareTo方法的形参类型就是Integer它自己了。和章节《普通泛型类——构成自限定》里的例子一样。
但接口Comparable的定义可没要求类型参数T必须自限定啊,它甚至连T的边界都没有,当然,这样的好处就是把决定权交给了Comparable的使用者,当使用者想要自限定时,就按照自限定的写法创建新类就好了。