C# 和 Java 中的泛型与 C++ 中的模板有什么区别?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31693/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the differences between Generics in C# and Java... and Templates in C++?
提问by pek
I mostly use Java and generics are relatively new. I keep reading that Java made the wrong decision or that .NET has better implementations etc. etc.
我主要使用 Java,泛型相对较新。我一直读到 Java 做出了错误的决定,或者 .NET 有更好的实现等等。
So, what are the main differences between C++, C#, Java in generics? Pros/cons of each?
那么,C++、C#、Java 在泛型方面的主要区别是什么?每个的优点/缺点?
采纳答案by Orion Edwards
I'll add my voice to the noise and take a stab at making things clear:
我会将我的声音添加到噪音中,并尝试将事情弄清楚:
C# Generics allow you to declare something like this.
C#泛型允许你声明这样的东西。
List<Person> foo = new List<Person>();
and then the compiler will prevent you from putting things that aren't Person
into the list.
Behind the scenes the C# compiler is just putting List<Person>
into the .NET dll file, but at runtime the JIT compiler goes and builds a new set of code, as if you had written a special list class just for containing people - something like ListOfPerson
.
然后编译器会阻止你把不在Person
列表中的东西放进去。
在幕后,C# 编译器只是将List<Person>
.NET dll 文件放入,但在运行时,JIT 编译器会开始构建一组新代码,就好像您编写了一个专门用于包含人员的特殊列表类 - 类似ListOfPerson
.
The benefit of this is that it makes it really fast. There's no casting or any other stuff, and because the dll contains the information that this is a List of Person
, other code that looks at it later on using reflection can tell that it contains Person
objects (so you get intellisense and so on).
这样做的好处是它使它变得非常快。没有强制转换或任何其他内容,并且因为 dll 包含了这是一个 List of 的信息,Person
所以稍后使用反射查看它的其他代码可以判断它包含Person
对象(因此您可以获得智能感知等)。
The downside of this is that old C# 1.0 and 1.1 code (before they added generics) doesn't understand these new List<something>
, so you have to manually convert things back to plain old List
to interoperate with them. This is not that big of a problem, because C# 2.0 binary code is not backwards compatible. The only time this will ever happen is if you're upgrading some old C# 1.0/1.1 code to C# 2.0
这样做的缺点是旧的 C# 1.0 和 1.1 代码(在添加泛型之前)无法理解这些 new List<something>
,因此您必须手动将内容转换回普通旧代码List
以与它们进行互操作。这不是什么大问题,因为 C# 2.0 二进制代码不向后兼容。唯一会发生这种情况的是,如果您将一些旧的 C# 1.0/1.1 代码升级到 C# 2.0
Java Generics allow you to declare something like this.
Java泛型允许你声明这样的东西。
ArrayList<Person> foo = new ArrayList<Person>();
On the surface it looks the same, and it sort-of is. The compiler will also prevent you from putting things that aren't Person
into the list.
从表面上看,它看起来是一样的,实际上是一样的。编译器还会阻止您将不在Person
列表中的内容放入列表中。
The difference is what happens behind the scenes. Unlike C#, Java does not go and build a special ListOfPerson
- it just uses the plain old ArrayList
which has always been in Java. When you get things out of the array, the usual Person p = (Person)foo.get(1);
casting-dance still has to be done. The compiler is saving you the key-presses, but the speed hit/casting is still incurred just like it always was.
When people mention "Type Erasure" this is what they're talking about. The compiler inserts the casts for you, and then 'erases' the fact that it's meant to be a list of Person
not just Object
不同之处在于幕后发生的事情。与 C# 不同的是,Java 不会去构建一个特殊的东西ListOfPerson
——它只是使用ArrayList
Java 中一直存在的普通旧代码。当你从数组中取出东西时,Person p = (Person)foo.get(1);
仍然需要完成通常的铸造舞。编译器为您节省了按键次数,但仍然会像往常一样产生命中/投射速度。
当人们提到“类型擦除”时,这就是他们所谈论的内容。该编译器插入蒙上了你,然后“擦除”的事实,它的意思是名单Person
不只是Object
The benefit of this approach is that old code which doesn't understand generics doesn't have to care. It's still dealing with the same old ArrayList
as it always has. This is more important in the java world because they wanted to support compiling code using Java 5 with generics, and having it run on old 1.4 or previous JVM's, which microsoft deliberately decided not to bother with.
这种方法的好处是不理解泛型的旧代码不必关心。它仍然ArrayList
像往常一样处理旧的。这在 Java 世界中更为重要,因为他们希望支持使用带有泛型的 Java 5 编译代码,并让它在旧的 1.4 或以前的 JVM 上运行,微软故意决定不打扰。
The downside is the speed hit I mentioned previously, and also because there is no ListOfPerson
pseudo-class or anything like that going into the .class files, code that looks at it later on (with reflection, or if you pull it out of another collection where it's been converted into Object
or so on) can't tell in any way that it's meant to be a list containing only Person
and not just any other array list.
缺点是我之前提到的速度下降,而且因为没有ListOfPerson
伪类或类似的东西进入 .class 文件,稍后查看它的代码(通过反射,或者如果你把它从另一个集合中拉出来它被转换成Object
或等等)不能以任何方式告诉它意味着它是一个只包含Person
而不只包含任何其他数组列表的列表。
C++ Templates allow you to declare something like this
C++ 模板允许你声明这样的东西
std::list<Person>* foo = new std::list<Person>();
It looks like C# and Java generics, and it will do what you think it should do, but behind the scenes different things are happening.
它看起来像 C# 和 Java 泛型,它会做你认为它应该做的事情,但在幕后发生了不同的事情。
It has the most in common with C# generics in that it builds special pseudo-classes
rather than just throwing the type information away like java does, but it's a whole different kettle of fish.
它与 C# 泛型最大的共同点在于它构建了特殊的pseudo-classes
而不是像 java 那样仅仅丢弃类型信息,但它是一个完全不同的鱼。
Both C# and Java produce output which is designed for virtual machines. If you write some code which has a Person
class in it, in both cases some information about a Person
class will go into the .dll or .class file, and the JVM/CLR will do stuff with this.
C# 和 Java 都产生专为虚拟机设计的输出。如果您编写一些包含Person
类的代码,在这两种情况下,有关Person
类的一些信息将进入 .dll 或 .class 文件,JVM/CLR 将对此进行处理。
C++ produces raw x86 binary code. Everything is notan object, and there's no underlying virtual machine which needs to know about a Person
class. There's no boxing or unboxing, and functions don't have to belong to classes, or indeed anything.
C++ 生成原始 x86 二进制代码。一切都不是对象,并且没有需要了解Person
类的底层虚拟机。没有装箱或拆箱,函数也不必属于类,甚至任何东西。
Because of this, the C++ compiler places no restrictions on what you can do with templates - basically any code you could write manually, you can get templates to write for you.
The most obvious example is adding things:
因此,C++ 编译器对您可以使用模板执行的操作没有任何限制——基本上任何您可以手动编写的代码,您都可以获得模板来为您编写。
最明显的例子是添加东西:
In C# and Java, the generics system needs to know what methods are available for a class, and it needs to pass this down to the virtual machine. The only way to tell it this is by either hard-coding the actual class in, or using interfaces. For example:
在 C# 和 Java 中,泛型系统需要知道类有哪些方法可用,并且需要将其传递给虚拟机。告诉它这一点的唯一方法是通过硬编码实际类或使用接口。例如:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
That code won't compile in C# or Java, because it doesn't know that the type T
actually provides a method called Name(). You have to tell it - in C# like this:
该代码不会在 C# 或 Java 中编译,因为它不知道该类型T
实际上提供了一个名为 Name() 的方法。你必须告诉它 - 在 C# 中是这样的:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
And then you have to make sure the things you pass to addNames implement the IHasName interface and so on. The java syntax is different (<T extends IHasName>
), but it suffers from the same problems.
然后你必须确保你传递给 addNames 的东西实现了 IHasName 接口等等。java 语法是不同的 ( <T extends IHasName>
),但它遇到相同的问题。
The 'classic' case for this problem is trying to write a function which does this
此问题的“经典”案例是尝试编写一个执行此操作的函数
string addNames<T>( T first, T second ) { return first + second; }
You can't actually write this code because there are no ways to declare an interface with the +
method in it. You fail.
您实际上无法编写此代码,因为无法使用其中的+
方法声明接口。你失败了。
C++ suffers from none of these problems. The compiler doesn't care about passing types down to any VM's - if both your objects have a .Name() function, it will compile. If they don't, it won't. Simple.
C++ 没有这些问题。编译器不关心将类型传递给任何虚拟机——如果你的两个对象都有一个 .Name() 函数,它就会编译。如果他们不这样做,就不会。简单的。
So, there you have it :-)
所以你有它 :-)
回答by travis
Wikipedia has great write-ups comparing both Java/C# genericsand Java generics/C++templates. The main article on Genericsseems a bit cluttered but it does have some good info in it.
维基百科有很多比较Java/C# 泛型和Java 泛型/C++模板的文章。关于泛型的主要文章似乎有点混乱,但它确实有一些很好的信息。
回答by jfs
Anders Hejlsberg himself described the differences here "Generics in C#, Java, and C++".
Anders Hejlsberg 自己在“ C#、Java 和 C++ 中的泛型”中描述了不同之处。
回答by Matt Cummings
The biggest complaint is type erasure. In that, generics are not enforced at runtime. Here's a link to some Sun docs on the subject.
最大的抱怨是类型擦除。在这种情况下,泛型不是在运行时强制执行的。 这是有关该主题的一些 Sun 文档的链接。
Generics are implemented by type erasure: generic type information is present only at compile time, after which it is erased by the compiler.
泛型通过类型擦除实现:泛型类型信息仅在编译时出现,之后由编译器擦除。
回答by On Freund
C++ templates are actually much more powerful than their C# and Java counterparts as they are evaluated at compile time and support specialization. This allows for Template Meta-Programming and makes the C++ compiler equivalent to a Turing machine (i.e. during the compilation process you can compute anything that is computable with a Turing machine).
C++ 模板实际上比它们的 C# 和 Java 模板强大得多,因为它们在编译时进行评估并支持专业化。这允许进行模板元编程并使 C++ 编译器等同于图灵机(即在编译过程中,您可以计算任何可以用图灵机计算的东西)。
回答by Konrad Rudolph
C++ rarely uses the “generics” terminology. Instead, the word “templates” is used and is more accurate. Templates describes one techniqueto achieve a generic design.
C++ 很少使用“泛型”术语。相反,使用了“模板”这个词,并且更准确。模板描述了一个技术,实现了通用的设计。
C++ templates is very different from what both C# and Java implement for two main reasons. The first reason is that C++ templates don't only allow compile-time type arguments but also compile-time const-value arguments: templates can be given as integers or even function signatures. This means that you can do some quite funky stuff at compile time, e.g. calculations:
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;
This code also uses the other distinguished feature of C++ templates, namely template specialization. The code defines one class template, product
that has one value argument. It also defines a specialization for that template that is used whenever the argument evaluates to 1. This allows me to define a recursion over template definitions. I believe that this was first discovered by Andrei Alexandrescu.
这段代码还使用了 C++ 模板的另一个显着特性,即模板特化。该代码定义了一个类模板,product
该模板具有一个值参数。它还为该模板定义了一个特化,每当参数计算为 1 时就使用该特化。这允许我定义模板定义上的递归。我相信这是由Andrei Alexandrescu首次发现的。
Template specialization is important for C++ because it allows for structural differences in data structures. Templates as a whole is a means of unifying an interface across types. However, although this is desirable, all types cannot be treated equally inside the implementation. C++ templates takes this into account. This is very much the same difference that OOP makes between interface and implementation with the overriding of virtual methods.
模板特化对于 C++ 很重要,因为它允许数据结构中的结构差异。模板作为一个整体是跨类型统一接口的一种手段。然而,尽管这是可取的,但在实现中不能平等对待所有类型。C++ 模板考虑到了这一点。这与 OOP 通过覆盖虚拟方法在接口和实现之间产生的差异非常相似。
C++ templates are essential for its algorithmic programming paradigm. For example, almost all algorithms for containers are defined as functions that accept the container type as a template type and treat them uniformly. Actually, that's not quite right: C++ doesn't work on containers but rather on rangesthat are defined by two iterators, pointing to the beginning and behind the end of the container. Thus, the whole content is circumscribed by the iterators: begin <= elements < end.
C++ 模板对其算法编程范式至关重要。例如,几乎所有的容器算法都定义为接受容器类型作为模板类型并统一对待它们的函数。实际上,这并不完全正确:C++ 不适用于容器,而是适用于由两个迭代器定义的范围,指向容器的开头和结尾。因此,整个内容由迭代器限定:begin <= elements < end。
Using iterators instead of containers is useful because it allows to operate on parts of a container instead of on the whole.
使用迭代器代替容器很有用,因为它允许对容器的一部分而不是整体进行操作。
Another distinguishing feature of C++ is the possibility of partial specializationfor class templates. This is somewhat related to pattern matching on arguments in Haskell and other functional languages. For example, let's consider a class that stores elements:
C++ 的另一个显着特征是类模板的部分特化的可能性。这在某种程度上与 Haskell 和其他函数式语言中参数的模式匹配有关。例如,让我们考虑一个存储元素的类:
template <typename T>
class Store { … }; // (1)
This works for any element type. But let's say that we can store pointers more effciently than other types by applying some special trick. We can do this by partiallyspecializing for all pointer types:
这适用于任何元素类型。但是假设通过应用一些特殊的技巧,我们可以比其他类型更有效地存储指针。我们可以通过部分专用于所有指针类型来做到这一点:
template <typename T>
class Store<T*> { … }; // (2)
Now, whenever we instance a container template for one type, the appropriate definition is used:
现在,每当我们为一种类型实例化容器模板时,都会使用适当的定义:
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
回答by izb
In Java, generics are compiler level only, so you get:
在 Java 中,泛型只是编译器级别的,所以你得到:
a = new ArrayList<String>()
a.getClass() => ArrayList
Note that the type of 'a' is an array list, not a list of strings. So the type of a list of bananas would equal() a list of monkeys.
请注意,'a' 的类型是一个数组列表,而不是一个字符串列表。因此,香蕉列表的类型将等于() 猴子列表。
So to speak.
可以这么说。
回答by Konrad Rudolph
Follow-up to my previous posting.
跟进我之前的帖子。
Templates are one of the main reasons why C++ fails so abysmally at intellisense, regardless of the IDE used. Because of template specialization, the IDE can never be really sure if a given member exists or not. Consider:
无论使用何种 IDE,模板是 C++ 在智能感知方面如此失败的主要原因之一。由于模板专业化,IDE 永远无法真正确定给定成员是否存在。考虑:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
Now, the cursor is at the indicated position and it's damn hard for the IDE to say at that point if, and what, members a
has. For other languages the parsing would be straightforward but for C++, quite a bit of evaluation is needed beforehand.
现在,光标位于指示的位置,IDE 很难在此时说出成员a
是否拥有以及成员拥有什么。对于其他语言,解析会很简单,但对于 C++,需要事先进行大量评估。
It gets worse. What if my_int_type
were defined inside a class template as well? Now its type would depend on another type argument. And here, even compilers fail.
它变得更糟。如果my_int_type
也在类模板中定义会怎样?现在它的类型将取决于另一个类型参数。在这里,甚至编译器也会失败。
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
After a bit of thinking, a programmer would conclude that this code is the same as the above: Y<int>::my_type
resolves to int
, therefore b
should be the same type as a
, right?
经过一番思考,程序员会得出结论,这段代码与上面的代码相同:Y<int>::my_type
解析为int
,因此b
应该与 类型相同a
,对吗?
Wrong. At the point where the compiler tries to resolve this statement, it doesn't actually know Y<int>::my_type
yet! Therefore, it doesn't know that this is a type. It could be something else, e.g. a member function or a field. This might give rise to ambiguities (though not in the present case), therefore the compiler fails. We have to tell it explicitly that we refer to a type name:
错误的。在编译器尝试解析此语句时,它实际上Y<int>::my_type
还不知道!因此,它不知道这是一个类型。它可以是其他东西,例如成员函数或字段。这可能会引起歧义(尽管在当前情况下不是这样),因此编译器会失败。我们必须明确地告诉它我们引用了一个类型名称:
X<typename Y<int>::my_type> b;
Now, the code compiles. To see how ambiguities arise from this situation, consider the following code:
现在,代码编译。要了解这种情况如何产生歧义,请考虑以下代码:
Y<int>::my_type(123);
This code statement is perfectly valid and tells C++ to execute the function call to Y<int>::my_type
. However, if my_type
is not a function but rather a type, this statement would still be valid and perform a special cast (the function-style cast) which is often a constructor invocation. The compiler can't tell which we mean so we have to disambiguate here.
这段代码语句是完全有效的,它告诉 C++ 执行对Y<int>::my_type
. 但是,如果my_type
不是函数而是类型,则此语句仍然有效并执行特殊的强制转换(函数式强制转换),这通常是构造函数调用。编译器无法分辨我们的意思,所以我们必须在这里消除歧义。
回答by serg10
Both Java and C# introduced generics after their first language release. However, there are differences in how the core libraries changed when generics was introduced. C#'s generics are not just compiler magicand so it was not possible to generifyexisting library classes without breaking backwards compatibility.
Java 和 C# 在它们的第一个语言发布后都引入了泛型。但是,当引入泛型时,核心库的变化方式有所不同。 C# 的泛型不仅仅是编译器的魔法,因此不可能在不破坏向后兼容性的情况下对现有的库类进行泛型。
For example, in Java the existing Collections Frameworkwas completely genericised. Java does not have both a generic and legacy non-generic version of the collections classes.In some ways this is much cleaner - if you need to use a collection in C# there is really very little reason to go with the non-generic version, but those legacy classes remain in place, cluttering up the landscape.
例如,在 Java 中,现有的集合框架是完全通用的。 Java 没有集合类的泛型和遗留非泛型版本。在某些方面,这更清晰——如果您需要在 C# 中使用集合,那么使用非泛型版本确实没有什么理由,但那些遗留类仍然存在,使环境变得混乱。
Another notable difference is the Enum classes in Java and C#.Java's Enum has this somewhat tortuous looking definition:
另一个显着区别是 Java 和 C# 中的 Enum 类。Java 的 Enum 有一个看起来有些曲折的定义:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(see Angelika Langer's very clear explanation of exactly whythis is so. Essentially, this means Java can give type safe access from a string to its Enum value:
(请参阅 Angelika Langer对确切原因的非常清楚的解释。本质上,这意味着 Java 可以提供从字符串到其 Enum 值的类型安全访问:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
Compare this to C#'s version:
将此与 C# 的版本进行比较:
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
As Enum already existed in C# before generics was introduced to the language, the definition could not change without breaking existing code. So, like collections, it remains in the core libraries in this legacy state.
由于在将泛型引入语言之前 Enum 已经存在于 C# 中,因此在不破坏现有代码的情况下无法更改定义。因此,与集合一样,它在这种遗留状态下仍保留在核心库中。
回答by J?rg W Mittag
There are already a lot of good answers on whatthe differences are, so let me give a slightly different perspective and add the why.
关于差异是什么,已经有很多很好的答案,所以让我给出一个稍微不同的观点并添加原因。
As was already explained, the main difference is type erasure, i.e. the fact that the Java compiler erases the generic types and they don't end up in the generated bytecode. However, the question is: why would anyone do that? It doesn't make sense! Or does it?
正如已经解释过的,主要区别在于类型擦除,即 Java 编译器擦除泛型类型并且它们不会以生成的字节码结束。然而,问题是:为什么有人会这样做?这没有意义!或者是吗?
Well, what's the alternative? If you don't implement generics in the language, where doyou implement them? And the answer is: in the Virtual Machine. Which breaks backwards compatibility.
那么,有什么替代方案?如果不落实在语言的仿制药,在那里做你实现它们?答案是:在虚拟机中。这打破了向后兼容性。
Type erasure, on the other hand, allows you to mix generic clients with non-generic libraries. In other words: code that was compiled on Java 5 can still be deployed to Java 1.4.
另一方面,类型擦除允许您将通用客户端与非通用库混合使用。换句话说:在 Java 5 上编译的代码仍然可以部署到 Java 1.4。
Microsoft, however, decided to break backwards compatibility for generics. That'swhy .NET Generics are "better" than Java Generics.
然而,微软决定打破泛型的向后兼容性。这就是 .NET 泛型比 Java 泛型“更好”的原因。
Of course, Sun aren't idiots or cowards. The reason why they "chickened out", was that Java was significantly older and more widespread than .NET when they introduced generics. (They were introduced roughly at the same time in both worlds.) Breaking backwards compatibility would have been a huge pain.
当然,Sun 不是白痴或懦夫。他们之所以“退出”,是因为 Java 在引入泛型时比 .NET 更古老、更广泛。(它们在两个世界中大致同时被引入。)打破向后兼容性将是一个巨大的痛苦。
Put yet another way: in Java, Generics are a part of the Language(which means they apply onlyto Java, not to other languages), in .NET they are part of the Virtual Machine(which means they apply to alllanguages, not just C# and Visual Basic.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 languagefeatures. 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 alllanguages. 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 notwork with generics and .NET 1.1, but it wouldwork with Java and Java 1.4.
将此与 .NET 功能(如 LINQ、lambda 表达式、局部变量类型推断、匿名类型和表达式树)进行比较:这些都是语言功能。这就是 VB.NET 和 C# 之间存在细微差别的原因:如果这些功能是 VM 的一部分,那么它们在所有语言中都是相同的。但是 CLR 没有改变:它在 .NET 3.5 SP1 中仍然与在 .NET 2.0 中相同。您可以使用 .NET 3.5 编译器编译使用 LINQ 的 C# 程序,并且仍然可以在 .NET 2.0 上运行它,前提是您不使用任何 .NET 3.5 库。这将不使用泛型和.NET 1.1的工作,但它会与Java和Java 1.4的工作。