我主要使用Java,泛型相对较新。我一直在阅读Java做出了错误的决定,或者.NET具有更好的实现等。
那么,C ++,C#,Java在泛型之间的主要区别是什么?每个优点/缺点?
我将把声音添加到噪音中,并努力使事情变得清晰:
C#泛型允许你声明这样的内容。
List<Person> foo = new List<Person>();
然后编译器将阻止你将不在Person列表中的内容放入列表中。 在后台,C#编译器只是将其List<Person>放入.NET dll文件中,但是在运行时,JIT编译器会继续构建新的代码集,就好像你已经编写了一个用于容纳人员的特殊列表类一样- ListOfPerson。
List<Person>
ListOfPerson
这样做的好处是它使速度变得非常快。没有任何强制转换或其他任何东西,并且由于dll包含这是List的信息,Person以后在使用反射查看它的其他代码可以告诉它包含Person对象(因此你得到了intellisense等)。
Person
intellisense
缺点是旧的C#1.0和1.1代码(在添加泛型之前)不理解这些新内容List<something>,因此你必须手动将其转换回普通的旧内容List才能与它们进行互操作。这并不是什么大问题,因为C#2.0二进制代码不向后兼容。唯一会发生的情况是,如果要将某些旧的C#1.0 / 1.1代码升级到C#2.0
List<something>
Java泛型允许你声明这样的内容。
ArrayList<Person> foo = new ArrayList<Person>();
从表面上看,它看起来是一样的。编译器还会阻止你将不在Person列表中的内容放入列表中。
不同之处在于幕后发生的事情。与C#不同,Java并没有构建特殊的东西ListOfPerson-它只使用ArrayListJava中一直存在的普通样式。当你从阵列中取出东西时,Person p = (Person)foo.get(1);仍然必须执行通常的投射舞蹈。编译器可以节省你的按键操作,但仍然像以前一样,仍然会导致快速命中/广播。 当人们提到“类型删除”时,这就是他们在谈论的内容。编译器会为你插入强制类型转换,然后“擦除”以下事实:它Person不仅是列表Object
Person p = (Person)foo.get(1)
Object
这种方法的好处是不需要理解泛型的旧代码就不必在意了。它仍然ArrayList像以前一样处理旧的问题。这在Java世界中更为重要,因为他们希望使用带有泛型的Java 5支持编译代码,并使其在旧版1.4或以前的JVM上运行,Microsoft故意决定不使用它。
缺点是我前面提到的速度降低,也是因为没有ListOfPerson伪类或类似的东西进入.class文件,以后会对其进行查看的代码(带有反射,或者如果你将其拉出另一个集合)它在什么地方被转换为Object等等)无法以任何方式告诉它是仅包含Person而不是仅包含任何其他数组列表的列表。
C ++模板允许你声明这样的内容
std::list<Person>* foo = new std::list<Person>();
它看起来像C#和Java泛型,并且会按你认为的方式工作,但是在幕后发生了许多事情。
它与C#泛型的最共同之处在于,它建立特殊的结构,pseudo-classes而不仅仅是像Java一样扔掉类型信息,但这是一条完全不同的鱼。
pseudo-classes
C#和Java都产生针对虚拟机设计的输出。如果你编写其中包含Person类的某些代码,则在两种情况下,有关Person类的某些信息都将放入.dll或.class文件中,并且JVM / CLR将对此进行处理。
.dll
.class
C ++生成原始的x86二进制代码。一切都不是对象,也没有底层虚拟机需要了解Person类。没有装箱或拆箱,功能不必属于类,甚至不属于任何东西。
因此,C ++编译器对模板的使用没有任何限制-基本上可以手动编写的任何代码,都可以为你编写模板。 最明显的示例是添加内容:
在C#和Java中,泛型系统需要知道可用于类的方法,并将其传递给虚拟机。告诉它的唯一方法是通过对实际的类进行硬编码或使用接口。例如:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
该代码不会在C#或Java中编译,因为它不知道该类型T实际上提供了一个称为Name()的方法。你必须告诉它-在C#中是这样的:
interface IHasName{ string Name(); }; string addNames<T>( T first, T second ) where T : IHasName { .... }
然后,你必须确保传递给addNames的东西实现IHasName接口,依此类推。Java语法不同(),但存在相同的问题。
此问题的“经典”情况是试图编写一个执行此操作的函数
string addNames<T>( T first, T second ) { return first + second; }
你实际上无法编写此代码,因为无法使用其中的+方法声明接口。你失败了。
C ++不会遇到这些问题。编译器不在乎将类型传递给任何VM-如果两个对象都具有.Name()函数,它将进行编译。如果他们不这样做,那就不会。简单。