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

java c++ generics templates language-features

81966 观看

12回复

5490 作者的声誉

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

作者: popopome 的来源 发布者: 2008 年 8 月 30 日

回应 12


74

129256 作者的声誉

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

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

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

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

 

作者: Shog9 发布者: 2008 年 8 月 30 日

123

6793 作者的声誉

决定

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

作者: Alexandru Nedelcu 发布者: 2008 年 8 月 30 日

3

80354 作者的声誉

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

作者: Ferruccio 发布者: 2008 年 9 月 3 日

3

14134 作者的声誉

C ++模板的另一个优点是规范化。

<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可以实现这一点。

作者: KeithB 发布者: 2008 年 9 月 3 日

1

399202 作者的声誉

@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); }
作者: Konrad Rudolph 发布者: 2008 年 9 月 3 日

4

143035 作者的声誉

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

是的,您可以说 C ++模板等同于Java通用概念(尽管更合适的是说Java泛型在概念上等同于C ++)

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

来自:Java Generics

作者: OscarRyz 发布者: 2009 年 1 月 31 日

115

507994 作者的声誉

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

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

要详细说明这一点:当您使用C ++模板时,您基本上是在创建代码的另一个副本,就像使用#define宏一样。这允许您执行诸如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集合类型都使用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 Generics都是:用于投射对象的包装器。那是因为Java Generics没有改进。他们使用类型擦除。这一决定是因为Java的泛型中,他们不希望打破向后兼容性的作品一起来到这么晚了(一个Map<String, String>是可用的,每当一个Map被调用)。与此相比,净/ C#在不使用类型擦除,从而导致各种各样的差异(例如,你可以使用基本类型和IEnumerableIEnumerable<T>不承担任何关于彼此)。

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

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

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

C ++模板具有Java Generics不具备的许多功能:

  • 使用原始类型参数。

    例如:

    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 ++的子集。

作者: cletus 发布者: 2009 年 1 月 31 日

13

12208 作者的声誉

Java Generics和Collections中有一个很好的解释, 由Maurice Naftalin,Philip Wadler撰写。我强烈推荐这本书。报价:

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

请在此处阅读完整说明。

替代文字
(来源:oreilly.com

作者: Julien Chastang 发布者: 2009 年 1 月 31 日

15

14134 作者的声誉

C ++模板具有Java泛型不具备的另一个特性是专业化。这允许您对特定类型具有不同的实现。例如,您可以为int提供高度优化的版本,同时仍然具有其他类型的通用版本。或者,您可以为指针和非指针类型使用不同的版本。如果你想在移动指针时操作解除引用的对象,这会派上用场。

作者: KeithB 发布者: 2009 年 1 月 31 日

0

1584 作者的声誉

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

例:

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

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

作者: MigMit 发布者: 2013 年 8 月 24 日

2

262158 作者的声誉

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

作者: user207421 发布者: 2016 年 1 月 31 日

1

529 作者的声誉

下面的答案来自Cracking The Coding Interview Solutions to Chapter 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参数必须从CardGame扩展。
  • 在C ++中,可以实例化type参数,而Java不支持此参数。
  • 在Java中,类型参数(即MyClass中的Foo)不能用于静态方法和变量,因为它们将在MyClass和MyClass之间共享。在C ++中,这些类是不同的,因此type参数可用于静态方法和变量。
  • 在Java中,MyClass的所有实例,无论其类型参数如何,都是相同的类型。类型参数在运行时被擦除。在C ++中,具有不同类型参数的实例是不同的类型。
作者: Jaycee 发布者: 2018 年 7 月 1 日
32x32