C + + 和 Java 中的“泛型”类型有什么不同?

Java 有泛型,C + + 提供了一个非常强大的 template编程模型。 那么,C + + 和 Java 泛型之间的区别是什么呢?

110738 次浏览

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

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

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

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

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

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

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,第一个方法将被调用; 如果用 Special对象调用 sum,第三个方法将被调用。我不认为 Java 可以做到这一点。

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

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

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

如果您熟悉 C + + 的模板机制,您可能会认为泛型是相似的,但相似性只是表面上的。泛型不会为每个专门化生成一个新类,也不允许“模板超编程”

发信人: Java 泛型

Java 泛型是不同于 C + + 模板的 非常严重

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

为了详细说明这一点: 当您使用 C + + 模板时,您基本上是在创建代码的另一个副本,就像您使用了 #define宏一样。这允许您在模板定义中使用 int参数来确定数组的大小等等。

Java 不是这样工作的,在 Java 中,所有的对象都是从 对象扩展开来的,所以,pre-Generics,你可以这样写代码:

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

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

因为所有的 Java 集合类型都使用 Object 作为它们的基类型,因此您可以在其中放入任何内容。Java5四处运行并添加了泛型,因此您可以执行以下操作:

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

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

这就是 Java 泛型的全部内容: 用于强制转换对象的包装器。那是因为 Java 泛型没有经过改进。他们使用类型擦除。之所以做出这个决定,是因为 Java Generics 出现得太晚,以至于他们不想破坏向下兼容(无论什么时候需要 Map,都可以使用 Map<String, String>)。比较一下。不使用类型擦除的 Net/C # ,这会导致各种各样的差异(例如,您可以使用基元类型,而 IEnumerableIEnumerable<T>彼此之间没有关系)。

使用用 Java5 + 编译器编译的泛型的类可以在 JDK1.4上使用(假设它不使用任何其他需要 Java5 + 的特性或类)。

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

但是这个关于如何执行泛型的决定产生了深远的影响,以至于(超级的) Java 泛型常见问题解答如雨后春笋般出现来回答人们关于 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 + + 的一个子集。

在《 Java 泛型和集合》一书中,对这个主题有很好的解释 作者: Maurice Naftalin,Philip Wadler 我强烈推荐这本书,引用如下:

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

请阅读完整的解释 给你

alt text
(来源: Ooreilly.com)

C + + 模板具有 Java 泛型所没有的另一个特性是专门化。它允许您为特定类型使用不同的实现。因此,例如,您可以为 Int提供高度优化的版本,同时为其他类型提供通用版本。或者,对于指针类型和非指针类型,可以有不同的版本。如果您想在交给指针时操作解除引用的对象,这将非常方便。

模板只不过是一个宏系统。语法糖。它们在实际编译之前被完全展开(或者,至少,编译器的行为与实际情况相似)。

例如:

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

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

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

下面的答案来自《 破解编码访谈解决方案》到第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 泛型有时被称为“语法 Sugar:”。

这与 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 而是使用 Integer。
  • 在 Java 中,可以将模板的类型参数限制为 例如,您可以使用泛型来实现 并指定 type 参数必须从 纸牌游戏。
  • 在 C + + 中,类型参数可以实例化,而 Java 不能 支持这个。
  • 在 Java 中,type 参数(即 MyClass 中的 Foo)不能是 用于静态方法和变量,因为这些方法和变量将在 MyClass 和 MyClass 之间共享。在 C + + 中,这些类是不同的,所以 type 参数可以用于静态方法和变量。
  • 在 Java 中,MyClass 的所有实例,不管它们的类型参数是什么,都是相同的类型。类型参数在运行时被擦除。在 C + + 中,具有不同类型参数的实例是不同类型的。

我想在这里引用 有什么区别吗的话:

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

上述语句是 C + + 能够提供真正的泛型类型的原因。虽然 Java 有严格的检查,因此他们不允许使用泛型的方式 C + + 允许它。