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

What are the differences between "generic" types in C++ and Java?

提问人:popopome 提问时间:8/31/2008 最后编辑:JesseTGpopopome 更新时间:4/16/2022 访问量:115055

问:

Java 具有泛型,而 C++ 提供了非常强大的 s 编程模型。 那么,C++和Java泛型有什么区别呢?template

Java C++ 泛型 模板 language-features

评论

2赞 n611x007 12/16/2012
stackoverflow.com/questions/31693

答:

99赞 Shog9 8/31/2008 #1

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

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

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

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

 

评论

5赞 Laurence Gonsalves 3/2/2010
这是一个相当好、简洁的解释。我很想做的一个调整是,Java 泛型是一种自动生成类型转换的工具,这些类型转换保证是安全的(在某些条件下)。在某些方面,它们与C++的.C++ 中的对象不会通过指针进行修改,除非 -ness 被丢弃。同样,Java 中泛型类型创建的隐式强制转换可以保证是“安全的”,除非在代码中的某个地方手动强制转换类型参数。constconstconst
0赞 Nitesh 2/23/2022
“将 C++ 模板视为一个非常好的宏系统”极大地削弱了 C++ 模板的功能
0赞 barneypitt 8/31/2022
巨大?我不能同意。模板比真正好的宏更有用的情况很少,而且相距甚远。
170赞 Alexandru Nedelcu 8/31/2008 #2

它们之间有很大的区别。在 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 的泛型有助于实现类型安全。

评论

29赞 alphazero 8/5/2011
他完全正确地认为它只是一个精心设计的句法糖。
46赞 dtech 2/4/2013
这不是纯粹的句法糖。编译器使用此信息来检查类型。即使这些信息在运行时不可用,我也不会简单地将编译使用的东西称为“句法糖”。如果你这么称呼它,那么 C 只是用于汇编的语法糖,而这只是用于机器代码的语法糖:)
52赞 poitroae 2/14/2013
我认为句法糖是有用的。
8赞 stonemetal 8/25/2013
你错过了一个主要的区别点,你可以用它来实例化泛型。在 c++ 中,可以使用模板 <int N> 并为用于实例化它的任何数字获得不同的结果。它用于编译时元游戏。就像答案一样:stackoverflow.com/questions/189172/c-templates-turing-complete
2赞 user207421 4/26/2016
不必以 或 .答案不正确,extendssuper
4赞 Ferruccio 9/3/2008 #3

Java(和 C#)泛型似乎是一个简单的运行时类型替换机制。
C++ 模板是一种编译时构造,它为您提供了一种修改语言以满足您需求的方法。它们实际上是编译器在编译过程中执行的纯函数式语言。

3赞 KeithB 9/3/2008 #4

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,则将调用第一个方法,如果使用对象调用,则将调用第三个方法。我不认为这在Java中是不可能实现的。sumSpecial

评论

2赞 Bhavuk Mathur 8/22/2016
可能是因为Java没有指针..!!你能用一个更好的例子来解释吗?
0赞 Shmuel Kamensky 2/17/2022
@BhavukMathur 我认为 Keithb 的意思是您可以使用模板重载方法。有点“通用”重载。指针只是一种示例类型。
1赞 Konrad Rudolph 9/3/2008 #5

@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); }

评论

2赞 Laurence Gonsalves 3/2/2010
为什么这是一个答案而不是评论?
3赞 Konrad Rudolph 3/2/2010
@Laurence:这一次,因为它早在 Stack Overflow 上实现评论之前就已经发布了。另一方面,因为它不仅是一个注释,也是对这个问题的回答:像上面这样的代码在 Java 中是不可能的。
5赞 OscarRyz 1/31/2009 #6

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

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

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

来自: Java 泛型

139赞 cletus 1/31/2009 #7

Java 泛型与 C++ 模板有很大不同。

基本上在C++中,模板基本上是一个美化的预处理器/宏集(注意:由于有些人似乎无法理解类比,我并不是说模板处理是一个宏)。在 Java 中,它们基本上是语法糖,以最小化对象的样板转换。这是对C++模板与Java泛型的相当不错的介绍

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

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 泛型在这篇文章中出现得太晚了,以至于他们不想破坏向后兼容性(只要需要 a 就可以使用 a)。与不使用类型擦除的 .Net/C# 相比,这会导致各种差异(例如,您可以使用基元类型并且彼此之间没有关系)。Map<String, String>MapIEnumerableIEnumerable<T>

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

这就是为什么 Java 泛型被称为句法糖的原因。

但是,这个关于如何做泛型的决定产生了深远的影响,以至于(极好的)Java 泛型 FAQ 如雨后春笋般涌现,以回答人们对 Java 泛型的许多问题。

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赞 Max Lybbert 1/31/2009
它们在概念上并不等价。最好的例子是奇怪的重复模板模式。其次是以政策为导向的设计。第三好的是C++允许在尖括号中传递整数 (myArray<5>)。
1赞 jalf 2/1/2009
不,它们在概念上并不等价。这个概念有一些重叠,但并不多。两者都允许您创建 List<T>,但仅此而已。C++ 模板走得更远。
5赞 Yuval Adam 2/1/2009
需要注意的是,类型擦除问题不仅仅意味着 的向后兼容性。这意味着您可以在旧的 JVM 上部署新代码,并且由于字节码的相似性,它将运行。Map map = new HashMap<String, String>
1赞 cletus 2/1/2009
你会注意到我说过“基本上是一个美化的预处理器/宏”。这是一个类比,因为每个模板声明都会创建更多的代码(而不是 Java/C#)。
4赞 Nemanja Trifunovic 2/2/2009
模板代码与复制粘贴有很大不同。如果你从宏观扩展的角度来考虑,你迟早会被一些微妙的错误所击中,比如这个:womble.decadentplace.org.uk/c++/......
14赞 Julien Chastang 2/1/2009 #8

在 Maurice Naftalin, Philip Wadler 的 Java Generics and Collections 中对此主题有很好的解释。我强烈推荐这本书。引用:

Java 中的泛型类似于 C++。...语法是故意的 相似,语义是 刻意不同。... 从语义上讲,Java 泛型是 由擦除定义,其中为 C++ 模板由扩展定义。

在此处阅读完整的说明。

alt text
(来源: oreilly.com

17赞 KeithB 2/1/2009 #9

C++ 模板具有 Java 泛型所没有的另一个功能是专用化。这允许您为特定类型提供不同的实现。因此,例如,您可以为 int 提供高度优化的版本,同时仍然为其余类型提供通用版本。或者,您可以为指针和非指针类型提供不同的版本。如果您想在递给指针时对取消引用的对象进行操作,这将派上用场。

评论

1赞 Faisal Vali 6/16/2009
+1 模板专用化对于编译时元编程非常重要 - 这种差异本身就使 Java 泛型的效力大大降低
0赞 MigMit 8/25/2013 #10

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

例:

假设我们想要两个函数。一个函数接受两个数字序列(列表、数组、向量等),并返回它们的内积。另一个函数采用一个长度,生成两个该长度的序列,将它们传递给第一个函数,并返回其结果。问题是我们可能会在第二个函数中犯错误,因此这两个函数的长度实际上并不相同。在这种情况下,我们需要编译器来警告我们。不是在程序运行时,而是在编译时。

在 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++ 重写它,它不会编译,抱怨模板无限扩展。

评论

0赞 clocktown 12/4/2016
好吧,这是 3 岁了,但我还是在回应。我不明白你的意思。Java 为该注释行生成错误的全部原因是,您将调用一个函数,该函数需要两个具有不同参数(A 和 Cons<A>)的 A,这非常基本,并且在不涉及泛型时也会发生。C++ 也可以这样做。除此之外,这个代码给了我癌症,因为它真的很可怕。但是,您仍然会在 C++ 中这样做,您当然必须进行修改,因为 C++ 不是 Java,但这不是 C++ 模板的缺点。
0赞 MigMit 12/5/2016
@clocktown不,你不能在C++这样做。再多的修改也不允许这样做。这是C++模板的缺点。
0赞 clocktown 12/6/2016
你的代码应该做什么 - 警告不同的长度 - 它没有做。在注释掉的示例中,它仅由于参数不匹配而产生错误。这在C++中也有效。你可以键入语义上等价的代码,并且比C++和Java中的这种混乱要好得多。
0赞 MigMit 12/6/2016
确实如此。参数不完全匹配,因为长度不同。你不能在C++中做到这一点。
2赞 user207421 1/31/2016 #11

我将用一句话来总结:模板创建新类型,泛型限制现有类型。

评论

3赞 Jakub 11/3/2016
你的解释太简短了!对于非常了解该主题的人来说,这是非常有意义的。但对于还不了解它的人来说,它没有多大帮助。(这是任何人在 SO 上提问的情况,明白了吗?
3赞 Jswq 7/1/2018 #12

下面的答案来自《破解编码面试解决方案》一书到第 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 的 Tow 实例将共享一个静态变量。

/*** 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 参数必须从 纸牌游戏。
  • 在 C++ 中,类型参数可以实例化,而 Java 则不能 支持这个。
  • 在 Java 中,type 参数(即 MyClass 中的 Foo)不能是 用于静态方法和变量,因为它们将在 MyClass 和 MyClass 之间共享。在 C++ 中,这些类是不同的,因此类型参数可用于静态方法和变量。
  • 在 Java 中,MyClass 的所有实例,无论其类型参数如何,都是相同的类型。类型参数在运行时被擦除。在 C++ 中,具有不同类型参数的实例是不同的类型。
-1赞 Piyush-Ask Any Difference 7/12/2020 #13

我想在这里引用askanydifference

C++ 和 Java 之间的主要区别在于它们对平台的依赖。虽然C++是依赖于平台的语言,但Java是独立于平台的语言。

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