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

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

Java具有泛型,并且C ++使用template提供了非常强大的编程模型。
那么,C ++和Java泛型之间有什么区别?


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

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

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

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

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

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

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

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

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


Java泛型与C ++模板完全不同。

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

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

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

1
2
3
4
5
6
7
8
9
public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

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

  ...
}

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

1
2
3
4
5
6
7
8
9
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,就可以使用Map)。将此与未使用类型擦除的.Net / C#进行比较,这会导致各种差异(例如,您可以使用原始类型,并且IEnumerableIEnumerable< T >彼此不相关)。

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

这就是为什么Java泛型被称为语法糖。

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

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

  • 使用原始类型参数。

    例如:

    1
    2
    3
    4
    5
    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }

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

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

  • Java允许参数的边界。

例如:

1
2
3
public class ObservableList<T extends List> {
  ...
}

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

除了与泛型的区别之外,出于完整性的考虑,这里是C ++和Java(以及另一个)的基本比较。

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

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


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

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

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

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


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


Java泛型和集合中对该主题有很好的解释
莫里斯·纳夫塔林(Maurice Naftalin),菲利普·沃德勒(Philip Wadler)。我强烈推荐这本书。报价:

Generics in Java resemble templates in
C++. ... The syntax is deliberately
similar and the semantics are
deliberately different. ...
Semantically, Java generics are
defined by erasure, where as C++
templates are defined by expansion.

请在此处阅读完整说明。

alt text
(来源:oreilly.com)


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

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

If you are familiar with C++'s template mechanism, you might think that generics are similar, but the similarity is superficial. Generics do not generate a new class for each specialization, nor do they permit"template metaprogramming."

来自:Java泛型


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

1
2
3
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,则将调用第一个方法;如果使用Special对象调用sum,则将调用第三个方法。我认为Java无法做到这一点。


Java(和C#)泛型似乎是一种简单的运行时类型替换机制。

C ++模板是一种编译时结构,可让您根据自己的需要修改语言。它们实际上是编译器在编译期间执行的纯功能语言。


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


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

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*** 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 ++中,可以实例化type参数,而Java无法
    支持这一点。
  • 在Java中,类型参数(即MyClass中的Foo)不能为
    用于静态方法和变量,因为它们将在MyClass和MyClass之间共享。在C ++中,这些类是不同的,因此类型参数可用于静态方法和变量。
  • 在Java中,MyClass的所有实例,无论其类型参数如何,都是相同的类型。类型参数在运行时被擦除。在C ++中,具有不同类型参数的实例是不同的类型。

@Keith:

该代码实际上是错误的,除了较小的故障(省略了template,特殊化语法看起来有所不同)之外,部分特殊化不适用于功能模板,仅适用于类模板。但是,该代码无需局部模板专门化即可工作,而应使用普通的旧重载:

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


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

例:

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

在Java中,您可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 ++重写它,但由于模板无限扩展而无法编译。


推荐阅读

    查看linux库类型命令?

    查看linux库类型命令?,系统,工作,信息,状态,电脑,命令,工具,代码,地址,发行,

    linux终端命令行编程?

    linux终端命令行编程?,系统,工作,命令,终端,概念,时间,第一,代码,发行,地方,L

    linux查看接口命令?

    linux查看接口命令?,地址,系统,标准,命令,管理,工作,文件,端口,目录,路径,lin

    linux网卡类型命令?

    linux网卡类型命令?,网络,系统,地址,信息,设备,状态,服务,名称,名字,网卡,如

    linux编程调用命令?

    linux编程调用命令?,系统,标准,管理,工作,基础知识,情况,环境,设备,基础,首

    linux编程所需的命令?

    linux编程所需的命令?,工作,地址,档案,系统,命令,管理,标准,信息,目录,文件,L

    使用linux命令调接口?

    使用linux命令调接口?,网络,系统,地址,信息,工具,情况,服务,灵活,电脑,名称,

    linux命令行编程乱码?

    linux命令行编程乱码?,环境,统一,乱码,中文,状态,软件,数据,系统,字符集,文

    linux查看命令类型用?

    linux查看命令类型用?,信息,系统,情况,命令,实时,工作,设备,电脑,文件,类型,

    linux命令三种类型?

    linux命令三种类型?,工作,地址,系统,标准,时间,管理,命令,目录,信息,文件,lin

    linux命令添加接口?

    linux命令添加接口?,地址,网络,名称,系统,工具,设备,信息,服务,中心,密码,Lin

    linux编程c命令符?

    linux编程c命令符?,工具,代码,系统,保险,环境,文件,程序,命令,终端,编辑,到底

    linux常用编程命令?

    linux常用编程命令?,基础,基础知识,系统,管理,概念,在线,发展,设备,设计,名

    linux接口模式命令?

    linux接口模式命令?,设备,系统,信息,网络,工具,端口,服务,数字,地址,情况,Lin

    linux命令行界面编程?

    linux命令行界面编程?,系统,环境,代码,密码,命令,终端,首页,软件,工作,电脑,l

    linux命令行编程参数?

    linux命令行编程参数?,网络,信息,系统,实时,状态,情况,工具,服务,环境,分析,L

    linux下命令访问接口?

    linux下命令访问接口?,网络,信息,状态,系统,地址,服务,工具,基础,管理,设备,l