有些语言在编译时强制执行类型,但忘记了运行时的类型。这被称为类型擦除。
例如,在C中,编译器将确保代码完全是类型证明的。因此生成的字节码不会担心运行时的类型信息。
就像一枚硬币的两面,另一面。有些语言在运行时进行类型检查(也可能在编译时)。这被称为具体化reification。
例如在Java中,即使你可以超越编译器并将内容分配给编译器。在运行时检查类型。
Java类型具体化的经典示例,
<b>class</b> TypeReificationSample { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { Object[] strArr = <b>new</b> String[1]; strArr[0] = (Object) 13; } }
在编译时,通过将其类型转换为Object来超越编译器。但是当你运行编译的类时,你会看到错误:
Exception in thread <font>"main"</font><font> java.lang.ArrayStoreException: java.lang.Integer at TypeReificationSample.main(TypeReificationSample.java:4) </font>
这表明Java在运行时才检查数组。
当泛型Generics在Java中实现时,引入了类型擦除以使语言向后兼容。
让我们再次看一个经典的例子,
<b>class</b> TypeCheck { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { List<String> strList = <b>new</b> ArrayList<String>(); strList.add(<font>"Hello"</font><font>); String hello = strList.get(0); System.out.println(hello); </font><font><i>// "Hello"</i></font><font> } } </font>
通常使用类型检查方式:
<b>class</b> GenericTypeCheck { <b>public</b> <b>static</b> <b>void</b> main(String[] args) { List strList = <b>new</b> ArrayList(); strList.add(<font>"Hello"</font><font>); String hello = (String) strList.get(0); System.out.println(hello); </font><font><i>// "Hello"</i></font><font> } } </font>
这两个类都编译成相同的字节码。
<b>class</b> TypeCheck { TypeCheck(); Code: 0: aload_0 1: invokespecial #1 <font><i>// Method java/lang/Object."<init>":()V</i></font><font> 4: <b>return</b> <b>public</b> <b>static</b> <b>void</b> main(java.lang.String[]); Code: 0: <b>new</b> #2 </font><font><i>// class java/util/ArrayList</i></font><font> 3: dup 4: invokespecial #3 </font><font><i>// Method java/util/ArrayList."<init>":()V</i></font><font> 7: astore_1 8: aload_1 9: ldc #4 </font><font><i>// String Hello</i></font><font> 11: invokeinterface #5, 2 </font><font><i>// InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z</i></font><font> 16: pop 17: aload_1 18: iconst_0 19: invokeinterface #6, 2 </font><font><i>// InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;</i></font><font> 24: checkcast #7 </font><font><i>// class java/lang/String</i></font><font> 27: astore_2 28: getstatic #8 </font><font><i>// Field java/lang/System.out:Ljava/io/PrintStream;</i></font><font> 31: aload_2 32: invokevirtual #9 </font><font><i>// Method java/io/PrintStream.println:(Ljava/lang/String;)V</i></font><font> 35: <b>return</b> } </font>
看看上面输出堆栈的第4个索引。它没有关于该类型的任何信息。
Java 5引入了变量参数。这意味着可以传递多个参数 ..
<b>public</b> <b>void</b> someMethodTakesMultipleArguments(String... args) { } <font><i>// Pass in generic arguments</i></font><font> <b>public</b> <T> <b>void</b> someMethodTakesMultipleArguments(T... <b>generic</b>) { } </font>
符号“...”告诉编译器第一种情况下是String数组,在第二种情况下是T数组。
泛型参数将导致潜在的不安全操作。该方法可能会转换或可能更改类型。这将导致不确定性。
因此编译器会在编译时发出警告。
Note: GenericArguments.java uses unchecked or unsafe operations.
因为varargs会导致堆污染:
Varargs method could cause heap pollution from non-reifiable varargs parameter
为了解决这个问题,Java7引入了@SafeVarargs注释。这将告诉编译器该方法或构造函数不会对varargs参数执行可能不安全的操作。
将@SafeVarargs注释在某个方法上面表示禁止编译器警告未经检查的警告。
但是添加@SafeVarargs到可能不安全的方法上将导致在运行时抛出ClassCastException。
何时应用此注释?
将变量参数传递给方法或构造函数时,不要改变或转换对象类型。该方法可能是安全的。
在哪里应用此注释?
使用在final和static方法。这可以防止它覆盖方法。接口中的方法不应该使用这个注释。