C ++和Java中的“泛型”类型之间有什么区别?


Answers:


144

它们之间有很大的区别。在C ++中,您不必为泛型类型指定类或接口。这就是为什么您可以创建真正的泛型函数和类,而不必担心键入错误。

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

上面的方法添加了两个相同类型的对象,并且可以用于具有“ +”运算符的任何类型T。

在Java中,如果要在传递的对象上调用方法,则必须指定一种类型,例如:

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

在C ++中,泛型函数/类只能在标头中定义,因为编译器会针对不同的类型(调用它)生成不同的函数。因此编译速度较慢。在Java中,编译不会带来很大的损失,但是Java使用一种称为“擦除”的技术,该技术在运行时会擦除泛型类型,因此在运行时Java实际上正在调用...

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

因此,Java中的通用编程并不是真正有用,它只是语法上的一点帮助新的foreach构造。

编辑:以上关于有用性的观点是由一个年轻的自我写成的。Java的泛型当然有助于类型安全。


27
他完全正确,因为它只是一个精心制作的语法糖。
alphazero 2011年

31
这不是纯粹的语法糖。编译器使用此信息检查类型。即使该信息在运行时不可用,我也不会称其为编译器仅使用的“语法糖”。如果您这样说,那么C只是汇编的语法糖,而对于机器代码来说只是语法糖:)
dtech

42
我认为语法糖是有用的。
poitroae

5
您错过了主要的区别点,即可以用来实例化泛型的地方。在c ++中,可以使用模板<int N>并获得用于实例化它的任何数字的不同结果。它用于编译时元编程。就像答案中的一样:stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal

2
不是要“指定类型”,在任一形式extendssuper。答案有误,
罗恩侯爵(Marquis of Lorne)

124

Java泛型是大量 C ++模板不同。

基本上,在C ++中,模板基本上是经过修饰的预处理器/宏集(注意:由于某些人似乎无法理解类推,因此我并不是说模板处理是宏)。在Java中,它们基本上是语法糖,可最大程度地减少对象的样板转换。这是对C ++模板与Java泛型的相当不错的介绍

要详细说明这一点:使用C ++模板时,基本上是在创建代码的另一个副本,就像使用#define宏一样。这使您可以执行诸如int在模板定义中使用参数来确定数组大小等操作。

Java不能那样工作。在Java中,所有对象都来自java.lang.Object,因此,泛型之前,您需要编写如下代码:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

因为所有Java集合类型都将Object作为其基本类型,因此您可以在其中放置任何内容。Java 5推出并添加了泛型,因此您可以执行以下操作:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

这就是Java泛型的全部内容:转换对象的包装器。那是因为Java泛型没有改进。他们使用类型擦除。之所以做出此决定,是因为Java泛型出现的太晚了,以至于他们不想破坏向后兼容性(Map<String, String>只要需要a,就可以使用a Map)。将此与未使用类型擦除的.Net / C#进行比较,这会导致各种差异(例如,您可以使用原始类型,IEnumerable并且IEnumerable<T>彼此之间没有任何关系)。

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

这就是Java泛型被称为语法糖的原因

但是,关于如何执行泛型的决定产生了深远的影响,以至于(一流的)Java Generics FAQ迅速出现,回答了人们对Java Generics的许多问题。

C ++模板具有Java泛型所没有的许多功能:

  • 使用原始类型参数。

    例如:

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

    Java不允许在泛型中使用基本类型参​​数。

  • 使用默认类型参数,这是我在Java中缺少的一项功能,但是有向后兼容的原因;

  • Java允许参数的边界。

例如:

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

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

除了与泛型的区别之外,为了完整性,这里是C ++和Java(和另一个)的基本比较

而且我也可以建议使用Java思考。作为C ++程序员,对象之类的许多概念已经是天生的,但是存在细微的差异,因此即使您略过部分内容,也应该有介绍性的文本。

学习Java时,您会学到的很多东西都是库(都是标准库(JDK中附带的库)和非标准库,其中包括Spring等常用的东西)。Java语法比C ++语法更冗长,并且不具有许多C ++功能(例如,运算符重载,多重继承,析构函数等),但这也并非严格使其成为C ++的子集。


1
它们在概念上并不相同。最好的例子是奇怪的重复模板模式。第二好的是面向策略的设计。第三好的事实是C ++允许在尖括号(myArray <5>)中传递整数。
Max Lybbert,09年

1
不,它们在概念上并不相同。这个概念有一些重叠,但没有太多。两者都允许您创建List <T>,但这已经足够了。C ++模板走得更远。
jalf

5
重要的是要注意,类型擦除问题不仅仅意味着向后兼容Map map = new HashMap<String, String>。这意味着您可以在旧的JVM上部署新代码,并且由于字节码的相似性而可以运行。
Yuval Adam

1
您会注意到我说的是“基本上是荣耀的预处理器/宏”。这是一个类比,因为每个模板声明将创建更多代码(与Java / C#相反)。
cletus

4
模板代码与复制粘贴非常不同。如果您考虑宏扩展,迟早会遇到诸如此类的细微错误:womble.decadentplace.org.uk/c++/…
Nemanja Trifunovic,2009年

86

C ++具有模板。Java具有泛型,看上去有点像C ++模板,但是它们非常非常不同。

顾名思义,模板是通过为编译器提供一个(等待它……)模板来工作的,模板可以通过填充模板参数来生成类型安全的代码。

据我所知,泛型的工作方式相反:编译器使用类型参数来验证使用它们的代码是否是类型安全的,但是生成的代码完全没有类型。

将C ++模板视为一个非常好的宏系统,并将Java泛型视为自动生成类型转换的工具。

 


4
这是一个很好,简洁的解释。我想做的一个调整是Java泛型是一种用于自动生成类型转换的工具,类型转换被保证是安全的(在某些条件下)。在某些方面,它们与C ++有关constconst除非const-ness被抛弃,否则不会通过指针修改C ++中的对象。同样,除非在代码中的某个位置手动将类型参数抛弃,否则由Java中的泛型类型创建的隐式强制转换保证是“安全的”。
劳伦斯·贡萨尔维斯

16

C ++模板具有Java泛型所没有的另一个功能是专业化。这使您可以对特定类型使用不同的实现。因此,例如,您可以为int拥有高度优化的版本,而对于其余类型仍然具有通用版本。或者,您可以为指针和非指针类型使用不同的版本。如果要在传递指针时对取消引用的对象进行操作,这将很方便。


1
+1模板专业化对于编译时元编程非常重要-这种差异本身使Java泛型的效力大大降低
Faisal Vali 2009年


5

基本上,AFAIK,C ++模板为每种类型创建代码的副本,而Java泛型使用完全相同的代码。

是的,您可以说 C ++模板等效于Java泛型概念(尽管更恰当的说法是Java泛型在概念上等效于C ++)

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

来自:Java泛型


3

Java(和C#)泛型似乎是一种简单的运行时类型替换机制。
C ++模板是一种编译时结构,可让您根据自己的需要修改语言。它们实际上是编译器在编译期间执行的纯功能语言。


3

C ++模板的另一个优点是专业化。

template <typename T> T sum(T a, T b) { return a + b; }
template <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,则将调用第一种方法,如果sum使用Special对象调用,则将调用第三个方法。我认为Java不可能做到这一点。


2
可能是因为Java没有指针.. !! 你能用一个更好的例子来解释吗?
Bhavuk Mathur

2

我将用一句话概括一下:模板创建新类型,泛型限制现有类型。


2
您的解释很简短!对于非常了解该主题的人来说,这是完全有意义的。但是对于那些还不了解它的人来说,它并没有太大帮助。(哪个人在SO上问问题,知道吗?)
Jakub

1

@基思:

该代码实际上是错误的,除了较小的故障(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); }

2
为什么这是答案而不是评论?
劳伦斯·贡萨尔维斯

3
@Laurence:一次,因为它是在Stack Overflow上实施注释之前发布的。另一个原因是,这不仅是注释,而且是问题的答案:在Java中,上述代码无法实现。
康拉德·鲁道夫

1

下面的答案来自第13章,《破解编码面试解决方案》,我认为这很好。

Java泛型的实现植根于“类型擦除:”的思想,该技术在将源代码转换为Java虚拟机(JVM)字节码时消除了参数化的类型。例如,假设您具有以下Java代码:

Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);

在编译期间,此代码被重写为:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

Java泛型的使用并没有真正改变我们的功能。它使事情变得更漂亮。因此,Java泛型有时被称为“语法糖:”。

这与C ++完全不同。在C ++中,模板本质上是美化的宏集,编译器会为每种类型创建模板代码的新副本。事实证明,MyClass实例不会与MyClass共享静态变量。但是,MyClass的两个实例将共享一个静态变量。

/*** MyClass.h ***/
 template<class T> class MyClass {
 public:
 static int val;
 MyClass(int v) { val v;}
 };
 /*** MyClass.cpp ***/
 template<typename T>
 int MyClass<T>::bar;

 template class MyClass<Foo>;
 template class MyClass<Bar>;

 /*** main.cpp ***/
 MyClass<Foo> * fool
 MyClass<Foo> * foo2
 MyClass<Bar> * barl
 MyClass<Bar> * bar2

 new MyClass<Foo>(10);
 new MyClass<Foo>(15);
 new MyClass<Bar>(20);
 new MyClass<Bar>(35);
 int fl fool->val; // will equal 15
 int f2 foo2->val; // will equal 15
 int bl barl->val; // will equal 35
 int b2 bar2->val; // will equal 35

在Java中,静态变量在MyClass的各个实例之间共享,而与不同的类型参数无关。

Java泛型和C ++模板还有许多其他差异。这些包括:

  • C ++模板可以使用基本类型,例如int。Java无法并且必须改为使用Integer。
  • 在Java中,您可以将模板的类型参数限制为某种类型。例如,您可以使用泛型来实现CardDeck并指定type参数必须从CardGame扩展。
  • 在C ++中,可以实例化type参数,而Java不支持。
  • 在Java中,类型参数(即MyClass中的Foo)不能用于静态方法和变量,因为它们将在MyClass和MyClass之间共享。在C ++中,这些类是不同的,因此type参数可用于静态方法和变量。
  • 在Java中,MyClass的所有实例,无论其类型参数如何,都是相同的类型。类型参数在运行时被擦除。在C ++中,具有不同类型参数的实例是不同的类型。

0

模板不过是一个宏系统。语法糖。它们在实际编译之前已完全扩展(或者至少,编译器的行为就像是实际情况一样)。

例:

假设我们要两个功能。一个函数接受两个数字序列(列表,数组,向量,无论如何),然后返回其内积。另一个函数需要一个长度,生成两个具有该长度的序列,然后将它们传递给第一个函数,然后返回结果。要注意的是,我们可能在第二个函数中犯了一个错误,因此这两个函数的长度实际上并不相同。在这种情况下,我们需要编译器来警告我们。不是在程序运行时,而是在编译时。

在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 ++重写它,但由于模板无限扩展而无法编译。


好的,这是3岁,但是我仍然在回应。我不明白你的意思。Java为该带注释的行生成错误的全部原因是,因为您将调用一个期望两个带有不同参数(A和Cons <A>)的A的函数,这实际上是基本的,并且在不涉及泛型时也会发生。C ++也这样做。除此之外,此代码还给我带来了癌症,因为它确实很可怕。但是,您仍然会像在C ++中那样进行操作,当然必须进行这样的修改,因为C ++不是Java,但这并不是C ++模板的缺点。
Clocktown

@clocktown不,您不能使用C ++做到这一点。大量修改都不允许这样做。这是C ++模板的缺点。
MigMit

您的代码应该执行的操作-警告不同的长度-不能执行。在您注释掉的示例中,由于参数不匹配,只会产生错误。在C ++中也可以。您可以键入在语义上等效的代码,并且比在C ++和Java中的这种混乱更好。
Clocktown

是的 参数不完全匹配,因为长度不同。您无法在C ++中做到这一点。
MigMit '16

0

我想在这里引用askanydifference

C ++和Java之间的主要区别在于它们对平台的依赖性。C ++是平台相关的语言,而Java是平台无关的语言。

上面的陈述是C ++能够提供真正的泛型类型的原因。尽管Java确实进行了严格的检查,因此它们不允许像C ++那样使用泛型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.