2013年12月21日 星期六

Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods

許裕永 譯 (原文 內容取自 Oracle 官網)

Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods - The Java SE 7 complier generates a warning at the declaration site of a varargs method or constructor with a non-reifiable varargs formal parameter. Java SE 7 introduces the compiler option -Xlint:varargs and the annotations @SafeVarargs and @SuppressWarnings({"unchecked", "varargs"}) to supress these warnings.
增強編譯器的警告及錯誤, 當在 Varargs 的方法中使用非具體型式的參數時 - Java SE7 的編譯器會產生警告在 Varargs 方法宣告的位置或建構方法擁有非具體型式的參數時. Java SE7 提出一個編譯選項 - Xlint varargs 和便箋 @SafeVarargs 及 @SuppressWarnings({"unchecked", "varargs"}) 來抑制這些警告.

This page covers the following topics:
本頁包含下列主題:

Heap Pollution
Variable Arguments Methods and Non-Reifiable Formal Parameters
Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters
Suppressing Warnings from Varargs Methods with Non-Reifiable Formal Parameters

Heap Pollution
堆疊污染
Most parameterized types, such as ArrayList<Number> and List<String>, are non-reifiable types. A non-reifiable type is a type that is not completely available at runtime. At compile time, non-reifiable types undergo a process called type erasure during which the compiler removes information related to type parameters and type arguments. This ensures binary compatibility with Java libraries and applications that were created before generics. Because type erasure removes information from parameterized types at compile-time, these types are non-reifiable.
大部份可以設定限型的型別, 像是 ArrayList<Number> 及 List<String>, 是屬於非具體型式的型別. 一個非具體型式的型別是指在執行時期不一定合法的型別. 在編譯時期, 非具體型式的型別會進行一個所謂型別抺除的流程, 這個流程會刪除這個型別關於特徵及主旨的資訊. 這保證二進位的兼容性關於建立在限型(泛型)之前的 Java 函式庫及應用程式. 因為在編譯時期會刪除可以設定限型的型別的資訊, 這些型別是非具體型式.

Heap pollution occurs when a variable of a parameterized type refers to an object that is not of that parameterized type. This situation can only occur if the program performed some operation that would give rise to an unchecked warning at compile-time. An unchecked warning is generated if, either at compile-time (within the limits of the compile-time type checking rules) or at runtime, the correctness of an operation involving a parameterized type (for example, a cast or method call) cannot be verified.
當把一個有宣告限型型別的變數參照向一個不同限型型別的物件時會發生堆疊污染. 這種狀況只會在該程式呈現某些運算式時在編譯時期產生 unchecked warning. 如果產生 unchecked warning, 無論是在編譯時期(關於編譯時期型別檢查規則的限制)或在執行時期 , 則涉及限型型別的運算式的正確性將無法被證實.

Consider the following example:
了解下列範例:

List l = new ArrayList<Number>();
List<String> ls = l; // unchecked warning
l.add(0, new Integer(42)); // another unchecked warning
String s = ls.get(0); // ClassCastException is thrown

During type erasure, the types ArrayList<Number> and List<String> become ArrayList and List, respectively.
在型別抺除時, ArrayList<Number> 及 List<String> 在的型別會分別變成 ArrayList 及 List.
The variable ls has the parameterized type List<String>. When the List referenced by l is assigned to ls, the compiler generates an unchecked warning; the compiler is unable to determine at compile time, and moreover knows that the JVM will not be able to determine at runtime, if l refers to a List<String> type; it does not. Consequently, heap pollution occurs.
變數 ls 有設定限型型別為 List<String>. 當一個 List 的參照被指派給 ls, 則編譯器會產生一個 unchecked warning, 編譯器沒有能力在編譯時期確認, 而且除此之外也知道 JVM 在執行時期也無法確認, l 是否指向一個 List<String>. 如果不是的話, 必然會發生堆疊污染.

As a result, at compile time, the compiler generates another unchecked warning at the add statement. The compiler is unable to determine if the variable l refers to a List<String> type or a List<Integer> type (and another heap pollution situation occurs). However, the compiler does not generate a warning or error at the get statement. This statement is valid; it is calling the List<String>.get method to retrieve a String object. Instead, at runtime, the get statement throws a ClassCastException.
結果, 在編譯時期, 編譯器對於 add 敍述會產生另一個 unchecked warning. 編譯器無法確認變數 l 是否指向一個 List<String> 型別或是一個 List<Integer> 型別 ( 而且另一個堆疊污染情況發生了). 總之, 編譯器不會對 get 敍述提出警告或錯誤. 這個敍述是合法的, 它是呼叫 List<String> 的 get 的方法來取得一個 String 物件. 取代的是在執行時期丟出一個 classCastException.

In detail, a heap pollution situation occurs when the List object l, whose static type is List<Number>, is assigned to another List object, ls, that has a different static type, List<String>. However, the compiler still allows this assignment. It must allow this assignment to preserve backwards compatibility with versions of Java SE that do not support generics. Because of type erasure, List<Number> and List<String> both become List. Consequently, the compiler allows the assignment of the object l, which has a raw type of List, to the object ls.
詳細來說, 一個堆疊污染的情況是發生在當 List 型別的物件 l , 它的限型型別是 List<Number>, 它被指派給 List 型別物件 ls, 而 ls 的限型型別是 List<String>. 總之編譯器是允許這樣的指派的. 它必須允這樣的指派, 對於 Java SE 中不支援限型機制(泛型)的版本.來保留其向下的兼容性. 因為在型別袜除後, List<Number> 及 List<String> 都會變成 List. 因此編譯器允許原始型別為List 的物件 l 被指派給物件 ls.

Furthermore, a heap pollution situation occurs when the l.add method is called. The static type second formal parameter of the add method is String, but this method is called with an actual parameter of a different type,Integer. However, the compiler still allows this method call. Because of type erasure, the type of the second formal parameter of the add method (which is defined as List<E>.add(int,E)) becomes Object. Consequently, the compiler allows this method call because, after type erasure, the l.add method can add any object of type Object, including an object of Integer type, which is a subtype of Object.
此外, 當 l 呼叫 add 方法時也會產生堆疊污染. add 方法中的第二個參數的限型型別設定為 String, 但這個方法被呼叫時引用的參數的實際型別卻是不同的型別, Integer. 不過, 編譯器還是會允許這個方法的呼叫. 因為經過型別袜除後, add 方法中的第二個參數的型別 (那是很清楚的像 List<E> add<int, E) ) 會變成 Object. 編譯器會允許這個方法的呼叫是必然的, 因為在型別袜除後, l.add 方法可以新增任何 Object 型別的物件, 包含一個 Integer 型別的物件, 它是 Object 的下層型別.

Variable Arguments Methods and Non-Reifiable Formal Parameters
變動參數的方法與非具體型式的參數

Consider the method ArrayBuilder.addToList in the following example. It is a variable arguments (also known as varargs) method that adds the objects of type T contained in the elements varargs formal parameter to theList listArg:
了解下列範例中的 ArrayBuilder.addToList 方法. 它是一個變動參數 (也就是 varargs) 的方法, 可以把 T 型別的物件作為包含在變動參數中的元素, 新增到 List listArg 之中 :

import java.util.*;

public class ArrayBuilder {

 public static <T> void addToList (List<T> listArg, T... elements) {
  for (T x : elements) {
   listArg.add(x);
  }
 }

 public static void faultyMethod(List<String>... l) {
  Object[] objectArray = l; // Valid
  objectArray[0] = Arrays.asList(new Integer(42));
  String s = l[0].get(0); // ClassCastException thrown here
 }

}

import java.util.*;

public class HeapPollutionExample {

 public static void main(String[] args) {

  List<String> stringListA = new ArrayList<String>();
  List<String> stringListB = new ArrayList<String>();
 
  ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
  ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve");
  List<List<String>> listOfStringLists = new ArrayList<List<String>>();
  ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);

  ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
 }
}
 

The Java SE 7 compiler generates the following warning for the definition of the method ArrayBuilder.addToList:
Java SE7 編譯器會產生下列警告來解釋方法 ArrayBuilder.addToList:

warning: [varargs] Possible heap pollution from parameterized vararg type T
When the compiler encounters a varargs method, it translates the varargs formal parameter into an array. However, the Java programming language does not permit the creation of arrays of parameterized types. In the methodArrayBuilder.addToList, the compiler translates the varargs formal parameter T... elements to the formal parameter T[] elements, an array. However, because of type erasure, the compiler converts the varargs formal parameter to Object[] elements. Consequently, there is a possibility of heap pollution. See the next section, Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters, for more information.
當編譯器遇到了變動參數的方法, 它會把合格的變動參數轉換為陣列. 然而, Java 程式語言並不允許那些的陣列屬於限型型別. 在方法 ArrayBuilder.addToList 中, 編譯器會轉換合格的變數參數 T... 的元素為合格的 T[] 元素, 一個陣列. 然而, 由於型別袜除, 編譯器會把合格的變動參數轉換為 Object[] 元素. 因此, 這就有可能產生堆疊污染. 參閱下一個單元,  Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters, 了解更多訊息.

Note: The Java SE 5 and 6 compilers generate this warning when the ArrayBuilder.addToList is called; in this example, the warning is generated for the class HeapPollutionExample. These compilers do not generate the warning at the declaration site. However, the Java SE 7 generates the warning at both the declaration site and the call site (unless the warnings are preempted with annotations; see Suppressing Warnings from Varargs Methods with Non-Reifiable Formal Parameters for more information). The advantage of generating a warning when a compiler encounters a varargs method that has a non-reifiable varargs formal parameter at the declaration site as opposed to the call site is that there is only one declaration site; there are potentially many call sites.
註: Java SE 5 及 6 的編譯器會在 ArrayBuilder.addToList 方法呼叫的時候產生這個警告; 在這個範例中是在類別 HeapPollutionExample 中產生. 這些編譯器並不會在宣告的位置產生警告. 然而, Java SE 7 會在宣告的位置以及呼叫的位置都產生警告 (除非這些警告有先行取得註解; 參閱 Suppressing Warnings from Varargs Methods with Non-Reifiable Formal Parameters 了解更多訊息 ). 當編譯器遇到非具體型式的變動參數的宣告時產生警告, 而不只是在呼叫端產生警告的好處是: 只有一個宣告端;卻有許多呼叫端.

Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters
在變動參數中使用非具體型式參數時的潛在的容易造成的傷害.

The method ArrayBuilder.faultyMethod shows why the compiler warns you about these kinds of methods. The first statement of this method assigns the varargs formal parameter l to the Object array objectArgs:
方法 ArrayBuilder.faultyMethod 呈現出了為什麼編譯器會在這些方法的使用型態上對你提出警告. 方法中的第一行敍述指派合法的變動參數 l 給 Object 陣列 objectArgs:

Object[] objectArray = l;
This statement can potentially introduce heap pollution. A value that does match the parameterized type of the varargs formal parameter l can be assigned to the variable objectArray, and thus can be assigned to l. However, the compiler does not generate an unchecked warning at this statement. The compiler has already generated a warning when it translated the varargs formal parameter List<String>... l to the formal parameter List[] l. This statement is valid; the variable l has the type List[], which is a subtype of Object[].
這個敍述可能引出潛在的堆疊污染. 一個完全符合合法的變數參數 l 的值可以被指派給變數 objectArray, 而且也可以指派回給 l . 然而, 編譯器不會對這個敍述產生 unchecked 警告. 編譯器已經在轉換合格的變數參數 List<String>... l 的元素為合格的 List[] l 元素時產生警告. 這個敍述是合法的, 變數 l 擁有 List[] 型別, 它是 Object[] 的下層型別.

Consequently, the compiler does not issue a warning or error if you assign a List object of any type to any array component of the objectArray array as shown by this statement:
因此, 如果你以像下列一樣的敍述, 指派一個 設定任何限型型別的 List 物件為 objectArray 中的陣列元素時, 編譯器不會發佈警告或錯誤:

objectArray[0] = Arrays.asList(new Integer(42));
This statement assigns to the first array component of the objectArray array with a List object that contains one object of type Integer.
這個敍述把設定限型型別為 Integer 的 List 型別物件, 指派為 objectArray 中的第一個陣列元素.

Suppose you call the ArrayBuilder.makeArray method with the following statement:
假設你使用下列敍述呼叫方法 ArrayBuilder.makeArray :

ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
At runtime, the JVM throws a ClassCastException at the following statement:
在執行時期, JVM 會針對下列敍述丟出一個 ClassCastException:

String s = l[0].get(0); // ClassCastException thrown here

The object stored in the first array component of the variable l has the type List<Integer>, but this statement is expecting an object of type List<String>.
儲存於變數 l 中的第一個陣列元素物件的型別是 List<Integer>, 但這個敍述期望的是型別為 List<String> 的物件.

Suppressing Warnings from Varargs Methods with Non-Reifiable Formal Parameters
避免產生使用非具體型式的變動參數的方法時的警告

If you declare a varargs method that has parameterized parameters, and you ensure that the body of the method does not throw a ClassCastException or other similar exception due to improper handling of the varargs formal parameter (as shown in the ArrayBuilder.faultyMethod method), you can suppress the warning that the compiler generates for these kinds of varargs methods by using one of the following options:
如果你宣告一個變動參數的方法它有可以設定限型的參數, 而且你確認方法中的程式碼不會丟出 ClassCaseException 或其他由於不適當的支援變動參數的類似例外 (例如示範中的 ArrayBuilder.faultyMethod 方法), 你可以使用下列選項之一的方式, 避免編譯器產生這些關於使用變動參數的方法的警告:

Add the following annotation to static and non-constructor method declarations:
在 static 及非建構方法的方法宣告時新增下列註解:

@SafeVarargsThe @SafeVarargs annotation is a documented part of the method's contract; this annotation asserts that the implementation of the method will not improperly handle the varargs formal parameter.
@SafeVarargs 註解是方法宣告合約中的一部份, 這個註解斷定這個方法的實作將不會, 不正確的使用變動參數.


Add the following annotation to the method declaration:
新增下例註解到方法的宣告:

@SuppressWarnings({"unchecked", "varargs"})Unlike the @SafeVarargs annotation, the @SuppressWarnings("varargs") does not suppress warnings generated from the method's call site.
不像 @SafeVarargs 註解, @SuppressWarnings("varargs") 不會在方法被呼叫時抑制警告產生.

Use the compiler option -Xlint:varargs.
使用編譯選項 -Xlint:varargs.

For example, the following version of the ArrayBuilder class has two additional methods, addToList2 and addToList3:
如同範例, 下列版本的 ArrayBuilder 類別有新增了兩個方法, addToList2 及 addToList3:

public class ArrayBuilder {

 public static <T> void addToList (List<T> listArg, T... elements) {
  for (T x : elements) {
   listArg.add(x);
  }
 }

@SuppressWarnings({"unchecked", "varargs"})
 public static <T> void addToList2 (List<T> listArg, T... elements) {
  for (T x : elements) {
   listArg.add(x);
  }
 }

@SafeVarargs
 public static <T> void addToList3 (List<T> listArg, T... elements) {
  for (T x : elements) {
   listArg.add(x);
  }
 }

// ...

}
public class HeapPollutionExample {

// ...

 public static void main(String[] args) {

// ...

  ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
  ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB);
  ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB);

// ...

 }
}

The Java compiler generates the following warnings for this example:
這個範例 Java 編譯器會產生下列警告:

addToList:
At the method's declaration: [unchecked] Possible heap pollution from parameterized vararg type T
在這個方法的宣告: [unchecked] 變動參數 type T 可能會有堆疊污染.

When the method is called: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
當這個方法被呼叫 : [unchecked] 不檢查限型陣列的建立, 當變動參數的型別是 List<String>[] 時.

addToList2:
When the method is called (no warning is generated at the method's declaration): [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
當這個方法被呼叫 (沒有警告產生在這個方法的宣告) : [unchecked] 不檢查限型陣列的建立, 當變動參數的型別是 List<String>[] 時.

addToList3:
No warnings are generated either at the method's declaration or when it is called.
沒有警告會產生無論是在方法宣告時或被呼叫時.

Note: In Java SE 5 and 6, it is the responsibility of the programmer who calls a varargs method that has a non-reifiable varargs formal parameter to determine whether heap pollution would occur. However, if this programmer did not write such a method, he or she cannot easily determine this. In Java SE 7, it is the responsibility of the programmer who writes these kinds of varargs methods to ensure that they properly handle the varargs formal parameter and ensure heap pollution does not occur.
注意 : 在 Java SE 5 及 6, 程式設計師有責任在呼叫非具體型式的變動參數時確認是否有堆疊污染會發生. 只是, 如果這個程式設計師不曾寫過這樣的程式, 他或她可能無法輕易的作出判定. 在 Java SE 7, 程式設計師有責任在開發非具體型式的變動參數的方法時, 擔保他們有正確的使用變動參數而且擔保不會發生堆疊污染.

沒有留言:

張貼留言