Java中的SafeVarargs和變數引數
有些語言在編譯時強制執行型別,但忘記了執行時的型別。這被稱為型別擦除。
例如,在C中,編譯器將確保程式碼完全是型別證明的。因此生成的位元組碼不會擔心執行時的型別資訊。
就像一枚硬幣的兩面,另一面。有些語言在執行時進行型別檢查(也可能在編譯時)。這被稱為具體化reification。
例如在Java中,即使你可以超越編譯器並將內容分配給編譯器。在執行時檢查型別。
Java型別具體化的經典示例:
在編譯時,通過將其型別轉換為Object來超越編譯器。但是當你執行編譯的類時,你會看到錯誤:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer at TypeReificationSample.main(TypeReificationSample.java:4)
這表明Java在執行時才檢查陣列。
當泛型Generics在Java中實現時,引入了型別擦除以使語言向後相容。
讓我們再次看一個經典的例子
class TypeCheck { public static void main(String[] args) { List<String> strList = new ArrayList<String>(); strList.add("Hello"); String hello = strList.get(0); System.out.println(hello); // "Hello" } }
通常使用型別檢查方式:
class GenericTypeCheck {
public static void main(String[] args) {
List strList = new ArrayList();
strList.add("Hello");
String hello = (String) strList.get(0);
System.out.println(hello); // "Hello"
}
}
這兩個類都編譯成相同的位元組碼。
class TypeCheck { TypeCheck(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String Hello 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: aload_1 18: iconst_0 19: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 24: checkcast #7 // class java/lang/String 27: astore_2 28: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 31: aload_2 32: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 35: return }
看看上面輸出堆疊的第4個索引。它沒有關於該型別的任何資訊。
Java 5引入了變數引數。這意味著可以傳遞多個引數 ..
public void someMethodTakesMultipleArguments(String... args) { }
// Pass in generic arguments
public <T> void someMethodTakesMultipleArguments(T... generic) { }
符號“...”告訴編譯器第一種情況下是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方法。這可以防止它覆蓋方法。介面中的方法不應該使用這個註釋。