Thursday, May 20, 2004

?这可不是泛型?-Bruce Eckel眼中的Java泛型

Bruce Eckel在前不久写了一片批判Java的泛型的文章,结合他在OO上浸淫多年的功力,一眼就看出了Java的泛型和其他例如C++,Python,Ruby等等这些语言的泛型的差别。

不过平心而论,Bruce Eckel的批评是比较中肯的,因为他也看到了Java和其他这些面向对象的语言之间的差别,他也可以理解Java实现出了这样的泛型。

另外,如果大家不知道Ruby是什么,可以参考下面的网站:http://www.ruby-lang.org/en/

详细内容如下:


昨晚,我作为嘉宾被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等等:)。

No comments:

Post a Comment