[譯者按:]
Bruce Eckel在前不久寫了一片批判Java的泛型的文章,結合他在OO上浸淫多年的功力,一眼就看出了Java的泛型和其他例如C++,Python,Ruby等等這些語言的泛型的差別。
不過平心而論,Bruce Eckel的批評是比較中肯的,因為他也看到了Java和其他這些面向對象的語言之間的差別,他也可以理解Java實現出了這樣的泛型。
另外,如果大家不知道Ruby是什么,可以參考下面的網站:http://www.ruby-lang.org/en/
[補充]添加一段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的模式組邀請去參加他們的一個研討會,并且讓我來決定討論的主題,為了更好的了解JDK1.5,我選擇了Java的Generics(泛型),最后討論的結果是我們大家都有點震驚。我們討論的主要素材是最新的Sun推出的Java 泛型手冊。我對“參數化類型”的經驗來自于C++,而C++的泛型又是基于ADA的Generics,事實上,Lisp語言是第一個實現了泛型的語言,有人說Simula語言也很早就有泛型了。在這些語言中,當你使用參數化的類型的時候,這個參數是作為一種隱式類型(latent type)的:一種被實現出了如何使用,但是卻沒有顯式的聲明的類型。也就是說,隱式類型是一種通過你調用方法來實現的。例如,你的模板方法是某個類型中的f()和g(),那么接下來你實現了一個類型包含了f()和g()這兩個方法,而事實上這個類型可能從來被定義過。舉個例子,在Python中,你可以這樣做:
def speak(anything):
anything.talk()
注意到,這里對anything沒有任何的類型限制,只是一個標識而已,假設這個類型能做一個叫做speak()的操作,就象實現了一個Interface一樣,但是事實上你根本不需要去真的實現這樣一個Interface,所以叫做隱式。現在我可以實現這樣一個“狗狗和機器人”的例子: 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()方法不關心是否有參數傳入,所以我可以傳給它任何的東西,就象我傳入的對象中支持的talk()方法一樣。我相信在Ruby語言中的實現是和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()方法不關心他的參數類型,但是在編譯的時候,他仍然能保證他能傳出那些信息。但是在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會把這個任何類型轉化為Object。所以當我說不能象C++/ADA/Python一樣真正的代表“任何類型”,他只是代表Object。看來如果想讓Java也能完成類似的工作必須定義一個包含了speak方法的接口(Interface),并且限制只能傳入這個接口。所以下面這樣的代碼能編譯: interface Speaks { void speak(); }
public class Communicate {
public void speak(T speaker) {
speaker.speak();
}
}
而這樣是說:T必須是一個實現了speak接口的類或者這樣的一個子類。所以我的反映就是,如果我不得不聲明這樣的一個子類,我為什么不直接用繼承的機制那?干嗎還非要弄的這么費事還糊弄人呢?就象這樣: interface Speaks { void speak(); }
public class CommunicateSimply {
public void speak(Speaks speaker) {
speaker.speak();
}
}
在這個例子里,泛型沒有任何的優勢,事實上,如果你真的這樣使用,會讓人迷糊的,因為你會不停的搔頭:為什么這里他需要一個泛型那?有什么優勢?回答是:什么都沒有。完全沒有必要用泛型,泛型完全沒有優勢。如果我們要用泛型來實現上面的“狗狗和機器人”的例子,我們被迫要使用接口或者父類,用這樣顯式的方式來實現一個所謂的“泛型”。 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說了必須這樣做)再一次,泛型和簡單的接口實現相比沒有任何的優勢。 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的一個方法而已。所以,事實上,我們只能說Java的泛型只是對Object類型的一個泛化而已。不過免去從Object和其他類型之間不辭辛勞的轉型,這就是這個所謂的“泛型”帶給我們的好處。看起來似乎只是一個對容器類的新的解決方案而不是其他,不是么?所以這次討論會得到的一致結論是,這個所謂的泛型只是解決了容器類之間的自動轉型罷了。另外一個爭論是,如果讓代表的是一種任意類型的話,會引起類型不安全的事件。這顯然不對,因為C++能在編譯的時候捕捉這樣的錯誤。“啊哈”,他們說,“那是因為我們被迫用另外一種方法來實現Java的泛型”。所以Java中的泛型是真正的“自動轉型”。這是Java世界的方法,我們將失去真正的泛型(也就是隱式類型,事實上,我們可以用反射-reflection來實現這樣的功能,我在我的《Thinking in Java》中做過2,3次這樣的試驗,但是實現起來有點亂,失去了Java的文雅本性)。一開始我對Java的泛型有震驚,但是現在過去了。至少有一點清晰的是,這是不得不這樣的。C#雖然有一個比Java更好的泛型模式(因為他們有點超前,他們修改了底層的IL所致,舉個例子說,類和類之中的靜態域(static field)是不一樣的),但是也不支持隱式類型。所以,如果你想用隱式參數,你不得不使用C++或者Python或者Smalltalk,或者Ruby等等:)。