I have a method that is supposed to return an instance of a given collection class with the given entries. The implementation is shown below.
public static <E, C extends Collection<E>> C
initalizeAndAddToCollection(Class<C> clazz, E... entries) {
//Collection object
Collection<E> collection;
//try to invoke default constructor of C
try {
collection = clazz.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException();
}
//Add elements to collection
for (E entry: entries)
collection.add(entry);
return (C) collection;
}
The problem is that the following code runs even though a double
should not be able to be stored in a list of type Integer
//Create and instantiate an ArrayList with element 1.0
ArrayList<Integer> list = initalizeAndAddToCollection(ArrayList.class, 1.0);
System.out.print(list.get(0));
Why does this code run, and how do I make it so that it results in either a compile or runtime error?
Edit: I noticed list.get(0).getClass()
does generate an exception, but I am not sure why either (or why the previous code does not).
In Java, generics are a compile-time only feature because they're implemented via type erasure . So while you may think you're creating an ArrayList<Integer>
at runtime with your newInstance
call, you're really just creating an ArrayList
.
In short, reflection breaks type safety for generics in Java.
This code gives a warning when I compile it. The warning says:
Note: MyTest.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
And in fact, when you do what it produces 4 warning messages:
MyTest.java:5: warning: [unchecked] Possible heap pollution from parameterized vararg type E
initalizeAndAddToCollection(Class<C> clazz, E... entries) {
^
where E,C are type-variables:
E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
MyTest.java:18: warning: [unchecked] unchecked cast
return (C) collection;
^
required: C
found: Collection<E>
where E,C are type-variables:
E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
MyTest.java:23: warning: [unchecked] unchecked method invocation: method initalizeAndAddToCollection in class MyTest is applied to given types
initalizeAndAddToCollection(ArrayList.class, 1.0);
^
required: Class<C>,E[]
found: Class<ArrayList>,double
where C,E are type-variables:
C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
MyTest.java:23: warning: [unchecked] unchecked conversion
initalizeAndAddToCollection(ArrayList.class, 1.0);
^
required: ArrayList<Integer>
found: ArrayList
4 warnings
These warnings explain why this seemingly incorrect code is compiling. You are doing things that the compiler would tell you are incorrect.
Why does this code run.
Because it is not actually breaking runtime type-safety.
Type erasure means that the collection is actually storing references to Object
.
When you then do System.out.print(list.get(0));
the parameter type for the print
call is Object
. That means no implicit cast to Integer
is needed.
and how do I make it so that so that it results in either a compile or runtime error?
If you need a compile time error, tell the compiler to treat warnings as errors. (Or check for warnings in the compilation output.)
If you need a runtime error, you need to add some explicit runtime type checks probably in your initalizeAndAddToCollection
method.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.