繁体   English   中英

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

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

Java 有泛型,C++ 提供了一个非常强大的带有template的编程模型。 那么,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 中的泛型编程并不是真正有用,它只是帮助新的 foreach 构造的一点语法糖。

编辑:上面关于有用性的意见是由年轻的自己写的。 Java 的泛型当然有助于类型安全。

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

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

这就是 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 (以及另一个)的基本比较

我也可以建议Thinking in Java 作为一名 C++ 程序员,很多像对象这样的概念已经成为第二天性,但存在细微差别,因此即使您略读部分,也值得拥有介绍性文本。

在学习 Java 时,您将学到的很多东西都是所有的库(包括标准的——JDK 中的内容——和非标准的,包括常用的东西,比如 Spring)。 Java 语法比 C++ 语法更冗长,并且没有很多 C++ 特性(例如运算符重载、多重继承、析构函数机制等),但这也不能严格使它成为 C++ 的子集。

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

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

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

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

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

Maurice Naftalin, Philip Wadler 在Java 泛型和集合中对此主题有很好的解释。 我强烈推荐这本书。 引用:

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

请在此处阅读完整说明。

替代文字
(来源: oreilly.com

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

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

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

来自: 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 不可能做到这一点。

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

@基思:

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

下面的答案来自Cracking The Coding Interview Solutions一书到第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 并指定类型参数必须从 CardGame 扩展。
  • 在 C++ 中,可以实例化类型参数,而 Java 不支持。
  • 在 Java 中,类型参数(即 MyClass 中的 Foo)不能用于静态方法和变量,因为它们将在 MyClass 和 MyClass 之间共享。 在 C++ 中,这些类是不同的,因此类型参数可用于静态方法和变量。
  • 在 Java 中,MyClass 的所有实例,无论它们的类型参数如何,都是相同的类型。 类型参数在运行时被擦除。 在 C++ 中,具有不同类型参数的实例是不同的类型。

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

示例:

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

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

我想在这里引用asanydifference

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM