1. 程式人生 > >Java中的SafeVarargs和變數引數

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方法。這可以防止它覆蓋方法。介面中的方法不應該使用這個註釋。