在泛型类实例化上创建了多少个实例?

时间:2014-05-20 18:13:14

标签: c# java generics instantiation

考虑这样的课程:

class Foo<T>
{
   private T myField;
   public void Set(T x)
   {
      myField = x;
   }
}

然后您使用T等于intbool(值类型)和StringList进行实例化。

如何创建实例化以及它们的外观?

我对Java和C#感兴趣。

从我读到的内容中,Java会创建一个基本类型的通用类,而在C#中,intbool会有两个类,String只有一个类, List因为它们是参考值(这是真的吗?静态字段怎么样?)。

4 个答案:

答案 0 :(得分:8)

请注意,我假设是常规CLR - 没有AOT等。

在IL级别:Foo<T>有一个定义;无论T是什么,IL都是相同的(共享的)。

在JIT级别:对于所有(共享)引用类型参数,一次一次一次每次 em>(单独)值类型参数。因此,Foo<string>Foo<List<...>>的JIT是共享的,Foo<int>Foo<bool>的每个JIT都是单独的JIT。

创建的对象/实例数与new Foo<...>(...)次调用(或Activator.CreateInstance(...)等)的数量相同。

答案 1 :(得分:4)

在JVM中,只有一个类:由类型擦除产生的类。

在CLR中,为类型参数的每个值构造一个闭合类型,每个值都有自己不同的静态字段副本;具有引用类型参数的封闭类型之间的JIT代码共享是一种实现优化。因此,虽然Foo<String>Foo<List>可能引用相同的JIT翻译方法实现,但它们不是同一类型,并且具有不同的静态字段。

答案 2 :(得分:3)

Java Generics由“类型擦除”实现。

编译器创建一个类Foo<T>,它基本上具有代码Foo<Object>。它不像C ++模板,其中基于类型实例化新类。编译比C ++版本更快,但确实阻止了一些在编译时进行的优化。我相信这个系统的主要原因是希望保持与前Generics Java的兼容性。

类型参数例如 Integer中的Foo<Integer>仅在编译时用于在程序员尝试传递Bar时发出错误,并允许编译器假设返回T的函数将返回T,而在前Generics Java中,程序员必须转换结果。值得注意的是,Foo<Integer>的任何方法都不会实际检查它们的参数是Integer,除非程序员明确地写出来。

对于静态字段,因为对于不同的特化只有一个Foo而不是单独的Foo,所以类型变量是“非静态的”并且试图声明类型为{的静态成员{1}}没有意义。在我的编译器上,它失败并出现错误“无法从静态上下文引用非静态类型变量T”。

答案 3 :(得分:2)

Java和.NET处理泛型的方式不同。我们来看看以下代码:

public class Foo<T>
{
    private static Object staticMember;

    public T getStaticMember() { 
        return (T) staticMember; 
    }

    private T instanceMember;

    public T getInstanceMember() {
        return instanceMember;
    }

    public Foo(T value)
    {
        if (staticMember == null) 
        {
            staticMember = value;
        }
        this.instanceMember = value;
    }        
}

这在质量方面不是很好的代码,但是为了这个例子,它是Java和C#的一段代码。

在Java中,运行时只知道类Foo。所以下面的代码

Foo<Integer> foo = new Foo<Integer>(3);
System.out.println(foo.getStaticMember()); // >> 3
System.out.println(foo.getInstanceMember()); // >> 3

实际上会被编译成类似的东西:

Foo foo = new Foo(3);
System.out.println(((Integer) foo.getInstanceMember()).toString()); // 3
System.out.println(((Integer) foo.getStaticMember()).toString()); // 3

如您所见,泛型被转换为静态成员的类型转换。

以下代码将在Java中失败并出现运行时异常:

Foo<Integer> foo1 = new Foo<Integer>(3);
Foo<String> foo2 = new Foo<String>("This is a string");
System.out.println(foo1.getInstanceMember()); // >> 3
System.out.println(foo2.getInstanceMember()); // >> This is a string
System.out.println(foo1.getStaticMember()); // >> 3
System.out.println(foo2.getStaticMember()); // Invalid cast exception

因为它会被视为:

Foo foo1 = new Foo(3);
Foo foo2 = new Foo("This is a string");
System.out.println(((Integer) foo1.getInstanceMember()).toString()); // >> 3
System.out.println(((String) foo2.getInstanceMember()).toString()); 
// >> This is a string
System.out.println(((Integer) foo1.getStaticMember()).toString()); // >> 3
System.out.println(((String) foo2.getStaticMember()).toString()); 
// Invalid cast exception

最后一行会尝试将静态成员(Integer)转换为String

以上代码可以在C#中正常工作,分别打印:

  

3
  3
  这是一个字符串
  这是一个字符串

为什么?

Java的工作原理

在Java中,编译器将Foo类创建为原始类型,忽略类型定义中的任何通用信息。这称为类型擦除also explained in Samuel Edwin Ward's answer)。编译器将尝试检测(或更好地说 - 猜测)类型使用并尝试通过在生成的代码中添加类型转换来补偿类型擦除,分析类的用途以实现此目的。因此,通常,结果将类似于Foo类型是通用的。问题是类型Foo只存在一次,其staticMember是同一个Foo<Integer>Foo<String>之间共享的实例。

.NET如何工作

在.NET中,Foo<int>Foo<string>声明导致在编译期间生成不同类型(实际上在JIT阶段,Marc GravellJeffrey Hantin在他们的各自的答案)。因此,Foo<string>类与Foo<int>完全不同。这导致每个属性都有自己的staticMember属性,因此保证Foo<string>.staticMember始终为string类型,而Foo<int>.staticMember是完全不同的成员int类型。