.NET中的泛型和Java泛型中的类型擦除

2022-10-24,,,,

开放类型和闭合类型

.net把带有类型参数的类型看做一个新的类型,clr将创建为这些类型创建内部类型对象,这适用于类,结构,接口和委托。但是,一个带有类型参数的类型成为开放类型,clr不允许开放类型实例化(就好比不允许接口实例化一样)。

当代码中引用了泛型类型,代码里可以指定一组泛型类型参数。如果传入实际的数据类型,那么这个类型就成为闭合类型,clr允许实例化闭合类型。然而,也有可能代码引用了泛型类型,但未指定泛型类型参数,这就在clr中创建了一个新的开放类型,这种类型无法实例化,看一个例子。

internal sealed class dictionarystringkey<tvalue> :dictionary<string, tvalue> 

        { 

        } 

        static void main(string[] args) 

        { 

            object o = null; 

            // dictionary<,> 有2个类型参数的开放类型 

            type t = typeof(dictionary<,>); 

            // 创建实例会失败 

            o = createinstance(t); 

            console.writeline(); 

            // dictionarystringkey<>有一个类型参数的开发类型 

            t = typeof(dictionarystringkey<>); 

            // 创建该类型的实例也会失败 

            o = createinstance(t); 

            console.writeline(); 

            // dictionarystringkey<guid> 是闭合类型 

            t = typeof(dictionarystringkey<guid>); 

            // 创建成功 

            o = createinstance(t); 

            // 输出类型名字 

            console.writeline("object type=" + o.gettype()); 

        } 

        private static object createinstance(type t) 

        { 

            object o = null; 

            try 

            { 

                //使用默认的构造函数来创造该类型的实例 

                o = activator.createinstance(t); 

                console.write("created instance of {0}", t.tostring()); 

            } 

            catch (argumentexception e) 

            { 

                console.writeline(e.message); 

            } 

            return o; 

        } 

运行结果:

activator.createinstance创建实例的时候,会提示你该类型包含泛型参数。

输出中,可以看到类型名称后跟着反引号(`)以及一个数字。这个数字即类型中的类型参数的数量。比如泛型dictionary类是2,因为它需要2个类型参数来指示tkey和tvalue。dictionarystringkey类只有1个因为它只需要指明1个类型tvalue。

.net中的类型

.net中,除了实例构造器,clr也支持类型构造器(也称作静态够器,类够在其或者类型初始化器)。类型构造器可以应用于接口(中不支持),引用类型(class)和值类型(struct),和实例构造器初始化类型的实例一样,类型构造器用来初始化类型的一些状态,类型的构造器如果有的话 只可能有1个,并且是无参的。可以参考之前的文章。

http://cnn237111.blog.51cto.com/2359144/576533

由于clr保证了类型初始化器只执行一次,并且是线程安全的,因此类型初始化器适用于用在单例模式中对单例对象的初始化。

类型中的静态字段可以认为是类型的一部分,而类型中的非静态字段可以认为是实例对象的一部分。当jit编译器把il语言转换成本地的cpu之类的时候,会遇到很多类型(比如自定义的class),clr为了能正确的加载包含这些类型的程序集,它会通过程序集的元数据,抽取出类型的信息,然后创建这些类型的数据数据结构。这些数据结构作为对象存放在堆中。堆中所有的对象都有2个成员,类型对象指针和同步块索引。类型中定义的静态字段也包含在数据结构对象中。类的实例对象都共享类型对象中同一个静态字段。如下图:方框中的manager是类型对象,静态字段存在于类型对象中。实例对象由椭圆框表示,指向类型对象。

对于.net泛型来说,每一个闭合类型都有自己的静态字段。也就是说list<>和list<string>中的静态字段是互相独立的。同样的,如果泛型类型定义了一个静态构造器,这些构造器也是按照各自的闭合类型运行。也就是说,list<datetime>和list<string>有自己独立的静态构造器。

static void main(string[] args) 

        { 

            bool issame = typeof(list<datetime>) == typeof(list<string>); 

            console.writeline(issame); 

            object o = activator.createinstance(typeof(list<datetime>)); 

            console.writeline(o.gettype()); 

            o = activator.createinstance(typeof(list<string>)); 

            console.writeline(o.gettype()); 

        } 

运行结果如下:

java泛型中的类型擦除

  经常听人说起java的泛型是伪泛型,因为在编译或运行期间,java的jit会对进行类型擦除。即jvm无法真正识别出泛型类型,因此在真正运行前会把泛型类型转换成原始类型。因此,所有的泛型类型,本质上都共享同一个类型对象。比如list<integer>类型在擦除后变成非泛型的list,这个list可以存放任何类型的数据。因此,java在运行的时候,无法获得类型。当然,使用反射也许可以知道,但是并不保证总是能够得到类型。因此在java代码中,不同的泛型类型其实都是出自相同的类型。例如下面代码:

public static void main(string[] args) throws exception { 

  list<integer> li = new arraylist<integer>(); 

  list<float> lf = new arraylist<float>(); 

  boolean issame = li.getclass() == lf.getclass(); 

  system.out.println(issame);//true 

  object o = li.getclass().newinstance(); 

  system.out.println(o.getclass().getname());//java.util.arraylist 

  o = lf.getclass().newinstance(); 

  system.out.println(o.getclass().getname());//java.util.arraylist 

也正是由于代码擦除,使得泛型类型本质上都是共享同一个类型对象,因此类型的静态字段也是共享的。例如下面代码:

public class atestclass<e> { 

public static int i=0; 

public atestclass() 

i++; 

 

------------------ 

 

public static void main(string[] args) throws exception { 

   atestclass<integer> ai=new atestclass<integer>(); 

   atestclass<float> af=new atestclass<float>(); 

   system.out.println(atestclass.i); 

最终运行的结果是2.