The java.lang.SafeVarargs annotation
In a previous blog post I mentioned the @SafeVarargs
annotation.
That post was about wildcard type arguments.
For this reason, the SafeVarargs
annotation was beyond the scope of that post.
In this blog post, I want to take a deeper look at the annotation. To be precise, we will look into all of the topics related to the annotation. In particular:
-
in what situations can varargs be unsafe?
-
how does the compiler compiles a varargs method invocation?
-
what is the meaning of the varargs related compiler warnings?
-
reifiable and non-reifiable types
-
arrays and non-reifiable types
-
and more.
So let's begin.
An unsafe varargs example
If the name of the annotation is "safe varargs" it must mean that, in some situations, varargs can be unsafe.
So let's see an example.
Before we do, please keep in mind the purpose of the following code is to illustrate an use of unsafe varargs. In other words, it is written in that way specifically to produce bad results. It shouldn't be considered an example for production code.
A server that configures and starts two services
Suppose we have the following Server
:
public class Server {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
var serviceA = new HashMap<String, Object>();
serviceA.put("name", "Service A");
var serviceB = new HashMap<String, Object>();
serviceB.put("name", "Service B");
Library.defaults(serviceA, serviceB);
serviceB.put("max", 512);
start(serviceA);
start(serviceB);
}
private static void start(Map<String, Object> config) {
System.out.println("Starting...");
String name = (String) config.get("name");
System.out.format("name=%s%n", name);
int max = (int) config.get("max");
System.out.format("max=%d%n", max);
System.out.println();
}
}
It creates the configuration for two services named:
-
Service A
-
Service B
The service configuration is given by a Map<String, Object>
.
The mapped key is the configuration's name and the mapped value is the configuration's actual value.
The Server
calls a Library
function which populates each specified configuration with the default values.
We will see the Library
code in a moment.
Then the Server
updates the B
service max
configuration with the value 512
.
What this configuration means and what it does is not important to our example.
Finally, the Server
"starts" the two services.
The starting process is represented by printing the configuration values to the standard output.
Let's now look at the Library
code:
final class Library {
private Library() {}
public static void defaults(ConfigBuilder... configs) {
Object[] values = configs;
addDefaultValues(values);
}
@SuppressWarnings("unchecked")
public static void defaults(Map<String, Object>... configs) {
Object[] values = configs;
addDefaultValues(values);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void addDefaultValues(Object[] values) {
for (Object input : values) {
if (input instanceof Map map) {
map.put("max", 256);
} else if (input instanceof ConfigBuilder b) {
b.integer("max", 256);
} else {
throw new IllegalArgumentException("Invalid type " + input.getClass());
}
}
}
}
We can see that the Library
supports the configuration in two forms:
-
a
ConfigBuilder
type; or -
our
Map<String, Object
> type.
It delegates to a private method where it sets a single default configuration value:
-
the
max
configuration has a default value of256
.
Notice that both public methods of our library declare varargs parameters. Additionally, and before invoking the private method, both public methods do the following:
Object[] values = configs;
Since in Java arrays are covariant the statement is valid.
Finally, notice that SuppressWarnings
were added to two of the Library
methods.
When we execute our Server
it runs successfully producing the following output:
Starting...
name=Service A
max=256
Starting...
name=Service B
max=512
Great! But let's change our server configuration to make it a little more "type-safe".
Introducing a ConfigValue
interface
Suppose now we decided that a Map<String, Object>
is a poor representation for our service's configuration.
We will model our configuration using a Map<String, ConfigValue>
instead.
Remember, the purpose of this example is to illustrate an unsafe varargs usage.
The ConfigValue
interface is given by the following:
sealed interface ConfigValue {
record IntValue(int value) implements ConfigValue {
@Override
public void apply(String key) {
System.out.format("%s=%d%n", key, value);
}
}
record StringValue(String value) implements ConfigValue {
@Override
public void apply(String key) {
System.out.format("%s=%s%n", key, value);
}
}
void apply(String key);
}
It supports integer and string values which is enough for our example.
As we have introduced this class, we need to change our Server
class:
public class Server {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
var serviceA = new HashMap<String, ConfigValue>();
serviceA.put("name", new StringValue("Service A"));
var serviceB = new HashMap<String, ConfigValue>();
serviceB.put("name", new StringValue("Service B"));
Library.defaults(serviceA, serviceB);
serviceB.put("max", new IntValue(512));
start(serviceA);
start(serviceB);
}
private static void start(Map<String, ConfigValue> config) {
System.out.println("Starting...");
for (var entry : config.entrySet()) {
var key = entry.getKey();
var value = entry.getValue();
value.apply(key);
}
System.out.println();
}
}
Notice that there are no more casts in our start
method.
So, in theory, we have succeeded in our improvement.
Or did we?
We have to update our Library
class as well.
Let's update the defaults
method signature to:
@SuppressWarnings("unchecked")
public static void defaults(Map<String, ConfigValue>... configs)
As all of our code compiles, we assume no more changes are required.
Let's try to execute our server:
Starting...
Exception in thread "main" java.lang.ClassCastException:
class java.lang.Integer cannot be cast to class iter2.ConfigValue
(java.lang.Integer is in module java.base of loader 'bootstrap';
iter2.ConfigValue is in unnamed module of loader 'app')
at iter2.Server.start(Server.java:36)
at iter2.Server.main(Server.java:26)
I formatted the output slightly so it is easier to read. The important part is:
java.lang.ClassCastException:
class java.lang.Integer cannot be cast to class iter2.ConfigValue
What happened? And how can we fix it?
We will see that in a moment.
For now, it seems that the "safe" term in the SafeVarargs
name refers to type-safety.
Heap pollution
The ClassCastException
we have experienced is the "symptom" of a condition known as heap pollution.
The term heap pollution is used in section 4.12.2 of the Java language specification:
It is possible that a variable of a parameterized type will refer to an object that is not of that parameterized type. This situation is known as heap pollution.
This is what happened in our last example.
Our configuration map is a parameterized type.
In the start
method, we expected the config
map to contain only ConfigValue
values.
In reality it had an Integer
value mapped to the max
key as indicated by the ClassCastException
message.
Continuing with the section 4.12.2 of the Java language specification, we find (emphasis mine):
Heap pollution can only occur if the program performed some operation involving a raw type that would give rise to a compile-time unchecked warning (...), or if the program aliases an array variable of non-reifiable element type through an array variable of a supertype which is either raw or non-generic.
While only one of the conditions is required, the Library
class does both of them.
First, in the addDefaultValues
method, it "performed some operation involving a raw type":
for (Object input : values) {
if (input instanceof Map map) {
map.put("max", 256);
} else ...
...
}
As it uses an instance of the Map
raw type.
Second, in the defaults(Map)
method, it "aliases an array variable of non-reifiable element type through an array variable of a supertype which is either raw or non-generic":
public static void defaults(Map<String, ConfigValue>... configs) {
Object[] values = configs;
addDefaultValues(values);
}
Where:
-
configs
is "an array variable of non-reifiable element type"; and -
values
is "an array variable of a supertype which is either raw or non-generic".
How to make our unsafe example safe?
To make our unsafe example safe we have to fix our Library
class.
From the previous section, we have to fix:
-
the operation involving a raw type; and
-
the aliasing of our varargs array.
One possible solution is:
final class Library {
private static final int MAX_DEFAULT_VALUE = 256;
private Library() {}
public static void defaults(ConfigBuilder... configs) {
for (var config : configs) {
config.integer("max", MAX_DEFAULT_VALUE);
}
}
public static void defaults(Map<String, ConfigValue>... configs) {
for (var config : configs) {
config.put("max", new IntValue(MAX_DEFAULT_VALUE));
}
}
}
Running our Server
now produces the following output:
Starting...
max=256
name=Service A
Starting...
max=512
name=Service B
Great! It works as expected.
But what about the SafeVarargs
annotation?
Is our example really safe?
The compiler warnings
Our last example runs. But the compilation raises two warnings:
$ javac -d /tmp -Xlint:all src/main/java/iter3/* src/main/java/shared/*
src/main/java/iter3/Library.java:33:
warning: [unchecked]
Possible heap pollution
from parameterized vararg type Map<String,ConfigValue>
public static void defaults(Map<String, ConfigValue>... configs) {
^
src/main/java/iter3/Server.java:31:
warning: [unchecked]
unchecked generic array creation
for varargs parameter of type Map<String,ConfigValue>[]
Library.defaults(serviceA, serviceB);
^
2 warnings
I formatted the output slightly so it is easier to read.
The first warning comes from the Library
class defaults(Map)
method.
The second warning comes from the Server
class.
And, worth noting, it is at the call-site of the method from the first warning.
Two methods with varargs. But warning in only one.
Our Library
class defines two methods with varargs parameters.
Their signatures are:
public static void defaults(ConfigBuilder... configs)
public static void defaults(Map<String, ConfigValue>... configs)
But compilation only raised a warning for the second one. Why didn't the first method trigger the same warning?
The SafeVarargs
annotation is actually defined by the Java language specification.
Section 9.6.4.7 states:
A variable arity parameter with a non-reifiable element type (§4.7) can cause heap pollution (§4.12.2) and give rise to compile-time unchecked warnings (§5.1.9).
In other words, a varargs parameter can only be unsafe if it is of a non-reifiable type.
So the question becomes: what are reifiable and non-reifiable types?
Reifiable types
Reifiable types are defined in section 4.7 of the Java language specification:
Because some type information is erased during compilation, not all types are available at run time. Types that are completely available at run time are known as reifiable types.
A type is reifiable if and only if one of the following holds:
It refers to a non-generic class or interface type declaration.
It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
It is a raw type (§4.8).
It is a primitive type (§4.2).
It is an array type (§10.1) whose element type is reifiable.
It is a nested type where, for each type T separated by a ".", T itself is reifiable.
We can translate the definition to the following Java code. Each of the parameters of the following methods is of a type that is reifiable:
// non-generic class or interface
foo(String s, IOException ex, Runnable r);
// parameterized type, unbounded wildcards
foo(List<?> l, Map<?,?> m);
// raw type
foo(List l, Map m);
// primitive types
foo(int i, boolean b);
// array, element type is reifiable
foo(int[] values, Path... files);
// nested type, each T is reifiable
foo(System.Logger logger, StackWalker.StackFrame frame);
Note that the return type of each method was not listed.
Varargs and reifiable types
Getting back to our Library
class, we have the defaults(ConfigBuilder)
method:
public static void defaults(ConfigBuilder... configs)
The varargs configs
parameter is of a type that is reifiable.
As ConfigBuilder
is reifiable, an array of ConfigBuilder
is also reifiable.
That is the reason the compiler did not issue a warning for this method.
Therefore, and regarding heap pollution, the combination:
-
varargs
-
reifiable types
is always safe.
Non-reifiable types
The previous section listed all of the possible reifiable types. So the complement of that must contain all of the non-reifiable types.
Joshua Bloch, in the Effective Java (second edition) book, says the following about non-reifiable types:
Intuitively speaking, a non-reifiable type is one whose runtime representation contains less information than its compile-time representation.
Let's see some examples. Each parameter of the following methods is of a type that is non-reifiable:
// type variables
foo(E e, T t);
// parameterized types
foo(List<String> l, Map<String, Object> m, Set<E> s);
// parameterized types, bounded wildcards
foo(List<? extends String> l, Function<? super T, ? extends R> f);
// array, element is non-reifiable
foo(E[] values, List<String>... lists);
Once again, the return type of each method was not listed. Additionally, the type parameters are implied to be defined either at the enclosing class or at the method itself.
Varargs and non-reifiable types
Going back to our Library
class once again, we have the defaults(Map)
method:
public static void defaults(Map<String, ConfigValue>... configs)
The varargs configs
parameter is of a type that is non-reifiable.
A parameterized type, Map<String, ConfigValue>
in this case, is non-reifiable.
Since it is non-reifiable, an array of those is also non-reifiable.
This is the reason the compiler issued a warning for this method.
Remember the SafeVarargs
section of the Java language specification (emphasis mine):
A variable arity parameter with a non-reifiable element type (§4.7) can cause heap pollution (§4.12.2)
Therefore, and regarding heap pollution, the combination:
-
varargs
-
non-reifiable types
is possibly unsafe. In other words, the combination can be safe under certain conditions.
Suppressing our first compiler warning
So the combination varargs and non-reifiable types can be safe under certain conditions. What are those conditions?
Remember the heap pollution section of the Java language specification (emphasis mine):
Heap pollution can only occur if the program performed some operation involving a raw type (...) or if the program aliases an array variable of non-reifiable element type through an array variable of a supertype which is either raw or non-generic.
In the last modification to the Library
class, we removed both of those unsafe operations.
Now, since:
-
heap pollution can only occur if those operations are performed; and
-
we have removed the unsafe operations.
We can conclude that our Library
class is safe regarding heap pollution.
Threfore, it is now safe to suppress the first compiler warning.
Let's annotate the default
method in the Library
class:
@SuppressWarnings("unchecked")
public static void defaults(Map<String, ConfigValue>... configs) {
for (var config : configs) {
config.put("max", new IntValue(MAX_DEFAULT_VALUE));
}
}
Let's compile our program:
$ javac -d /tmp -Xlint:all src/main/java/iter3/* src/main/java/shared/*
src/main/java/iter3/Server.java:31:
warning: [unchecked]
unchecked generic array creation
for varargs parameter of type Map<String,ConfigValue>[]
Library.defaults(serviceA, serviceB);
^
1 warning
Nice. We have successfully suppressed the first warning.
Suppressing our second compiler warning
Before we suppress our second warning, let's try to understand it. In order to do it, we will have to look into a series of concepts.
A new array is created at the varargs method call-site
Imagine we have the following method:
private static Set<String> asSet(String... strings) {
var set = new HashSet<String>();
for (String s : strings) {
set.add(s);
}
return set;
}
It accepts an array of strings (as varargs) and adds each element to a Set
instance.
An invocation of the form:
var set = asSet("A", "B", "B", "C", "C");
is actually compiled as:
var set = asSet(new String[] {"A", "B", "B", "C", "C"});
We can use the javap
tool to confirm.
It produces the following output for both versions:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: iconst_5
1: anewarray #16 // class java/lang/String
4: dup
5: iconst_0
6: ldc #18 // String A
8: aastore
(...)
24: dup
25: iconst_4
26: ldc #22 // String C
28: aastore
29: invokestatic #24 // Method asSet:([Ljava/lang/String;)Ljava/util/Set;
It creates a new array of String
, adds the string values to the array and invokes the asSet
method with the created array.
Arrays and non-reifiable types
It is not possible to create arrays of non-reifiable types.
For example, the following array creation expressions do not compile:
Map<String, ConfigValue>[] maps;
// does not compile!
maps = new Map<String, ConfigValue>[2];
class Container<E> {
// does not compile!
final E[] values = new E[10];
}
Why is it not possible? We will see that in a moment.
The java.lang.ArrayStoreException
In Java, arrays are reifiable. In other words, arrays know, at runtime, their component type.
The following program illustrates this fact.
It creates a String[]
and tries to add two elements to it:
public class TheArrayStoreException {
public static void main(String[] args) {
Object[] a = new String[2];
var c = a.getClass();
if (c.isArray()) {
var componentType = c.getComponentType();
System.out.println("Component type is " + componentType);
}
System.out.println("Index 0");
a[0] = "abc";
System.out.println("Index 1");
a[1] = 123;
}
}
Running this program produces the following output:
Component type is class java.lang.String
Index 0
Index 1
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
at iter4.TheArrayStoreException.main(TheArrayStoreException.java:22)
It fails at the last statement with an uncaught ArrayStoreException
.
So what is happening?
In Java, arrays are covariant. We have seen this before. The first statement in the program is valid:
Object[] a = new String[2];
Since our a
variable is of type Object[]
both assignments at the end of the program are also valid:
a[0] = "abc";
a[1] = 123;
However, as mentioned, arrays in Java are reifiable. So, while the two assignments are valid at compile-time, the last one fails at runtime:
the array knows at runtime it is only allowed to accept
String
values.
So it rejects the last assignment with a java.lang.ArrayStoreException
.
Erasure: generics do not have a ArrayStoreException
equivalent
From the previous example, we conclude that arrays behave substantially different than generics.
In Java, generics are invariant. In other words, unlike arrays, parameterized types are not covariant. Additionally, parameterized types (excluding the ones with unbounded wildcards) are non-reifiable.
Let's try to create a version of the previous example using a parameterized types:
public class TheArrayStoreExceptionCounter {
@SuppressWarnings({"rawtypes", "unchecked"})
public static void main(String[] args) {
List a = new ArrayList<String>(2);
var c = a.getClass();
var typeParameters = c.getTypeParameters();
for (var typeVar : typeParameters) {
System.out.println(typeVar);
}
System.out.println("Index 0");
a.add("abc");
System.out.println("Index 1");
a.add(123);
}
}
This program runs successfully producing the following output:
E
Index 0
Index 1
Therefore, in this example, the last statement did not fail.
Granted, this is a rather poor example.
It uses raw types and it suppresses unchecked warnings.
In other words, it would very likely fail with a ClassCastException
at some later point.
However, a proper example would not have compiled at all. Which is another major difference to the previous array example.
The point I am trying to make:
-
at runtime, a
String[]
knows it must only allowString
values. -
at runtime, a
List<String>
does not know that it must only allowString
values. This is enforced at compile-time and the information is erased at runtime.
Arrays and non-reifiable types (revisited)
Let's now combine the information of the two previous sections.
Suppose the following compiles (remember, it does not compile):
// does not compile!
Object[] a = new List<String>[2];
Then this assignment should be allowed:
a[0] = new ArrayList<String>();
While we would expect the following to throw an ArrayStoreException
:
a[1] = new ArrayList<Integer>();
Except, as we have seen in the previous section, the Integer
information is erased at runtime.
If the Integer
information is erased, how can our hypothetical List<String>[]
know it should not allow a List<Integer>
?
Well, it can't.
Therefore, it is not possible to create arrays of non-reifiable types.
Varargs and non-reifiable types
So how does the compiler work around these two opposing facts:
-
it has to create an array at the call site of a varargs method; and
-
it is not possible to create arrays of non-reifiable types?
In other words, consider a method having the following signature:
private static Set<String> asSet(List<String>... lists);
If we were to invoke like so:
var set = asSet(
Arrays.asList("A", "B", "C"),
Arrays.asList("B", "C", "D"));
As it is not possible to create arrays of non-reifiable types, the compiler cannot generate the following:
// does not compile!
var set = asSet(new List<String>[] {
Arrays.asList("A", "B", "C"),
Arrays.asList("B", "C", "D")});
So what is the solution? The compiler, instead, generates the following code:
var set = asSet(new List[] {
Arrays.asList("A", "B", "C"),
Arrays.asList("B", "C", "D")});
In other words, the compiler:
-
creates an array of the raw type
List
; and -
does an unchecked conversion from
List[]
toList<String>[]
.
Let's see this two steps in action. The following program does just that:
public class ArraysAndNonReifiableTypes {
public static void main(String[] args) {
List<String>[] lists = new List[2];
System.out.println(lists.length);
}
}
When we try to compile it the compiler gives two warnings:
$ javac -d /tmp -Xlint:all src/main/java/iter4/ArraysAndNonReifiableTypes.java
src/main/java/iter4/ArraysAndNonReifiableTypes.java:18:
warning: [rawtypes] found raw type: List
List<String>[] lists = new List[2];
^
missing type arguments for generic class List<E>
where E is a type-variable:
E extends Object declared in interface List
src/main/java/iter4/ArraysAndNonReifiableTypes.java:18:
warning: [unchecked] unchecked conversion
List<String>[] lists = new List[2];
^
required: List<String>[]
found: List[]
2 warnings
So the warnings are, in order, about:
-
raw type; and
-
unchecked conversion.
As we have mentioned.
Finally suppressing the second compiler warning
Let's recap the second compiler warning.
It was issued at the Server
class at the call-site of the Library.defaults
varargs method:
src/main/java/iter3/Server.java:31:
warning: [unchecked]
unchecked generic array creation
for varargs parameter of type Map<String,ConfigValue>[]
Library.defaults(serviceA, serviceB);
^
1 warning
The compiler is warning us that, in order to invoke that method, it will have to:
-
create an array of the raw type
Map
; and -
do an unchecked conversion from
Map[]
toMap<String, ConfigValue>[]
.
Both are potentially unsafe operations; depending on what the invoked method will do to the created array.
However, when we suppressed the first compiler warning, we concluded that the method is safe.
Therefore, we can now safely suppress the second compiler warning.
We cannot annotate an expression, so we must annotate the whole main
method, like so:
@SuppressWarnings("unchecked")
public static void main(String[] args) {
var serviceA = new HashMap<String, ConfigValue>();
serviceA.put("name", new StringValue("Service A"));
var serviceB = new HashMap<String, ConfigValue>();
serviceB.put("name", new StringValue("Service B"));
Library.defaults(serviceA, serviceB);
serviceB.put("max", new IntValue(512));
start(serviceA);
start(serviceB);
}
This iteration of our Server
now compiles without warning:
$ javac -d /tmp -Xlint:all src/main/java/iter3/* src/main/java/shared/*
[no warnings emitted]
Suppressing both warnings with SafeVarargs
There are two problems with our previous suppressions:
-
we had to suppress warnings for the whole
main
method.
This means that we might have inadvertently suppressed an unchecked warning coming from another source. -
we have to suppress warnings for each and every invocation of our
Library.defaults
method.
In fact, the suppression is required each time any varargs method invocation implies the creation of an array of non-reifiable types.
Therefore, in order to mitigate these problems, the SafeVarargs
annotation was introduced in Java 7.
Remember that both generics and varargs were introduced in Java 5.
Let's apply the annotation to our running example.
We first annotate our defaults
method with the annotation:
@SafeVarargs
public static void defaults(Map<String, ConfigValue>... configs) {
for (var config : configs) {
config.put("max", new IntValue(MAX_DEFAULT_VALUE));
}
}
And we can remove the SuppressWarnings
annotation from the main method:
public class Server {
public static void main(String[] args) {
...
}
private static void start(Map<String, ConfigValue> config) {
...
}
}
And it compiles without warnings:
$ javac -d /tmp -Xlint:all src/main/java/iter5/* src/main/java/shared/*
[no warnings emitted]
A final note on SafeVarargs
The SafeVarargs
annotation can only be added to methods that cannot be overridden.
The annotation javadocs states (emphasis mine):
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 nor private.
In other words:
-
the method must be
static
; or -
if it is an instance method, it must be private; or
-
if it is an instance method and it is not private, then it must be final.
The reason is that a safe method can be made unsafe by overriding it.
A final note on heap pollution
Consider the following method:
// not safe!!!
private static <T> T[] genericArray(T... values) {
return values;
}
The genericArray
method simply returns the varargs argument.
Apart from that, it does not do any operation at all to the array.
Can we annotate this method with SafeVarargs
?
The answer is no. This is definitely not a safe method.
To illustrate it, consider the following usage of the method:
public class FinalNote {
public static void main(String[] args) {
List<String>[] generic = genericArray(
Arrays.asList("A", "B", "C"),
Arrays.asList("D", "E", "F"));
Object[] alias = generic;
alias[0] = Arrays.asList(1, 2, 3);
String a = generic[0].get(0);
System.out.println(a);
}
private static <T> T[] genericArray(T... values) {
return values;
}
}
It fails with a ClassCastException
:
Exception in thread "main" java.lang.ClassCastException:
class java.lang.Integer cannot be cast to class java.lang.String
(java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at iter6.FinalNote.main(FinalNote.java:21)
Remember section 4.12.2 of the Java language specification:
Heap pollution can only occur (...) if the program aliases an array variable of non-reifiable element type through an array variable of a supertype which is either raw or non-generic.
By returning the array as it is we allowed another part of the program to "alias the array variable".
Conclusion
In this blog post we took a deep look at the SafeVarargs
annotation.
We started with an example containing an unsafe use of a varargs argument. It caused a situation known as heap pollution.
We fixed our example so it was type-safe. But the compiler would issue two warnings.
In the process of suppressing those two warnings we looked into:
-
varargs and reifiable types
-
varargs and non-reifiable types
-
array creation at the varargs call-site
-
arrays covariant vs generics invariant
-
the
java.lang.ArrayStoreException
-
generics and type-erasure
-
why it is not possible to create an array of a non-reifiable type
-
raw types and unchecked conversions
Finally we suppresed the warnings with a SafeVarargs
annotation.
The source code for all of the examples can be found at this GitHub repository.
References
Apart from the Java language specification and the javadocs for the SafeVarargs
annotation, I also used the following as reading material for this blog post:
-
PROPOSAL: Simplified Varargs Method Invocation / Bob Lee (crazybob)
-
Effective Java (2nd edition) item 25: Prefer lists to arrays / Joshua Bloch
-
Effective Java (3rd edition) item 32: Combine generics and varargs judiciously / Joshua Bloch
Additionally, I believe this is the tracker bug for SafeVarargs
: