我主要使用Java,泛型相对较新。我一直读到Java做出了错误的决定,或者.NET拥有更好的实现等。每个优点/缺点?
#1 楼
我会大声疾呼,并努力使事情变得清晰:C#泛型允许您声明这样的内容。
List<Person> foo = new List<Person>();
,然后编译器将阻止您放置不在列表中的内容。
在后台,C#编译器只是将
Person
放入.NET dll文件中,但是在运行时,JIT编译器会去构建新的代码集,就像您已经编写了一个仅用于容纳人员的特殊列表类-诸如List<Person>
之类。没有任何强制转换或其他任何东西,并且由于dll包含这是ListOfPerson
列表的信息,以后在使用反射查看它的其他代码可以告诉它包含Person
对象(因此您会得到intellisense等)。 缺点是旧的C#1.0和1.1代码(在添加泛型之前)不理解这些新的
Person
,因此您必须手动将其转换回普通的旧List<something>
才能与它们进行互操作。这并不是什么大问题,因为C#2.0二进制代码不向后兼容。唯一会发生这种情况的情况是,如果要将一些旧的C#1.0 / 1.1代码升级到C#2.0。看起来一样,而且有点像。编译器还会阻止您将非List
的内容放到列表中。区别在于幕后情况。与C#不同,Java不会构建特殊的
Person
-它仅使用Java中一直存在的普通ListOfPerson
。当您从阵列中取出数据时,仍然必须执行常规的ArrayList
转换舞蹈。编译器可以节省您的按键操作,但仍然像以前一样,仍然可以快速实现点击/播放。当人们提到“类型删除”时,这就是他们在谈论的内容。编译器会为您插入强制类型转换,然后“消除”它是Person p = (Person)foo.get(1);
而不只是Person
列表的事实这种方法的好处是,不了解泛型的旧代码没有照顾。它仍然像以前一样处理旧的
Object
。这在Java世界中更为重要,因为他们希望支持使用Java 5和泛型来编译代码,并使其运行在旧版1.4或以前的JVM上,而Microsoft故意决定不打扰。缺点是速度太快就像我之前提到的那样,也是因为没有
ArrayList
伪类或.class文件中的类似内容,以后会对其进行查看的代码(带有反射,或者如果将其从另一个已转换的集合中拉出进入ListOfPerson
等),无论如何都不能说它只是一个仅包含Object
而不是其他数组列表的列表。C ++模板允许您声明类似这样的内容
ArrayList<Person> foo = new ArrayList<Person>();
它看起来像C#和Java泛型,并且会按照您认为的方式工作,但是在幕后却发生了许多不同的事情。
它与C#泛型的最共同之处在于它构建了特殊的q4312079而不是仅仅把类型信息扔掉ke java确实可以,但是那完全是鱼缸。
C#和Java都产生针对虚拟机设计的输出。如果您编写了其中包含
Person
类的代码,则在两种情况下,有关pseudo-classes
类的一些信息都将放入.dll或.class文件中,并且JVM / CLR将对此进行处理。C ++生成原始的x86二进制代码。一切都不是对象,也没有底层虚拟机需要了解
Person
类。没有装箱或拆箱,功能不必属于类,甚至不属于任何东西。因此,C ++编译器对模板的操作没有任何限制-基本上您可以手动编写的任何代码,都可以为您编写模板。
最明显的示例是添加内容:
在C#和Java中,泛型系统需要知道可用于类的方法,并将其传递给虚拟机。告诉它的唯一方法是通过对实际的类进行硬编码或使用接口。例如:
std::list<Person>* foo = new std::list<Person>();
该代码无法在C#或Java中编译,因为它不知道类型
Person
实际上提供了一个名为Name()的方法。您必须告诉它-在C#中是这样的:string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
然后必须确保传递给addNames的东西实现IHasName接口,依此类推。 Java语法不同(
Person
),但是存在相同的问题。此问题的“经典”情况是试图编写一个执行此操作的函数
实际编写此代码是因为无法使用
T
方法声明接口。您会失败。C ++不会遇到这些问题。编译器不在乎将类型传递给任何VM-如果两个对象都具有.Name()函数,它将进行编译。如果他们不这样做,那就不会。很简单。
所以,您可以找到它:-)
#2 楼
C ++很少使用“泛型”术语。取而代之的是使用“模板”一词,并且更加准确。模板描述了一种用于实现通用设计的技术。C++模板与C#和Java所实现的模板有很大不同,这有两个主要原因。第一个原因是C ++模板不仅允许编译时类型参数,还允许编译时const-value参数:模板可以以整数或函数签名的形式给出。这意味着您可以在编译时做一些非常时髦的事情,例如计算: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
,该模板具有一个value参数。它还为该模板定义了一种特殊化,只要参数的计算结果为1,就会使用该特殊化。这使我可以定义模板定义的递归。我相信这是Andrei Alexandrescu首次发现的。模板专用化对C ++很重要,因为它允许数据结构的结构差异。模板作为一个整体是一种统一类型间接口的方法。但是,尽管这是理想的,但是在实现中不能平等地对待所有类型。 C ++模板考虑了这一点。这与OOP在接口和实现之间以及使用虚方法的覆盖之间的差异几乎相同。
C ++模板对其算法编程范例至关重要。例如,几乎所有容器算法都被定义为接受容器类型作为模板类型并对其进行统一处理的函数。实际上,这并不完全正确:C ++不适用于容器,但不能用于由两个迭代器定义的范围,这些范围指向容器的开始和结尾。因此,整个内容由迭代器限制:begin <= elements
使用迭代器而不是容器很有用,因为它允许在容器的一部分上而不是整体上进行操作。这在某种程度上与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*.
评论
有时我希望.net中的泛型功能可以允许将类型以外的内容用作键。如果值类型数组是框架的一部分(令我惊讶的是,考虑到需要与将固定大小的数组嵌入结构中的旧API进行交互的某种方式,它们就没有了),则声明类,其中包含一些单独的项,然后是一个值类型数组,其大小是通用参数。实际上,最接近的对象是拥有一个类对象,该类对象保存各个项目,然后还保存对包含数组的单独对象的引用。
–超级猫
13年1月6日在18:20
@supercat如果您与旧版API交互,则其想法是使用编组(可通过属性进行注释)。 CLR始终没有固定大小的数组,因此在这里使用非类型模板参数将毫无帮助。
–康拉德·鲁道夫(Konrad Rudolph)
2013年1月6日19:27
我想我感到困惑的是,看起来像固定大小的值类型数组应该不难,而且它允许许多数据类型按引用而不是按值编组。虽然按值编组在真正无法以其他任何方式处理的情况下很有用,但我认为在几乎所有可用的情况下,按引用编组都是上乘的,因此允许此类情况包含固定结构大小的数组似乎是一个有用的功能。
–超级猫
13年1月6日在20:35
顺便说一句,非类型通用参数将有用的另一种情况是代表尺寸标注数量的数据类型。可以在表示数量的实例中包括尺寸信息,但是将此类信息包含在类型内将允许您指定一个集合应该保存代表特定尺寸单位的对象。
–超级猫
13年1月6日在20:39
#3 楼
Anders Hejlsberg自己在这里描述了差异“ C#,Java和C ++中的泛型”。评论
我真的很喜欢那次采访。对于像我这样的非c#人来说,这很清楚c#泛型发生了什么。
– Johannes Schaub-小人
09年4月12日在1:38
#4 楼
关于它们之间的区别已经有了很多好的答案,所以让我给出一个略有不同的观点并添加原因。正如已经解释过的,主要区别在于类型擦除,即事实Java编译器会删除通用类型,并且它们不会以生成的字节码结尾。但是,问题是:为什么有人会这样做?没道理!还是呢?
那么,还有什么选择?如果您不使用该语言实现泛型,那么在哪里实现它们?答案是:在虚拟机中。另一方面,这破坏了向后兼容性。
类型擦除允许您将通用客户端与非通用库混合使用。换句话说:使用Java 5编译的代码仍然可以部署到Java 1.4。
Microsoft决定打破对泛型的向后兼容性。这就是.NET泛型比Java泛型“更好”的原因。
当然,Sun不是白痴或胆小鬼。他们之所以“大吃一惊”,是因为Java在引入泛型时比.NET明显更老,而且更广泛。 (它们在两个世界中几乎同时被引入。)向后兼容将是一个巨大的痛苦。
采用另一种方式:在Java中,泛型是Language的一部分(这意味着它们仅适用于Java,不适用于其他语言),在.NET中它们是虚拟机的一部分(这意味着它们适用于所有语言,而不仅是C#和Visual Basic.NET)。
将此与LINQ,lambda表达式,局部变量类型推断,匿名类型和表达式树等.NET功能进行比较:这些都是语言功能。这就是VB.NET和C#之间存在细微差异的原因:如果这些功能是VM的一部分,则所有语言的功能都相同。但是CLR并没有改变:.NET 3.5 SP1中的内容与.NET 2.0中的内容相同。您可以编译使用LINQ和.NET 3.5编译器的C#程序,并且只要不使用任何.NET 3.5库,就可以在.NET 2.0上运行它。不适用于泛型和.NET 1.1,但适用于Java和Java 1.4。
评论
LINQ主要是一种库功能(尽管C#和VB还在其旁边添加了语法糖)。只需加载System.Core程序集,任何针对2.0 CLR的语言都可以充分利用LINQ。
–理查德·伯格
09年7月30日在20:53
是的,对不起,我应该更加清楚。 LINQ。我指的是查询语法,而不是单子标准查询运算符,LINQ扩展方法或IQueryable接口。显然,您可以使用任何.NET语言中的语言。
–Jörg W Mittag
09年7月30日在21:26
我在考虑Java的另一种选择。即使Oracle也不希望破坏向后兼容性,它们仍然可以做出一些编译器技巧,以避免擦除类型信息。例如,可以将ArrayList
–地球引擎
2012年10月12日,0:14
#5 楼
我上一篇文章的后续文章。模板是C ++在智能感知中如此失败的主要原因之一,而与所使用的IDE无关。由于模板的特殊性,IDE永远无法真正确定给定成员是否存在。考虑:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
现在,光标在指示的位置上,并且该死的IDE很难说成员
a
是否以及拥有什么。对于其他语言,解析将很简单,但对于C ++,则需要事先进行大量评估。情况变得更糟。如果还在类模板中定义
my_int_type
怎么办?现在,它的类型将取决于另一个类型参数。在这里,甚至编译器也会失败。 Y<int>::my_type
应该和int
类型相同,对吧?错误。在编译器尝试解析此语句时,它实际上还不知道
b
!因此,它不知道这是一种类型。可能是其他情况,例如成员函数或字段。这可能会引起歧义(尽管在当前情况下不是),因此编译器将失败。我们必须明确地告诉我们,我们引用的是类型名称:template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
现在,代码可以编译了。要了解这种情况下如何产生歧义,请考虑以下代码:但是,如果a
不是函数而是类型,则此语句仍然有效并执行特殊的强制转换(函数样式强制转换),通常是构造函数调用。编译器无法分辨出我们的意思,因此我们必须在这里消除歧义。评论
我非常同意。不过还是有希望的。自动完成系统和C ++编译器必须非常紧密地交互。我很确定Visual Studio永远不会有这样的功能,但是事情可能会在Eclipse / CDT或其他基于GCC的IDE中发生。希望! :)
–贝诺
09年2月19日在10:48
#6 楼
Java和C#在其第一种语言发行后都引入了泛型。但是,引入泛型时,核心库的更改方式有所不同。 C#的泛型不仅仅是编译器的魔力,因此不可能在不破坏向后兼容性的情况下生成现有的库类。例如,在Java中,现有的Collections Framework是完全泛化的。 Java没有集合类的通用和遗留非通用版本。在某些方面,这更清洁-如果您需要在C#中使用集合,则确实没有什么理由要使用非泛型版本,但是那些旧类仍然存在,从而使情况更加混乱。另一个显着区别是Java和C#中的Enum类。 Java的Enum具有以下曲折的定义:从字符串到其Enum值:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
将此与C#的版本进行比较:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
作为Enum在将泛型引入该语言之前,该语言已经存在于C#中,因此在不破坏现有代码的情况下不能更改该定义。因此,与集合一样,它以这种旧状态保留在核心库中。
评论
甚至C#的泛型不只是编译器魔术,编译器还可以做进一步的魔术来泛化现有的库。他们没有理由需要将ArrayList重命名为List
–地球引擎
2012年10月12日,0:24
#7 楼
已经晚了11个月,但是我认为这个问题已经可以解决Java Wildcard的问题。这是Java的语法功能。假设您有一个方法:
public <T> void Foo(Collection<T> thing)
并且假设您不需要在方法主体中引用类型T。您先声明一个名称T,然后只使用一次,那么为什么还要考虑一个名称呢?相反,您可以编写:
public void Foo(Collection<?> thing)
问号要求编译器假装您声明了一个普通的命名类型参数,该参数仅需在该位置出现一次。
使用通配符不能做的事,也不能使用命名类型参数来进行通配符(这是在C ++和C#中总是如此的方式)。
评论
再晚11个月...使用Java通配符可以执行某些操作,而使用命名类型参数则不能。您可以在Java中执行此操作:类Foo
– R. Martinho Fernandes
2010年6月4日14:00
#8 楼
Wikipedia具有比较Java / C#泛型和Java泛型/ C ++模板的出色文章。有关泛型的主要文章似乎有些混乱,但确实有一些不错的信息。#9 楼
最大的抱怨是类型擦除。因此,泛型不会在运行时强制执行。这是一些有关该主题的Sun文档的链接。泛型通过类型实现
擦除:泛型类型信息仅在编译时出现,在
被编译器擦除。
#10 楼
实际上,C ++模板比C#和Java模板强大得多,因为它们是在编译时进行评估并支持专业化的。这允许进行模板元编程,并使C ++编译器等效于Turing机器(即在编译过程中,您可以计算Turing机器可计算的任何内容)。#11 楼
在Java中,泛型仅是编译器级别的,因此您将得到:因此,香蕉列表的类型将等于()猴子列表。可以这么说。
#12 楼
看起来,在其他一些非常有趣的提议中,有一个关于完善泛型和打破向后兼容性的提议:当前,泛型是使用擦除实现的,这意味着
通用类型信息在运行时不可用,这使得某些类型的代码难以编写。泛型
是通过这种方式实现的,以支持
与较早的
非泛型代码的向后兼容性。修饰的泛型
将使泛型类型信息在运行时可用,这将破坏传统的非泛型代码。但是,尼尔·格夫特(Neal Gafter)提出了使类型变为仅可修正的类型(
),以免破坏向后兼容性。 Java 7提案
#13 楼
NB:我没有足够的意见要发表,所以请随意将其作为对适当答案的评论。真正的泛型而又不破坏向后兼容性,因此他们为此付出了明显的努力。您不必将非泛型.net 1.0代码更改为仅可在.net 2.0中使用的泛型。 .Net Framework 2.0甚至在4.0之前,通用列表和非通用列表都仍然可用,完全是出于向后兼容的原因。因此,仍然使用非通用ArrayList的旧代码仍然可以使用,并且使用与以前相同的ArrayList类。
从1.0至今一直保持向后代码兼容性...因此,即使在.net 4.0中,如果您愿意,可以选择使用1.0 BCL中的任何非泛型类。
因此,我认为Java不必打破向后兼容性以支持真正的泛型。
评论
人们谈论的不是那种向后兼容。这个想法是对运行时的向后兼容性:在.NET 2.0中使用泛型编写的代码无法在.NET Framework / CLR的较早版本上运行。同样,如果Java要引入“真正的”泛型,则新的Java代码将无法在较旧的JVM上运行(因为它需要中断对字节码的更改)。
– tzaman
2010年8月3日,17:36
那是.net,不是通用名称。始终需要重新编译才能定位特定的CLR版本。有字节码兼容性,有代码兼容性。而且,我是专门针对需要转换使用旧List的旧代码以使用新的泛型List的,这是完全不正确的。
–Sheepy
2010年8月3日,18:05
我认为人们正在谈论向前兼容。即在.net 1.1上运行的.net 2.0代码,该代码会中断,因为1.1运行时对2.0“伪类”一无所知。难道不是因为“ Java没有实现真正的泛型是因为他们想保持向前的兼容性”吗? (而不是向后)
–Sheepy
2010年8月3日,18:14
兼容性问题很微妙。我认为问题不在于在Java中添加“真正的”泛型会影响使用旧版Java的任何程序,但是使用“新改进的”泛型的代码将很难与此类旧版本代码交换此类对象。对新类型一无所知。例如,假设程序有一个ArrayList
–超级猫
2013年1月6日18:25
评论
为C#中的引用类型生成的伪类具有相同的实现,因此您将不会获得确切的ListOfPeople。查看blogs.msdn.com/ericlippert/archive/2009/07/30/…
– Piotr Czapla
09年8月13日在6:28
不,您不能使用泛型编译Java 5代码,并且不能在旧的1.4 VM上运行Java 5代码(至少Sun JDK不能实现此功能。某些第三方工具可以这样做。)您可以使用以前编译的1.4 JAR 1.5 / 1.6代码。
– Finnw
2010-2-3在17:13
我反对不能写int addNames
–马什加
11年7月20日在13:24
@AlexanderMalakhov这不是故意的。重点不是要教育C ++的习语,而是要说明每种语言如何以不同的方式处理看似相同的代码。这个目标本来很难实现更多不同的代码
– Orion Edwards
2012年6月4日21:15
@phresnel我原则上同意,但是如果我用惯用的C ++编写该代码段,那么C#/ Java开发人员将很难理解它,因此(我相信)在解释这种差异方面做得更糟。让我们不同意这一点:-)
– Orion Edwards
13年3月25日在20:23