簡介
java泛型里會有class SelfBounded<T extends SelfBounded<T>> { }
這種寫法,泛型類有一個類型參數T,但這個T有邊界SelfBounded<T>
。這邊界里有兩個疑問:
- SelfBounded已經在左邊出現,但SelfBounded類還沒定義完這里就用了;
- 同樣,T也在左邊出現過了,這是該泛型類的類型參數標識符。
這兩點確實挺讓人疑惑,思考這個類定義時容易陷入“死循環”。
注意,自限定的要點實際就是這兩個“疑問”。
普通泛型類——構成自限定
class BasicHolder<T> {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
class Subtype extends BasicHolder<Subtype> {}
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<T>
泛型類的類型參數並沒有什么邊界,在繼承它的時候,你本可以class A extends BasicHolder<B> {}
這樣普普通通的用。- 但
class Subtype extends BasicHolder<Subtype> {}
這樣用,就構成自限定了。從定義上來說,它繼承的父類的類型參數是它自己。從使用上來說,Subtype
對象本身的類型是Subtype
,且Subtype
對象繼承而來的成員(element)、方法的形參(set方法)、方法的返回值(get方法)也是Subtype
了(這就是自限定的重要作用)。這樣Subtype
對象就只允許和Subtype
對象(而不是別的類型的對象)交互了。 - 雖然
class Subtype extends BasicHolder<Subtype> {}
這樣用,看起來是類定義還沒有結束,就把自己的名字用到了邊界的泛型類的類型參數。雖然感覺稍微有點不合理,但這里就強行理解一下吧。自限定的用法:父類作為一個泛型類或泛型接口,用子類的名字作為其類型參數。 - 以上兩點,就解釋了簡介里的第二個“疑問”。正因為
class Subtype extends BasicHolder<Subtype>
這樣用可以讓Subtype
對象只允許和Subtype
對象交互,這里再把Subtype
抽象成類型參數T,不就剛好變成了T extends SelfBounded<T>
這樣的寫法。 - 在主函數里,根據自限定的重要作用,且由於
BasicHolder<T>
泛型類有個成員變量和set方法,所以st1.set(st2); st2.set(st3);
可以像鏈表一樣,節點的后繼指向一個節點,后者又可以指向另外的節點。
自限定類型的泛型類
下面是自限定類型的標准用法。
class SelfBounded<T extends SelfBounded<T>> {//自限定類型的標准用法
//所有
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A extends SelfBounded<A> {}
public class SelfBounding {
public static void main(String[] args) {
A a = new A();//a變量只能與A類型變量交互,這就是自限定的妙處
SelfBounded<A> b = new SelfBounded<A>();
}
} ///:~
- 拋開自限定類型的知識點,觀察SelfBounded泛型類,發現該泛型類的類型參數T有
SelfBounded<T>
的邊界要求。根據上個章節的講解,一個普通的泛型類我們都可以繼承它來做到自限定,且因為要使用SelfBounded泛型類之前,我們必須有一個實際類型能符合SelfBounded<T>
的邊界要求,所以這里就模仿上一章,創建一個新類來符合這個邊界,即class A extends SelfBounded<A> {}
,這樣新類A
便符合了SelfBounded<T>
的邊界。 - 這時你覺得終於可以使用SelfBounded泛型類了,於是你便
SelfBounded<A> b = new SelfBounded<A>();
,但是這個b變量本身的類型是SelfBounded<A>
,成員函數的形參或返回值的類型卻是A
,這個效果看起來不是我們想要的自限定的效果。(b變量不可以和別的SelfBounded<A>
對象交互,因為它繼承來的成員函數的類型限定是A
,這樣把別的SelfBounded<A>
對象傳給成員函數會造成ClassCastException,這屬於父類對象傳給子類引用,肯定不可以。所以說沒有達到自限定。) - 其實,這里是我們多此一舉了,新類
class A extends SelfBounded<A> {}
創建的時候就已經一舉三得了。1.出現SelfBounded尖括號里面的A
需要滿足邊界SelfBounded<T>
,它自己的類定義已經滿足了。2.給了SelfBounded泛型類的定義是為了使用它,新類A
的對象也能使用到它,只不過這里是繼承使用。3.根據上一章的講解,新類A
的類定義形成了自限定。 - 可能一般我們以為要使用SelfBounded泛型類要有兩步(1.創建新類型以符合邊界 2.以剛創建的新類型的名字來創建SelfBounded泛型類對象),但由於
class SelfBounded<T extends SelfBounded<T>>
類定義中,SelfBounded作為了自己的泛型類型參數的邊界,這樣,想創建一個新類作為T類型參數以符合邊界時,這個新類就必須繼承到SelfBounded的所有成員(這也是我們想要的效果)。所以就可以class A extends SelfBounded<A> {}
這樣一步到位。這也解釋了簡介里的第一個“疑問”。
對了,對於第一個疑問,你可能想看一下,如果邊界里的泛型類不是自己,會是什么情況:
class testSelf<T> {
//假設這里也有一些成員變量,成員方法
}
class SelfBounded<T extends testSelf<T>> {//類型參數的邊界不是自己的名字SelfBounded
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class testA extends testSelf<testA> {}//這個新類可作為SelfBounded的類型參數T,因為符合了邊界
public class SelfBounding {
public static void main(String[] args) {
SelfBounded<testA> a = new SelfBounded<testA>();
}
} ///:~
按照一般使用SelfBounded泛型類的兩個步驟,首先需要創建新類class testA extends testSelf<testA> {}
來符合邊界,然后新類型作為類型參數使用來創建SelfBounded對象,即SelfBounded<testA> a = new SelfBounded<testA>()
。但a變量的效果卻不是我們想要的自限定的效果,總之看起來很奇怪。
一旦你把class SelfBounded<T extends testSelf<T>>
的邊界改成<T extends SelfBounded<T>>
,那么新類testA,那么它的定義就應該是class testA extends SelfBounded<testA> {}
,然后正因為testA繼承了SelfBounded<testA>
,所以testA就獲得了父類SelfBounded的成員方法且這些成員方法的形參或返回值都是testA。
通過這個反例便進一步解釋了簡介的第一個“疑問”。
對本章第一個例子作進一步的拓展吧:
class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // Also OK
class C extends SelfBounded<C> {
C setAndGet(C arg) { set(arg); return get(); }
}
class D {}
// Can't do this:
// class E extends SelfBounded<D> {}
// 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<A>
這樣使用也是可以的,畢竟繼承SelfBounded時,給定的具體類型A確實滿足了邊界。不過B對象沒有自限定的效果了。class C extends SelfBounded<C>
展示了:在自己新增的成員方法里,去調用繼承來的成員方法。注意,繼承來的方法被限定類型為C即自身了,這就是自限定的效果。class E extends SelfBounded<D>
無法通過編譯,因為給定的具體類型A不符合邊界。class F extends SelfBounded
,你可以繼承原生類型,此時T會作為它的上限SelfBounded(邊界)來執行。如下圖:
也可以將自限定用於泛型方法:
//借用之前定義好的SelfBounded
class testNoBoundary<T> {}//我自己新加的
public class SelfBoundingMethods {
static <T extends SelfBounded<T>> T f(T arg) {
return arg.set(arg).get();
}
static <T extends testNoBoundary<T>> T f1(T arg) {
return arg;
}
public static void main(String[] args) {
A a = f(new A());
class selfBound extends testNoBoundary<selfBound> {}
selfBound b = f1(new selfBound());
}
} ///:~
f
靜態方法要求T自限定,且邊界是SelfBounded。那么之前定義的A類型就符合要求了。- 我加了個
f1
靜態方法,它也要求T自限定,且邊界是testNoBoundary。注意testNoBoundary泛型類對類型參數T沒有邊界要求。class selfBound extends testNoBoundary<selfBound> {}
這里用了局部內部類創建了一個符合邊界要求的新類型。
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<E extends Enum<E>>
是自限定類型的標准寫法:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { }
截取部分匯編來看:
public final class WeekDay extends java.lang.Enum<WeekDay> {
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<E extends Enum<E>>
的類定義是這種標准的自限定類型,所以編譯器直接生成的類必須是WeekDay extends java.lang.Enum<WeekDay>
(即本文中講的:需先創建一個符合邊界條件的實際類型,但創建的同時又繼承Enum
本身,所以就一步到位了)。- 正因為編譯器生成的枚舉類都是
Enum
的子類,結合上條分析,每種Enum
子類的自限定類型都是Enum
子類自身。這樣WeekDay的實例就只能和WeekDay的實例交互(星期幾和星期幾比較),Month的實例就只能和Month的實例交互(月份和月份比較)。
JDK源碼里自限定的應用——Integer
Integer
的類定義是:
public interface Comparable<T> {
public int compareTo(T o);
}
public final class Integer extends Number implements Comparable<Integer> {//省略}
可以看到Integer
實現了Comparable<Integer>
,這也是自限定,這樣,從Comparable接口繼承來的compareTo方法的形參類型就是Integer
它自己了。和章節《普通泛型類——構成自限定》里的例子一樣。
但接口Comparable的定義可沒要求類型參數T必須自限定啊,它甚至連T的邊界都沒有,當然,這樣的好處就是把決定權交給了Comparable的使用者,當使用者想要自限定時,就按照自限定的寫法創建新類就好了。