我主要使用Java,泛型相对较新。我一直读到Java做出了错误的决定,或者。net有更好的实现等等。
那么,c++, c#, Java在泛型上的主要区别是什么?利弊各有哪些?
我主要使用Java,泛型相对较新。我一直读到Java做出了错误的决定,或者。net有更好的实现等等。
那么,c++, c#, Java在泛型上的主要区别是什么?利弊各有哪些?
当前回答
注:我没有足够的观点来评论,所以请随意将此作为评论移动到适当的答案。
Contrary to popular believe, which I never understand where it came from, .net implemented true generics without breaking backward compatibility, and they spent explicit effort for that. You don't have to change your non-generic .net 1.0 code into generics just to be used in .net 2.0. Both the generic and non-generic lists are still available in .Net framework 2.0 even until 4.0, exactly for nothing else but backward compatibility reason. Therefore old codes that still used non-generic ArrayList will still work, and use the same ArrayList class as before. Backward code compatibility is always maintained since 1.0 till now... So even in .net 4.0, you still have to option to use any non-generics class from 1.0 BCL if you choose to do so.
所以我不认为java必须打破向后兼容性来支持真正的泛型。
其他回答
Anders Hejlsberg自己在这里描述了“c#、Java和c++中的泛型”的区别。
在Java中,泛型只是编译器级别的,所以你得到:
a = new ArrayList<String>()
a.getClass() => ArrayList
注意,'a'的类型是数组列表,而不是字符串列表。所以香蕉列表的类型等于()猴子列表。
可以这么说。
11个月后,但我认为这个问题已经准备好了一些Java通配符的东西。
这是Java的一个语法特性。假设你有一个方法:
public <T> void Foo(Collection<T> thing)
假设您不需要在方法体中引用类型T。您声明了一个名称T,然后只使用了一次,那么为什么还要为它想一个名称呢?相反,你可以这样写:
public void Foo(Collection<?> thing)
问号要求编译器假装您声明了一个普通的命名类型参数,该参数只需要在该位置出现一次。
用通配符可以做的事情,用命名类型参数也可以做(在c++和c#中,这些事情总是这样做的)。
我将加入我的声音,试着把事情弄清楚:
c#泛型允许你声明这样的东西。
List<Person> foo = new List<Person>();
然后编译器会阻止你把Person以外的东西放到列表中。 在幕后,c#编译器只是把List<Person>放到。net dll文件中,但是在运行时,JIT编译器会构建一组新的代码,就好像你写了一个专门用来包含人的列表类——类似于ListOfPerson。
这样做的好处是速度很快。没有强制转换或任何其他东西,因为dll包含这是一个List of Person的信息,稍后使用反射查看它的其他代码可以告诉它包含Person对象(因此您可以获得智能感知等等)。
这样做的缺点是旧的c# 1.0和1.1代码(在添加泛型之前)不理解这些新的List<something>,所以您必须手动将它们转换回普通的旧List以与它们互操作。这不是什么大问题,因为c# 2.0二进制代码不向后兼容。这种情况只会发生在你将一些旧的c# 1.0/1.1代码升级到c# 2.0的时候
Java泛型允许您声明类似的内容。
ArrayList<Person> foo = new ArrayList<Person>();
表面上看是一样的,也差不多是一样的。编译器也会阻止你把不是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
这种方法的好处是,不理解泛型的旧代码不必关心。它仍然和以前一样处理相同的数组列表。这在java世界中更为重要,因为他们希望支持使用带泛型的java 5编译代码,并让它运行在旧的1.4或以前的JVM上,而微软故意决定不去麻烦这些。
缺点是我前面提到的速度问题,也因为没有ListOfPerson伪类或任何类似的东西进入.class文件,稍后查看它的代码(使用反射,或者如果您从另一个集合中将它转换为Object等)无法以任何方式判断它是一个仅包含Person的列表,而不是任何其他数组列表。
c++模板允许你这样声明
std::list<Person>* foo = new std::list<Person>();
它看起来像c#和Java的泛型,它会做你认为它应该做的事情,但在幕后发生了不同的事情。
它与c#泛型最相似的地方在于,它构建了特殊的伪类,而不是像java那样把类型信息扔掉,但这是完全不同的事情。
c#和Java的输出都是为虚拟机设计的。如果你编写了一些包含Person类的代码,在这两种情况下,关于Person类的一些信息都会进入。dll或。class文件中,JVM/CLR将对此做一些事情。
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语法是不同的(<T扩展IHasName>),但也会遇到同样的问题。
这个问题的“经典”案例是尝试编写一个这样做的函数
string addNames<T>( T first, T second ) { return first + second; }
您不能实际编写这段代码,因为没有办法在接口中声明带有+方法的接口。你失败了。
c++没有这些问题。编译器并不关心将类型传递给任何VM -如果你的两个对象都有. name()函数,它会编译。如果他们不这样做,它就不会。简单。
所以,你有了它:-)
维基百科上有很多比较Java/ c#泛型和Java泛型/ c++模板的文章。关于泛型的主要文章似乎有点混乱,但它确实有一些很好的信息。