[譯者按:]
Bruce Eckel在前不久寫了一片批判Java的泛型的文章,結(jié)合他在OO上浸淫多年的功力,一眼就看出了Java的泛型和其他例如C++,Python,Ruby等等這些語言的泛型的差別。
不過平心而論,Bruce Eckel的批評是比較中肯的,因?yàn)樗部吹搅薐ava和其他這些面向?qū)ο蟮恼Z言之間的差別,他也可以理解Java實(shí)現(xiàn)出了這樣的泛型。
另外,如果大家不知道Ruby是什么,可以參考下面的網(wǎng)站:http://www.ruby-lang.org/en/
[補(bǔ)充]添加一段Bruce Eckel自己的評論:
Guess what. I really don't care. You want to call it "generics," fine, implement something that looks like C++ or Ada, that actually produces a latent typing mechanism like they do. But don't implement something whose sole purpose is to solve the casting problem in containers, and then insist that on calling it "Generics." Of course, Java has long precedence in arrogantly mangling well- accepted meanings for things: one that particularly stuck in my craw was the use of "design pattern" to describe getters and setters. In JDK 1.4 we were told some lame way to use assertions which was a backward justification for them being disabled by default. JDK 1.4 also had to invent its own inferior logging system rather than using the openly created, well-tested and well-liked Log4J. And we've also been told many times about how Java has been as fast or faster than C++, or about how one or another feature is great and flawless. I point to the threads implementation which has had major changes quietly made from version to version with not so much as a peep of apology or admission that "hey, we really screwed up here." Or maybe I was just not on that particular announcement list.
昨晚,我作為嘉賓被Silicon Valley的模式組邀請去參加他們的一個(gè)研討會,并且讓我來決定討論的主題,為了更好的了解JDK1.5,我選擇了Java的Generics(泛型),最后討論的結(jié)果是我們大家都有點(diǎn)震驚。我們討論的主要素材是最新的Sun推出的Java 泛型手冊。我對“參數(shù)化類型”的經(jīng)驗(yàn)來自于C++,而C++的泛型又是基于ADA的Generics,事實(shí)上,Lisp語言是第一個(gè)實(shí)現(xiàn)了泛型的語言,有人說Simula語言也很早就有泛型了。在這些語言中,當(dāng)你使用參數(shù)化的類型的時(shí)候,這個(gè)參數(shù)是作為一種隱式類型(latent type)的:一種被實(shí)現(xiàn)出了如何使用,但是卻沒有顯式的聲明的類型。也就是說,隱式類型是一種通過你調(diào)用方法來實(shí)現(xiàn)的。例如,你的模板方法是某個(gè)類型中的f()和g(),那么接下來你實(shí)現(xiàn)了一個(gè)類型包含了f()和g()這兩個(gè)方法,而事實(shí)上這個(gè)類型可能從來被定義過。舉個(gè)例子,在Python中,你可以這樣做:
def speak(anything):
anything.talk()
注意到,這里對anything沒有任何的類型限制,只是一個(gè)標(biāo)識而已,假設(shè)這個(gè)類型能做一個(gè)叫做speak()的操作,就象實(shí)現(xiàn)了一個(gè)Interface一樣,但是事實(shí)上你根本不需要去真的實(shí)現(xiàn)這樣一個(gè)Interface,所以叫做隱式。現(xiàn)在我可以實(shí)現(xiàn)這樣一個(gè)“狗狗和機(jī)器人”的例子: class Dog:
def talk(self): print "Arf!"
def reproduce(self): pass
class Robot:
def talk(self): print "Click!"
def oilChange(self): pass
a = Dog()
b = Robot()
speak(a)
speak(b)
Speak()方法不關(guān)心是否有參數(shù)傳入,所以我可以傳給它任何的東西,就象我傳入的對象中支持的talk()方法一樣。我相信在Ruby語言中的實(shí)現(xiàn)是和Python一致的。在C++中你可以做相同的事情: class Dog {
public:
void talk() { }
void reproduce() { }
};
class Robot {
public:
void talk() { }
void oilChange() { }
};
template void speak(T speaker) {
speaker.talk();
}
int main() {
Dog d;
Robot r;
speak(d);
speak(r);
}
再次聲明,speak()方法不關(guān)心他的參數(shù)類型,但是在編譯的時(shí)候,他仍然能保證他能傳出那些信息。但是在Java(同樣在C#)語言中,你卻不能這樣做,下面這樣的做法,在JDK1.5中就編譯不過去(注意,你必須添加source – 1.5來使得javac能識別你的泛型代碼) public class Communicate {
public void speak(T speaker) {
speaker.talk();
}
}
但是這樣卻可以: public class Communicate {
public void speak(T speaker) {
speaker.toString(); // Object methods work!
}
}
Java的泛型使用了“消磁”,也就是說如果你打算表示“任何類型”,那么Java會把這個(gè)任何類型轉(zhuǎn)化為Object。所以當(dāng)我說不能象C++/ADA/Python一樣真正的代表“任何類型”,他只是代表Object。看來如果想讓Java也能完成類似的工作必須定義一個(gè)包含了speak方法的接口(Interface),并且限制只能傳入這個(gè)接口。所以下面這樣的代碼能編譯: interface Speaks { void speak(); }
public class Communicate {
public void speak(T speaker) {
speaker.speak();
}
}
而這樣是說:T必須是一個(gè)實(shí)現(xiàn)了speak接口的類或者這樣的一個(gè)子類。所以我的反映就是,如果我不得不聲明這樣的一個(gè)子類,我為什么不直接用繼承的機(jī)制那?干嗎還非要弄的這么費(fèi)事還糊弄人呢?就象這樣: interface Speaks { void speak(); }
public class CommunicateSimply {
public void speak(Speaks speaker) {
speaker.speak();
}
}
在這個(gè)例子里,泛型沒有任何的優(yōu)勢,事實(shí)上,如果你真的這樣使用,會讓人迷糊的,因?yàn)槟銜煌5纳︻^:為什么這里他需要一個(gè)泛型那?有什么優(yōu)勢?回答是:什么都沒有。完全沒有必要用泛型,泛型完全沒有優(yōu)勢。如果我們要用泛型來實(shí)現(xiàn)上面的“狗狗和機(jī)器人”的例子,我們被迫要使用接口或者父類,用這樣顯式的方式來實(shí)現(xiàn)一個(gè)所謂的“泛型”。 interface Speaks { void talk(); }
class Dog implements Speaks {
public void talk() { }
public void reproduce() { }
}
class Robot implements Speaks {
public void talk() { }
public void oilChange() { }
}
class Communicate {
public static void speak(T speaker) {
speaker.talk();
}
}
public class DogsAndRobots {
public static void main(String[] args) {
Dog d = new Dog();
Robot r = new Robot();
Communicate.speak(d);
Communicate.speak(r);
}
}
(注意到在泛型中你用的extends而不是implements,implements是不能使用的,Java是精確的,并且Sun說了必須這樣做)再一次,泛型和簡單的接口實(shí)現(xiàn)相比沒有任何的優(yōu)勢。 interface Speaks { void talk(); }
class Dog implements Speaks {
public void talk() { }
public void reproduce() { }
}
class Robot implements Speaks {
public void talk() { }
public void oilChange() { }
}
class Communicate {
public static void speak(Speaks speaker) {
speaker.talk();
}
}
public class SimpleDogsAndRobots {
public static void main(String[] args) {
Dog d = new Dog();
Robot r = new Robot();
Communicate.speak(d);
Communicate.speak(r);
}
}
如果我們真的寫一段能真正代表“任何類型”的泛型代碼的話,那么這段代碼所代表的類型只能是Object,所以我們的泛型代碼只能說是Object的一個(gè)方法而已。所以,事實(shí)上,我們只能說Java的泛型只是對Object類型的一個(gè)泛化而已。不過免去從Object和其他類型之間不辭辛勞的轉(zhuǎn)型,這就是這個(gè)所謂的“泛型”帶給我們的好處。看起來似乎只是一個(gè)對容器類的新的解決方案而不是其他,不是么?所以這次討論會得到的一致結(jié)論是,這個(gè)所謂的泛型只是解決了容器類之間的自動轉(zhuǎn)型罷了。另外一個(gè)爭論是,如果讓代表的是一種任意類型的話,會引起類型不安全的事件。這顯然不對,因?yàn)镃++能在編譯的時(shí)候捕捉這樣的錯(cuò)誤。“啊哈”,他們說,“那是因?yàn)槲覀儽黄扔昧硗庖环N方法來實(shí)現(xiàn)Java的泛型”。所以Java中的泛型是真正的“自動轉(zhuǎn)型”。這是Java世界的方法,我們將失去真正的泛型(也就是隱式類型,事實(shí)上,我們可以用反射-reflection來實(shí)現(xiàn)這樣的功能,我在我的《Thinking in Java》中做過2,3次這樣的試驗(yàn),但是實(shí)現(xiàn)起來有點(diǎn)亂,失去了Java的文雅本性)。一開始我對Java的泛型有震驚,但是現(xiàn)在過去了。至少有一點(diǎn)清晰的是,這是不得不這樣的。C#雖然有一個(gè)比Java更好的泛型模式(因?yàn)樗麄冇悬c(diǎn)超前,他們修改了底層的IL所致,舉個(gè)例子說,類和類之中的靜態(tài)域(static field)是不一樣的),但是也不支持隱式類型。所以,如果你想用隱式參數(shù),你不得不使用C++或者Python或者Smalltalk,或者Ruby等等:)。