Picses' sky

Picses' sky
posts - 43, comments - 29, trackbacks - 0, articles - 24

[译者按:]

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等等:)。

相关文章
对该文的评论
CSDN 网友 ( 2006-02-24)
sadf
ilovevc ( 2004-05-26)
泛型对java可是一个新的东西,因此Bruce Eckel只能参照其他已经有的实现,来判断Java的泛型能力。如果参照C++中的泛型,那么Java现在具有的这个泛型的能力,大概不会超过在任何一本讲泛型的C++书籍上前5页。没有trait,police,deduction,template template parameter,partial specialization,基本上也就仅仅能够解决容器的强制类型转换问题。

另外,像c++那种意义的泛型,极度依赖编译器,而不是预处理程序,预处理程序只能在文字literal上工作,例如include,macro,ifdef等,而泛型需要处理语法单元,而且这个过程极度不简单。至少,C++98标准出来这么多年了,现在市面上还没有100%符合C++标准的编译器存在,泛型无疑是其中的一个技术难点。C++编译器的开发者虽然不见得比Java强,但是应该不至于差太远,因此还是有难度的。

C++的泛型可以产生出一种设计模式,我们从面向过程(数据和算法分离)到面向对象(数据和算法结合),也许以后就是面向泛型了(数据和算法再次分离)。^_^

marshine ( 2004-05-25)
TO SnowFalcon:
你不觉得你所说的方式不就是目前C#使用的方式吗?只是表示方法不一样罢了。而且总的来说讨论的是Generic,它的价值并不会因为是否有接口限制而失去意义,即便存在Bruce Eckel所说的问题,也不能说“这可不是范型”,过了。
Benfish ( 2004-05-24)
c#泛型比java泛型好像先进一些,java中对原始类型还要自动装箱,而c#中是直接使用原始类型,这样就提高运行效率了
Benfish ( 2004-05-24)
怎么大家说来说去就说那一种泛型的应用啊。
其实泛型给我们提供的东西多着呢!
比如:
queue qint;
set sset;
我觉的这种应用才是最能提高效率的。
c++的泛型是在编译时的泛型,比如你要编译作者提的那个speak()函数,
你就必须让它和class Dog和class Robot的源代码一起编译
我想c++stl中提供的模板类也要在使用时以源代码提供
在编译以后就不存在泛型了,它们都在编译时自动转变成了确定的类型
而在c#中使用Queue是不需要Queue这个类的源代码的。

而且java, c#都是强类型的,像文章中举的那个speak()函数的例子就
不符合强类型的精神,所以不可能在java或者c#中实现的。
但是这并不是数它们就必c++弱
像c++那种意义的泛型
任何语言都可以简单的在编译器上加上一个预处理程序来实现
这和运行的是什么架构没有任何关系,仅仅是一个编译问题

只有注册用户登录后才能发表评论。


网站导航: