[译者按:]
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等等:)。