C ++和Java中的“通用”types有什么区别?

Java具有generics,C ++通过template提供了一个非常强大的编程模型。 那么,C ++和Javagenerics有什么区别呢?

他们之间有很大的区别。 在C ++中,您不必为genericstypes指定类或接口。 这就是为什么你可以创build真正的generics函数和类,与宽松的打字警告。

 template <typename T> T sum(T a, T b) { return a + b; } 

上面的方法添加了两个相同types的对象,可用于任何具有“+”运算符的typesT.

在Java中,如果要调用传递对象的方法,则必须指定一个types,如下所示:

 <T extends Something> T sum(T a, T b) { return a.add ( b ); } 

在C ++中,generics函数/类只能在头文件中定义,因为编译器会为不同的types(调用它)生成不同的函数。 所以编译速度较慢。 在Java中,编译没有大的损失,但是Java使用了一种叫做“擦除”的技术,其中genericstypes在运行时被擦除,所以在运行时Java实际上正在调用…

 Something sum(Something a, Something b) { return a.add ( b ); } 

所以在Java中进行通用编程并不是很有用,它只是一个帮助新的foreach结构的语法糖。

编辑:有用的意见是由年轻的自己写的。 Java的generics当然有助于types安全。

Javagenerics与C ++模板大不相同。

基本上在C ++模板基本上是一个荣耀的预处理器/macros集( 注意:因为有些人似乎不能理解类比,我不是说模板处理是一个macros)。 在Java中,它们基本上是语法糖,可以最大限度地减less对象的样式转换。 这是一个相当不错的介绍C + +模板与Javagenerics 。

为了详细说明这一点:当你使用C ++模板时,基本上就是创build代码的另一个副本,就像使用#definemacros一样。 这允许你做一些事情,比如在模板定义中有int参数来决定数组的大小等等。

Java不能这样工作。 在Java中,所有对象都是从java.lang.Object扩展而来的,所以在generics之前,你可以这样写代码:

 public class PhoneNumbers { private Map phoneNumbers = new HashMap(); public String getPhoneNumber(String name) { return (String)phoneNumbers.get(name); } ... } 

因为所有Java集合types都使用Object作为它们的基types,因此您可以将任何东西放入其中。 爪哇5滚动和添加generics,所以你可以做这样的事情:

 public class PhoneNumbers { private Map<String, String> phoneNumbers = new HashMap<String, String>(); public String getPhoneNumber(String name) { return phoneNumbers.get(name); } ... } 

这就是所有的Javagenerics:铸造对象的包装。 这是因为Javagenerics没有完善。 他们使用types擦除。 做出这个决定是因为Java Generics在片断中来得太晚,以至于他们不想破坏向后兼容性( Map<String, String>在每次调用Map时都可用)。 将其与不使用types删除的.Net / C#进行比较,这会导致各种不同(例如,您可以使用原始types, IEnumerableIEnumerable<T>彼此无关)。

使用Java 5+编译器编译的generics类在JDK 1.4上可用(假定它不使用任何其他需要Java 5+的function或类)。

这就是为什么Javagenerics被称为语法糖 。

但是,关于如何做generics的决定具有深远的影响,以至于Javagenerics常见问题 ( Java Generics FAQ )如雨后春笋般涌现出来,回答了人们对Javagenerics的许多疑问。

C ++模板有一些Javagenerics不具备的特性:

  • 使用原始types参数。

    例如:

     template<class T, int i> class Matrix { int T[i][i]; ... } 

    Java不允许在generics中使用原始types参数。

  • 使用默认的types参数 ,这是我在Java中想念的一个function,但是有一些向后兼容的原因;

  • Java允许参数的边界。

例如:

 public class ObservableList<T extends List> { ... } 

确实需要强调的是,具有不同参数的模板调用实际上是不同的types。 他们甚至不共享静态成员。 在Java中,情况并非如此。

除了generics之外,为了完整起见,下面是C ++和Java (以及另一个 )的基本比较 。

我也可以build议Java的思考 。 作为一名C ++程序员,像对象这样的许多概念已经是第二性质了,但是它们之间存在细微的差别,所以即使你忽略了部分内容,也可以有一个介绍性的文本。

学习Java时学到的很多东西是所有的库(包括标准 – JDK中的东西)和非标准的东西,包括像Spring这样常用的东西。 Java语法比C ++语法更加冗长,并且没有很多C ++特性(例如运算符重载,多重inheritance,析构函数机制等),但是它并不严格地使它成为C ++的一个子集。

C ++有模板。 Java有generics,看起来有点像C ++模板,但它们非常不同。

正如其名称所暗示的,模板通过向编译器提供(等待它)模板来工作,该模板可以通过填充模板参数来生成types安全的代码。

generics,正如我所理解的那样,另一种方式是:编译器使用types参数来validation使用它们的代码是否是types安全的,但是生成的代码是根本没有types生成的。

把C ++模板想象成一个非常好的macros观系统,把Javagenerics看作自动生成types转换的工具。

C ++模板的另一个特性是Javagenerics没有专门化。 这使您可以为特定的types提供不同的实现。 因此,例如,您可以针对int具有高度优化的版本,而对于其余types,仍然具有通用版本。 或者你可以有不同的指针和非指针types的版本。 如果你想在交给指针的时候对解除引用的对象进行操作,这就派上用场了。

在Javagenerics和集合中有一个很好的解释,这个主题由Maurice Naftalin,Philip Wadler。 我强烈推荐这本书。 去引用:

Java中的generics类似于C ++中的模板。 …语法故意相似,语义是故意不同的。 …从语义上讲,Javagenerics是通过删除来定义的,C ++模板是由扩展定义的。

请在这里阅读完整的说明。

替代文字http://oreilly.com/catalog/covers/0596527756_cat.gif

基本上,AFAIK,C ++模板为每种types创build一个代码副本,而Javagenerics使用完全相同的代码。

是的,你可以说 C ++模板等同于Java通用概念 (虽然更恰当的说Java概念上的generics等价于C ++)

如果你熟悉C ++的模板机制,你可能会认为generics是相似的,但相似性是肤浅的。 generics不会为每个专业化生成新的类,也不允许“模板元编程”。

来自: Javagenerics

C ++模板的另一个优点是specilization。

 <typename T> T sum(T a, T b) { return a + b; } <typename T> T sum(T* a, T* b) { return (*a) + (*b); } Special sum(const Special& a, const Special& b) { return a.plus(b); } 

现在,如果你用指针调用sum,第二个方法将被调用,如果你用非指针对象调用sum,第一个方法将被调用,如果你用Special对象调用sum(),第三个方法将被调用。 我不认为这是可能的Java。

Java(和C#)generics似乎是一个简单的运行时typesreplace机制。
C ++模板是一个编译时的结构,它给你一种修改语言的方法来满足你的需求。 它们实际上是编译器在编译期间执行的纯函数语言。

模板不过是一个macros观系统。 语法糖。 在实际编译之前它们被完全扩展(或者至less编译器的行为就像是这种情况)。

例:

假设我们需要两个function。 一个函数需要两个序列(列表,数组,vector,不pipe怎样)的数字,并返回它们的内积。 另一个函数需要一个长度,生成两个长度的序列,将它们传递给第一个函数,并返回结果。 问题是我们可能在第二个函数中犯了一个错误,所以这两个函数的长度并不相同。 在这种情况下,我们需要编译器来警告我们。 不是在程序运行时,而是在编译时。

在Java中你可以这样做:

 import java.io.*; interface ScalarProduct<A> { public Integer scalarProduct(A second); } class Nil implements ScalarProduct<Nil>{ Nil(){} public Integer scalarProduct(Nil second) { return 0; } } class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{ public Integer value; public A tail; Cons(Integer _value, A _tail) { value = _value; tail = _tail; } public Integer scalarProduct(Cons<A> second){ return value * second.value + tail.scalarProduct(second.tail); } } class _Test{ public static Integer main(Integer n){ return _main(n, 0, new Nil(), new Nil()); } public static <A implements ScalarProduct<A>> Integer _main(Integer n, Integer i, A first, A second){ if (n == 0) { return first.scalarProduct(second); } else { return _main(n-1, i+1, new Cons<A>(2*i+1,first), new Cons<A>(i*i, second)); //the following line won't compile, it produces an error: //return _main(n-1, i+1, first, new Cons<A>(i*i, second)); } } } public class Test{ public static void main(String [] args){ System.out.print("Enter a number: "); try { BufferedReader is = new BufferedReader(new InputStreamReader(System.in)); String line = is.readLine(); Integer val = Integer.parseInt(line); System.out.println(_Test.main(val)); } catch (NumberFormatException ex) { System.err.println("Not a valid number"); } catch (IOException e) { System.err.println("Unexpected IO ERROR"); } } } 

在C#中,你可以编写几乎相同的东西。 尝试用C ++重写它,它不会编译,抱怨模板的无限扩展。

我将总结在一个单一的句子:模板创build新的types,generics限制了现有的types。

@Keith:

该代码实际上是错误的,除了较小的毛刺( template省略,专业化语法看起来不同),部分专业化不能在函数模板上工作,只能在类模板上工作。 然而,这段代码不需要部分模板专门化,而是使用普通的旧的重载:

 template <typename T> T sum(T a, T b) { return a + b; } template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }