我主要使用Java,泛型相对较新。我一直读到Java做出了错误的决定,或者。net有更好的实现等等。

那么,c++, c#, Java在泛型上的主要区别是什么?利弊各有哪些?


当前回答

看起来,在其他非常有趣的建议中,有一个是关于改进泛型和打破向后兼容性的:

Currently, generics are implemented using erasure, which means that the generic type information is not available at runtime, which makes some kind of code hard to write. Generics were implemented this way to support backwards compatibility with older non-generic code. Reified generics would make the generic type information available at runtime, which would break legacy non-generic code. However, Neal Gafter has proposed making types reifiable only if specified, so as to not break backward compatibility.

Alex Miller关于Java 7提案的文章

其他回答

关于差异是什么,已经有很多很好的答案,所以让我给出一个稍微不同的角度,并补充为什么。

正如已经解释过的,主要的区别是类型擦除,即Java编译器擦除泛型类型,它们不会出现在生成的字节码中。然而,问题是:为什么会有人这么做呢?这说不通啊!真的是这样吗?

那么,还有什么选择呢?如果你不在语言中实现泛型,你在哪里实现它们呢?答案是:在虚拟机中。这打破了向后兼容性。

另一方面,类型擦除允许混合泛型客户端和非泛型库。换句话说:在Java 5上编译的代码仍然可以部署到Java 1.4。

然而,微软决定打破泛型的向后兼容性。这就是为什么。net泛型比Java泛型“更好”。

当然,孙不是白痴也不是懦夫。他们“退缩”的原因是,当他们引入泛型时,Java明显比。net更古老,更广泛。(它们在两个世界几乎同时被引入。)打破向后兼容性将是一个巨大的痛苦。

换句话说:在Java中,泛型是语言的一部分(这意味着它们只适用于Java,而不是其他语言),在。net中,它们是虚拟机的一部分(这意味着它们适用于所有语言,而不仅仅是c#和Visual Basic.NET)。

Compare this with .NET features like LINQ, lambda expressions, local variable type inference, anonymous types and expression trees: these are all language features. That's why there are subtle differences between VB.NET and C#: if those features were part of the VM, they would be the same in all languages. But the CLR hasn't changed: it's still the same in .NET 3.5 SP1 as it was in .NET 2.0. You can compile a C# program that uses LINQ with the .NET 3.5 compiler and still run it on .NET 2.0, provided that you don't use any .NET 3.5 libraries. That would not work with generics and .NET 1.1, but it would work with Java and Java 1.4.

最大的抱怨是字体擦除。在这种情况下,不在运行时强制执行泛型。这里有一些关于这个主题的Sun文档的链接。

泛型是按类型实现的 Erasure:泛型类型信息为 之后,仅在编译时出现 它被编译器擦除。

在Java中,泛型只是编译器级别的,所以你得到:

a = new ArrayList<String>()
a.getClass() => ArrayList

注意,'a'的类型是数组列表,而不是字符串列表。所以香蕉列表的类型等于()猴子列表。

可以这么说。

c++很少使用“泛型”术语。相反,使用“模板”这个词更准确。模板描述了实现通用设计的一种技术。

c++模板与c#和Java实现的模板非常不同,主要有两个原因。第一个原因是c++模板不仅允许编译时类型参数,还允许编译时常量值参数:模板可以作为整数甚至函数签名。这意味着你可以在编译时做一些非常奇怪的事情,例如计算:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

这段代码还使用了c++模板的另一个显著特性,即模板专门化。代码定义了一个类模板,product,它有一个值参数。它还为该模板定义了一个特化,每当参数的值为1时就使用该特化。这允许我在模板定义上定义递归。我相信这是安德烈·亚历山德雷斯库最先发现的。

模板专门化对于c++很重要,因为它允许数据结构的结构差异。模板作为一个整体是一种跨类型统一接口的方法。然而,尽管这是可取的,但在实现中不能平等对待所有类型。c++模板考虑到了这一点。这与OOP在覆盖虚方法的接口和实现之间的区别非常相似。

c++模板对于它的算法编程范型是必不可少的。例如,几乎所有容器的算法都定义为接受容器类型为模板类型并统一对待它们的函数。实际上,这并不完全正确:c++并不适用于容器,而是适用于由两个迭代器定义的范围,它们分别指向容器的开始部分和结束部分。因此,整个内容都由迭代器限定:begin <= elements < end。

使用迭代器而不是容器是有用的,因为它允许对容器的部分而不是整个进行操作。

c++的另一个显著特征是类模板的部分专门化。这在某种程度上与Haskell和其他函数式语言中参数的模式匹配有关。例如,让我们考虑一个存储元素的类:

template <typename T>
class Store { … }; // (1)

这适用于任何元素类型。但是我们假设,通过应用一些特殊的技巧,我们可以比其他类型更有效地存储指针。我们可以通过部分专门化所有指针类型来做到这一点:

template <typename T>
class Store<T*> { … }; // (2)

现在,每当我们为一种类型实例化容器模板时,都会使用适当的定义:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

Java和c#在它们的第一个语言版本之后都引入了泛型。然而,在引入泛型时,核心库的变化方式有所不同。c#的泛型不仅仅是编译器的魔法,因此不可能在不破坏向后兼容性的情况下泛化现有的库类。

例如,在Java中,现有的集合框架是完全泛化的。Java没有集合类的泛型版本和遗留的非泛型版本。在某种程度上,这要干净得多——如果你需要在c#中使用一个集合,那么使用非泛型版本的理由真的很少,但是那些遗留类仍然存在,使环境变得混乱。

另一个显著的区别是Java和c#中的Enum类。Java的Enum有这样一个看起来有点曲折的定义:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(见Angelika Langer对为什么会这样的解释非常清楚。从本质上讲,这意味着Java可以提供从字符串到Enum值的类型安全访问:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

比较一下c#版本:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

因为Enum在引入泛型之前就已经存在于c#中,所以如果不改变定义就会破坏现有的代码。因此,像集合一样,它以这种遗留状态保留在核心库中。