Annotation type to reduce varargs warnings available in b123, Java 7 Coin minting done?

The improved varargs warnings, part of the Coin proposal finally made into the latest JDK 7 build. I believe build 123 is special as this milestone marks the completion of all the planned JSR 334 features (the head side of the Coin) scheduled for implementation in Java SE 7. So, Coin minting done? I think so. Let us get the final dose of Project Coin.

I would like to list out some key points that are worth refreshing when discussing varargs:

  • Generics are implemented by erasure and hence non-reifiable and enforces element types only at compile time.
  • Arrays are reified, which makes them enforce element types at runtime.
  • Arrays are covariant, while parameterized types are invariant.
  • Varargs are implemented as an array of objects and can hold only reifiable types.
  • Arrays when mixed with parameterized types are generally the source of unchecked warnings emitted by the compiler.

In essence, Generics and arrays (including varargs) do not play well together [Item 25: Prefer lists to arrays in Effective Java 2nd Edition Pg 119]. Whenever you invoke a varargs method, an array is created to hold the varargs parameters. If the element type of this array is not reifiable, it is likely to generate a compiler warning. On the other hand, it is not permitted to create an array whose component type is a concrete parameterized type, because it is not type safe.

Here is a simple example that shows generic array creation.

[scala]
// Generic array creation is illegal – won’t compile
List<List<String>> numbersInThreeLanguages = Arrays.asList(new List<String>[]{Arrays.asList("Un", "Deux", "Trois"), Arrays.asList("Uno", "Dos", "Tres"), Arrays.asList("One", "Two", "Three")});
[/scala]

The legal code would look like, but this would generate an unchecked warning:

[scala]
//[unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
List<List<String>> numbersInThreeLanguages = Arrays.asList(Arrays.asList("Un", "Deux", "Trois"),
Arrays.asList("Uno", "Dos", "Tres"), Arrays.asList("One", "Two", "Three"));
[/scala]

The warning can be suppressed at the call site as this is considered a safe invocation:

[scala]
@SuppressWarnings(value = "unchecked")
List<List<String>> numbersInThreeLanguages = Arrays.asList(Arrays.asList("Un", "Deux", "Trois"),
Arrays.asList("Uno", "Dos", "Tres"), Arrays.asList("One", "Two", "Three"));
[/scala]

These unchecked warnings issued when invoking varargs methods that are tagged as candidates for inducing heap pollution are considered harmless. Some of the widely used helper methods in JDK library produce such warnings which are useless and requires the client code to explicitly suppress them at the call site, if you hate them.

  • public static List Arrays.asList(T… a)
  • public static boolean Collections.addAll(Collection c, T… elements)
  • public static > EnumSet EnumSet.of(E first, E… rest)

The Coin varargs improvement adds a new @Documented annotation type, java.lang.SafeVararags, that can be used at the declaration site to suppress unchecked warnings for certain varargs invocations which are considered safe such as the ones listed above.

When the JDK libraries are retrofitted to take advantage of this new annotation type, client code can safely remove any SuppressWarnings annotation from the call site. For example, Arrays.asList() would carry this special annotation that reduces these warnings at the call site.

[scala]
@SafeVarargs
public static <T> List<T> asList(T… a) {
return new ArrayList<T>(a);
}
[/scala]

In addition to this annotation type, a new mandatory compiler warning “Possible heap pollution from parameterized vararg type : {0}” is generated on declaration sites of problematic varargs methods that are able to induce contributory heap pollution. To illustrate this, let us look at Alex Miller’s generics puzzler code below.

[scala]
//[unchecked] Possible heap pollution from parameterized vararg type T[]
public static <T> T[] mergeArrays2(T[]… arrays) {
List<T> result = new ArrayList<T>();

for(T[] array : arrays) {
for(T item : array) {
result.add(item);
}
}
// BROKEN: This generates a compiler warning about checking against an erased type
return (T[])result.toArray();
}
[/scala]

This code would cause a ClassCastException at runtime and the warning generated by the compiler is indeed legitimate, so this is a case where the warning should never be suppressed and should be fixed. I believe there are couple different ways to solve this puzzle (details in Alex’s blog comments), but the recommended approach is to prefer collections to arrays when designing APIs (as in Puzzle #3 in the recent installment of Java Puzzlers from Josh Bloch and Bob Lee).

The solution outlined below requires the caller to provide a hint on the return type, which is kind of ugly, but works.

[scala highlight=”2,3,10″]
@SafeVarargs
static <T, U extends T> T[] mergeArrays(T[] returnType, U[]… arrays) {
Class<?> componentType = returnType.getClass().getComponentType();
// Determine total length
int length = 0;
for (T[] a : arrays) {
length += a.length;
}
@SuppressWarnings(value = "unchecked")
T[] result = (T[]) Array.newInstance(componentType, length);
int j = 0;
for (T[] anArray : arrays) {
for (T element : anArray) {
result[j++] = element;
}
}
return result;
}
[/scala]

If you notice, the use of @SafeVarargs annotation at the declaration site is valid since we have fixed the underlying problem of creating type safe array. The unsafe casting at line#10 when reflectively creating an array instance is harmless, so we can suppress that unchecked warning too.

The client code that invokes this method using a String array and an Integer array is shown below:

[scala]
String[] s1 = new String[]{"one", "two", "three"};
String[] s2 = new String[]{"four", "five", "six"};
String[] s3 = new String[]{"seven", "eight", "nine"};
String[] sHint = new String[]{};
String[] sMerged = mergeArrays(sHint, s1, s2, s3);
System.out.println(Arrays.toString(sMerged));
//Output: [one, two, three, four, five, six, seven, eight, nine]
Integer[] i1 = new Integer[]{1, 2, 3};
Integer[] i2 = new Integer[]{4, 5, 6};
Integer[] i3 = new Integer[]{7, 8, 9};
Integer[] iHint = new Integer[]{};
Integer[] iMerged = mergeArrays(iHint, i1, i2, i3);
System.out.println(Arrays.toString(iMerged));
//Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
[/scala]

@SafeVarargs Applicability

This annotation type is applicable to Constructor and Method types only as defined by the Target meta-annotation. Here is the specification copied from the javadoc of @SafeVarargs which clearly outlines its scope and requirements in detail.

In addition to the usage restrictions imposed by its @Target meta-annotation, compilers are required to implement additional usage restrictions on this annotation type; it is a compile-time error if a method or constructor declaration is annotated with a @SafeVarargs annotation, and either:

  • the declaration is a fixed-arity method or constructor
  • the declaration is a variable-arity method that is neither static nor final.

Compilers are encouraged to issue warnings when this annotation type is applied to a method or constructor declaration where:

  • The variable-arity parameter has a reifiable element type, which includes primitive types, Object, and String. (The unchecked warnings this annotation type suppresses already do not occur for a reifiable element type.)
  • The body of the method or constructor declaration performs potentially unsafe operations, such as an assignment to an element of the variable-arity parameter’s array that generates an unchecked warning.

Another important thing Joe clarified regarding overriding on coin-dev: “As documented in its specification, the SafeVarargs annotation is only applicable to static methods, final instance methods, and constructors; therefore, overriding does not occur. Annotation inheritance only works on classes (not methods, interfaces, or constructors) so without changing annotation inheritance, a SafeVarargs-style annotation cannot be passed through general instance methods in classes or through interfaces.”

Conclusion

I think the Coin chapter for Java 7 has come to an end. Now, what is pending is the dogfooding, which has already begun as JDK libraries are getting a dose of Coin in form of Diamond finishings. IDEs are gearing up support for these new language features. Both, NetBeans 7 Beta and IntellijIDEA 10 has a decent support for some of the Project Coin features. The ride with Project Coin has been an exciting one so far. Hope you all enjoyed exploring these cool new Java 7 features with me; why wait, mint yours today 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s