Why can't I cast a Collection<GenericFoo> to a Collection<GenericFoo<?>> - java

The crux of the question is, why does this cause a compile-time error?
List<Collection> raws = new ArrayList<Collection>();
List<Collection<?>> c = raws; // error
Background
I understand why generics aren't covariant in general. If we could assign List<Integer> to List<Number>, we'd expose ourselves to ClassCastExceptions:
List<Integer> ints = new ArrayList<Integer>();
List<Number> nums = ints; // compile-time error
nums.add(Double.valueOf(1.2));
Integer i = ints.get(0); // ClassCastException
We get a compile-time error at line 2 to save us from a run-time error at line 4. That makes sense.
List<C> to List<C<?>>
But how about this:
List<Collection> rawLists = new ArrayList<Collection>();
List<Collection<?>> wildLists = rawLists; // compile-time error
// scenario 1: add to raw and get from wild
rawLists.add(new ArrayList<Integer>());
Collection<?> c1 = wildLists.get(0);
Object o1 = c1.iterator().next();
// scenario 2: add to wild and get from raw
wildLists.add(new ArrayList<String>());
Collection c2 = rawLists.get(0);
Object o2 = c2.iterator().next();
In both scenarios, ultimately I get only get Object elements without casting, so I can't get a "mysterious" ClassCastException.
The section in the JLS that corresponds to this is §4.10.2, so I understand why the compiler is giving me the error; what I don't get is why the spec was written this way, and (to ward off speculative/opinion-based answers), whether it actually provides me any compile-time safety.
Motivating example
In case you're wondering, here's (a stripped-down version of) the use case:
public Collection<T> readJsons(List<String> jsons, Class<T> clazz) {
List<T> list = new ArrayList<T>();
for (String json : jsons) {
T elem = jsonMapper.readAs(json, clazz);
list.add(elem);
}
return list;
}
// call site
List<GenericFoo<?>> foos = readJsons(GenericFoo.class); // error
The error is because GenericFoo.class has type Class<GenericFoo>, not Class<GenericFoo<?>> (§15.8.2). I'm not sure why that is, though I suspect it's a related reason; but regardless, that wouldn't be a problem if Class<GenericFoo> could be casted — either implicitly or explicitly — to Class<GenericFoo<?>>.

First of all, raw type and wildcard type are quite different. For one, raw type completely erases all generic information.
So we have List<x> and List<y> where x is not y. This is certainly not subtype relationship.
You can, nevertheless, ask the casting to be allowed. But please read
JLS 5.5.1 , and tell me you want to add something more to it:) Browse the whole page, actually, it's a great wall of text just for casting.
And remember this is just the first ripple in the whole effect. What about List<List<x>> and List<List<y>>, etc.

Related

Java: (Lack of) Errors in Generic Typecasting

I can't figure out why I'm not getting errors in my Java code. I have a class which uses a generic type:
import java.util.*; // For ArrayList
public class Hat<T>
{
public ArrayList<T> convert(String s)
{
T t = (T) s; // Cast happens here
ArrayList<T> list = new ArrayList<T>();
list.add(t);
return list;
}
}
Then, I execute some code which I think should create an error:
Hat<Integer> h = new Hat<Integer>();
ArrayList<Integer> iList = h.convert("hello");
What this does is creates an ArrayList of Integers, that, somehow, has a String as an element! This does not throw any errors on runtime, not even if you print the ArrayList (it prints "[hello]").
I would've expected an error to be thrown from the "convert" method. Why does this not happen, and is it possible to make it happen? Interestingly, it happens when I try to get the element back from the ArrayList as an Integer, but the error doesn't come from the "convert" method.
In Java, generics are only used at compile-time; they are "erased" after the type-checker validates the program and have no effect on the program's execution. In particular, at runtime there's no difference between an ArrayList<Integer> and an ArrayList<String> (or an ArrayList of anything else, for that matter). After typechecking is complete, your program is erased, and the program that executes is equivalent to:
public class Hat
{
public ArrayList convert(String s)
{
Object t = s;
ArrayList list = new ArrayList();
list.add(t);
return list;
}
}
Hat h = new Hat();
ArrayList iList = h.convert("hello");
which behaves the way you observed.
So the question is, why does this program typecheck when it obviously produces a bad value that claims to be an ArrayList<Integer> but contains strings? Shouldn't the type system reject programs like that?
Well, it does, except that there's a big loophole: unchecked casts. When you do a cast to a type that involves a generic -- in your case, the line T t = (T) s; -- Java doesn't have anything at runtime that it could use to test if the cast is valid, due to erasure. The Java designers could have just disallowed that kind of cast, in which case your program would fail to compile.
They didn't do it that way, though. Instead, they chose to allow casts that involve generics and trust that the programmer who wrote the cast was smarter than the compiler and knew the cast would work out. If you use one of these casts, though, all bets are off, and the type system can end up, as you discovered, with ArrayList<Integer>s that actually contain strings. So to warn you that you need to be careful, they had the compiler
but to issue an "unchecked cast" warning whenever you write such a cast, reminding you that there's a suspicious cast and it's up to you to prove that it's correct. In codebases I've worked on, unchecked casts need to be annotated with #SuppressWarning and a comment describing why the cast is always valid.
So what if you want to deal with unchecked casts and you'd rather issue a runtime check? In that case you're going to have to program the runtime check yourself. You can often do this with Class objects. In your case, you could add an extra Class parameter to your Hat constructor that represents the class you expect T to be, and use it to make a typesafe cast that's checked at runtime:
public class Hat<T>
{
private final Class<? extends T> expectedClass;
public Hat(Class<? extends T> expectedClass)
{
this.expectedClass = expectedClass;
}
public ArrayList<T> convert(String s)
{
T t = expectedClass.cast(s); // This cast will fail at runtime if T isn't String
ArrayList<T> list = new ArrayList<T>();
list.add(t);
return list;
}
}
Then your callsite would need to change to:
Hat<Integer> h = new Hat<Integer>(Integer.class);
ArrayList<Integer> iList = h.convert("hello"); // throws

Confusion regarding super use in Generics Java [duplicate]

I gather that you cannot bind a Java generics type parameter to a lower bound (i.e. using the super keyword). I was reading what the Angelika Langer Generics FAQ had to say on the subject. They say it basically comes down to a lower bound being useless ("not making any sense").
I'm not convinced. I can imagine a use for them to help you be more flexible to callers of a library method that produces a typed result. Imagine a method that created an array list of a user-specified size and filled it with the empty string. A simple declaration would be
public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);
But that's unnecessarily restrictive to your clients. Why can't they invoke your method like this:
//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);
//shouldn't compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);
At this point I would be tempted to try the following definition:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
List<T> list = new ArrayList<T>(size);
for(int i = 0; i < size; i++) {
list.add("");
}
return list;
}
But it will not compile; the super keyword is illegal in this context.
Is my example above a bad example (ignoring what I say below)? Why isn't a lower bound useful here? And if it would be useful, what's the real reason that it is not permitted in Java?
P.S.
I know that a better organization might be something like this:
public static void populateListWithEmptyStrings(List<? super String> list, int size);
List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);
Can we for the purpose of this question pretend that due to a requirement, we need to do both operations in one method call?
Edit
#Tom G (justifiably) asks what benefit having a List<CharSequence> would have over a List<String>. For one, nobody said the returned list is immutable, so here's one advantage:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));
Basically, its not useful enough.
I think your example points out the only advantage of a lower bound, a feature the FAQ calls Restricted Instantiation:
The bottom line is: all that a " super " bound would buy you is the restriction that only supertypes of Number can be used as type arguments. ....
But as the other posts point out, the usefulness of even this feature can be limited.
Due to the nature of polymorphism and specialization, upper bounds are far more useful than lower bounds as described by the FAQ (Access To Non-Static Members and Type Erasure). I suspect the complexity introduced by lower bounds aren't worth its limited value.
OP: I want to add I think you did show it is useful, just not useful enough. Come up with the irrefutable killer use cases and I'll back the JSR. :-)
the spec does talk about lower bounds of type parameters, for example
4.10.2
a type variable is a direct supertype of its lower bound.
5.1.10
a fresh type variable ... whose lower bound
It appears that a type variable only has a (non-null) lower bound if it's a synthetic one as result of wildcard capture. What if the language allow lower bounds on all type parameters? Probably it doesn't cause a lot of trouble, and it's excluded only to keep generics simpler (well ...) Update it is said that theoretical investigation of lower bounded type parameters is not thoroughly conducted.
Update: a paper claiming lower bounds are ok: "Java Type Infererence Is Broken: Can We Fix It" by Daniel Smith
RETRACT: the following argument is wrong. OP's example is legitimate.
Your particular example is not very convincing. First it's not type safe. The returned list is indeed a List<String>, it's unsafe to view it as another type. Suppose your code compiles:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
then we can add non-String to it, which is wrong
CharSequence chars = new StringBuilder();
l2.add(chars);
Well a List<String> is not, but somewhat like a list of CharSequence. Your need can be solved by using wildcard:
public static List<String> createArrayListFullOfEmptyStrings(int size)
// a list of some specific subtype of CharSequence
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);
// illegal, won't compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());
More than an answer, this is another (possibly killer?) use case.
I have a ModelDecorator helper. I want it to have the following public API
class ModelDecorator<T>{
public static <T> ModelDecorator<T> create(Class<T> clazz);
public <SUPER> T from(SUPER fromInstance);
}
So, given classes A, B extends A, it can be used like this:
A a = new A();
B b = ModelDecorator.create(B.class).from(a);
But I want to have bounds on T and SUPER, so I make sure that only subclases can be instantiated using the API. At this moment, I can do:
C c = new C();
B b = ModelDecorator.create(B.class).from(c);
Where B DOES not inherit from C.
Obviously, if I could do:
public <SUPER super T> T from(SUPER fromInstance);
That would solve my problem.
What advantage does typing the List give you at that point? When you iterate over the returned collection, you should still be able to do the following:
for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
Edit: I bring good news. There is a way to get most of what you want.
public static <R extends List<? super String>> R createListFullOfEmptyString(IntFunction<R> creator, int size)
{
R list = creator.apply(size);
for (int i = 0; i < size; i++)
{
list.add("");
}
return list;
}
// compiles
List<Object> l1 = createListFullOfEmptyString(ArrayList::new, 5);
List<CharSequence> l2 = createListFullOfEmptyString(ArrayList::new, 5);
List<String> l3 = createListFullOfEmptyString(ArrayList::new, 5);
// doesn't compile
List<Integer> l4 = createListFullOfEmptyString(ArrayList::new, 5);
The downside is clients do need to provide either an instance of R to mutate, or some means to construct an R. There is no other way to safely construct it.
I'll retain my original answer below for informational purposes.
In summary:
There is not a good reason, it just has not been done.
And until such time as it is, it will be impossible to write exact types with correct variance for methods that do all of:
A) Accept or create parametrized data structure
B) Write computed (not-passed-in) value(s) to that data structure
C) Return that data structure
Writing/accepting values is exactly the case where contravariance applies, which means the type parameter on the data structure must be lower-bounded by the type of the value being written to the data structure. The only way to express that in Java currently is using a lower-bounded wildcard on the data structure, e.g. List<? super T>.
If we are designing an API such as the OP's, which might naturally (but not legally) be expressed as:
// T is the type of the value(s) being computed and written to the data structure
// Method creates the data structure
<S super T> Container<S> create()
// Method writes to the data structure
<S super T> Container<S> write(Container<S> container)
Then the options available to us are:
A) Use a lower-bounded wildcard, and force callers to cast the output:
// This one is actually useless - there is no type the caller can cast to that is both read- and write-safe.
Container<? super T> create()
// Caller must cast result to the same type they passed in.
Container<? super T> write(Container<? super T> container)
B) Overly restrict the type parameter on the data structure to match the type of the value being written, and force callers to cast the input and output:
// Caller must accept as-is; cannot write values of type S (S super T) into the result.
Container<T> create()
// Caller must cast Container<S> (S super T) to Container<T> before calling, then cast the result back to Container<S>.
Container<T> write(Container<T> container)
C) Use a new type parameter and do our own unsafe casting internally:
// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> create()
// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> write(Container<S> container)
Pick your poison.
Hmm, ok - let's work with this. You define a method:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
What does that mean? It means that if I call your method, then I get back a list of some superclass of String. Maybe it returns a list of String. Maybe it returns a list of Object. I don't know.
Cool.
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
According to you, that should compile. But that's not right! I can put an Integer into a list of Object - l1.add(3) . But if you are returning a list of String, then doing that should be illegal.
List<String> l3 = createArrayListFullOfEmptyStrings(5);
According to you, that should compile. But that's not right! l3.get(1) should always return a String ... but that method might have returned a list of Object, meaning that l3.get(1) could conceivably be an Integer.
The only thing that works is
List<? super String> l5 = createArrayListFullOfEmptyStrings(5);
All I know is that I can safely call l4.put("foo"), and I can safely get Object o = l4.get(2) .

Java Generics : Casting a raw type to any reifiable type doesn't generate unchecked cast warning

I have the following question regarding the code below:
public class GenericBridgeMethods <T> {
public static void main(String[] args) {
List obj = new ArrayList<Integer>();
List <?> l1 = (List<?>) obj; // clause 1
GenericBridgeMethods <?> g1 = (GenericBridgeMethods<?>) obj; // clause 2
}
}
a. Clause 1 of course won't give an unchecked cast warning
b. Clause 2 also did not give an unchecked cast warning
I noticed that a cast from a raw type (obj) to a ANY reifiable type (like GenericBridgeMethods or GenericBridgeMethods <?>) will not give a unchecked cast warning. If you run this code, a runtime error will occur at clause 2.
Shouldn't the compiler give a warning at clause 2
EDIT 1:
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Comparable c1 = (Comparable)a1; // clause 5
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Comparable c2 = (Comparable)l1; // clause 8
Can anyone explain why only clause 4 has error?
Well, first off in GenericBridgeMethods as you have defined it, T is not a reifiable type. Reifiable means that the type will be encoded into the class and will be available at runtime. That is not true of T.
Clause 2 does not give a runtime warning because it is checked: There will be a runtime check that obj is type-assignable to the GenericBridgeMethods type. Since you've opted for a wildcard as the type parameter, nothing about T needs to be checked.
If on the other hand you did something like this:
GenericBridgeMethods<String> g1 = (GenericBridgeMethods<String>) obj;
that would give you an unchecked assignment warning because the fact that obj is a GenericBridgeMethods of Strings cannot be checked at runtime. However, the same warning would appear if you had done this:
List<String l1 = (List<String>) obj;
Edit
If you're confused as to why the compiler allows you to try to cast a List to a GenericBridgeMethods, the answer is because the compiler can't know the entire hierarchy of GenericBridgeMethods and its subclasses. There could be a subclass of GenericBridgeMethods that implements List, in which case the cast might be legitimate.
You will however get a compile error if you made GenericBridgeMethods a final class (and thus prevented it from having subclasses). In this case, you will get an inconvertable types error.
Just to show you your question has little to do with reifiable types and generics, take a look at this:
public static void main(String[] args) {
List obj = new ArrayList<Integer>();
//this is allowed (no warning), even though it will fail at runtime
CharSequence sequence = (CharSequence) obj;
}
You can explicitly cast obj to a CharSequence even though you know that it will fail at runtime. The reason is because all the compiler knows is that obj is a type of List. Since List is an interface, there could be an implementation of CharSequence that is also a List, and so the cast must be permitted.
Every explicit cast carries a degree of possibility that it could fail at runtime. Otherwise, it would be a redundant cast and the compiler should allow you to omit the explicit cast.
Edit - Regarding your "edit #1"
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Comparable c1 = (Comparable)a1; // clause 5
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Comparable c2 = (Comparable)l1; // clause 8
You are wondering why only "clause 4" does not compile. I think I explained this already above and in the comments, but I'll go thsough this specific example for you step-by-step.
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Casting a1 to Number does not work because Number and ArrayList are both classes, not interfaces. Because Java does not allow inheritance from multiple classes, for an object to be an instance of both Number and ArrayList, Number would have to be a subclass of ArrayList or vice versa. This is known to not be true at compile time.
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Comparable c1 = (Comparable)a1; // clause 5
Since Comparable is an interface, a subclass of ArrayList might be a Comparable.
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Since List is an interface a subclass of Number could implement List. The compiler does not know when checking the cast that l1 holds an ArrayList.

Why can't a Generic Type Parameter have a lower bound in Java?

I gather that you cannot bind a Java generics type parameter to a lower bound (i.e. using the super keyword). I was reading what the Angelika Langer Generics FAQ had to say on the subject. They say it basically comes down to a lower bound being useless ("not making any sense").
I'm not convinced. I can imagine a use for them to help you be more flexible to callers of a library method that produces a typed result. Imagine a method that created an array list of a user-specified size and filled it with the empty string. A simple declaration would be
public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);
But that's unnecessarily restrictive to your clients. Why can't they invoke your method like this:
//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);
//shouldn't compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);
At this point I would be tempted to try the following definition:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
List<T> list = new ArrayList<T>(size);
for(int i = 0; i < size; i++) {
list.add("");
}
return list;
}
But it will not compile; the super keyword is illegal in this context.
Is my example above a bad example (ignoring what I say below)? Why isn't a lower bound useful here? And if it would be useful, what's the real reason that it is not permitted in Java?
P.S.
I know that a better organization might be something like this:
public static void populateListWithEmptyStrings(List<? super String> list, int size);
List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);
Can we for the purpose of this question pretend that due to a requirement, we need to do both operations in one method call?
Edit
#Tom G (justifiably) asks what benefit having a List<CharSequence> would have over a List<String>. For one, nobody said the returned list is immutable, so here's one advantage:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));
Basically, its not useful enough.
I think your example points out the only advantage of a lower bound, a feature the FAQ calls Restricted Instantiation:
The bottom line is: all that a " super " bound would buy you is the restriction that only supertypes of Number can be used as type arguments. ....
But as the other posts point out, the usefulness of even this feature can be limited.
Due to the nature of polymorphism and specialization, upper bounds are far more useful than lower bounds as described by the FAQ (Access To Non-Static Members and Type Erasure). I suspect the complexity introduced by lower bounds aren't worth its limited value.
OP: I want to add I think you did show it is useful, just not useful enough. Come up with the irrefutable killer use cases and I'll back the JSR. :-)
the spec does talk about lower bounds of type parameters, for example
4.10.2
a type variable is a direct supertype of its lower bound.
5.1.10
a fresh type variable ... whose lower bound
It appears that a type variable only has a (non-null) lower bound if it's a synthetic one as result of wildcard capture. What if the language allow lower bounds on all type parameters? Probably it doesn't cause a lot of trouble, and it's excluded only to keep generics simpler (well ...) Update it is said that theoretical investigation of lower bounded type parameters is not thoroughly conducted.
Update: a paper claiming lower bounds are ok: "Java Type Infererence Is Broken: Can We Fix It" by Daniel Smith
RETRACT: the following argument is wrong. OP's example is legitimate.
Your particular example is not very convincing. First it's not type safe. The returned list is indeed a List<String>, it's unsafe to view it as another type. Suppose your code compiles:
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
then we can add non-String to it, which is wrong
CharSequence chars = new StringBuilder();
l2.add(chars);
Well a List<String> is not, but somewhat like a list of CharSequence. Your need can be solved by using wildcard:
public static List<String> createArrayListFullOfEmptyStrings(int size)
// a list of some specific subtype of CharSequence
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);
// illegal, won't compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());
More than an answer, this is another (possibly killer?) use case.
I have a ModelDecorator helper. I want it to have the following public API
class ModelDecorator<T>{
public static <T> ModelDecorator<T> create(Class<T> clazz);
public <SUPER> T from(SUPER fromInstance);
}
So, given classes A, B extends A, it can be used like this:
A a = new A();
B b = ModelDecorator.create(B.class).from(a);
But I want to have bounds on T and SUPER, so I make sure that only subclases can be instantiated using the API. At this moment, I can do:
C c = new C();
B b = ModelDecorator.create(B.class).from(c);
Where B DOES not inherit from C.
Obviously, if I could do:
public <SUPER super T> T from(SUPER fromInstance);
That would solve my problem.
What advantage does typing the List give you at that point? When you iterate over the returned collection, you should still be able to do the following:
for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
Edit: I bring good news. There is a way to get most of what you want.
public static <R extends List<? super String>> R createListFullOfEmptyString(IntFunction<R> creator, int size)
{
R list = creator.apply(size);
for (int i = 0; i < size; i++)
{
list.add("");
}
return list;
}
// compiles
List<Object> l1 = createListFullOfEmptyString(ArrayList::new, 5);
List<CharSequence> l2 = createListFullOfEmptyString(ArrayList::new, 5);
List<String> l3 = createListFullOfEmptyString(ArrayList::new, 5);
// doesn't compile
List<Integer> l4 = createListFullOfEmptyString(ArrayList::new, 5);
The downside is clients do need to provide either an instance of R to mutate, or some means to construct an R. There is no other way to safely construct it.
I'll retain my original answer below for informational purposes.
In summary:
There is not a good reason, it just has not been done.
And until such time as it is, it will be impossible to write exact types with correct variance for methods that do all of:
A) Accept or create parametrized data structure
B) Write computed (not-passed-in) value(s) to that data structure
C) Return that data structure
Writing/accepting values is exactly the case where contravariance applies, which means the type parameter on the data structure must be lower-bounded by the type of the value being written to the data structure. The only way to express that in Java currently is using a lower-bounded wildcard on the data structure, e.g. List<? super T>.
If we are designing an API such as the OP's, which might naturally (but not legally) be expressed as:
// T is the type of the value(s) being computed and written to the data structure
// Method creates the data structure
<S super T> Container<S> create()
// Method writes to the data structure
<S super T> Container<S> write(Container<S> container)
Then the options available to us are:
A) Use a lower-bounded wildcard, and force callers to cast the output:
// This one is actually useless - there is no type the caller can cast to that is both read- and write-safe.
Container<? super T> create()
// Caller must cast result to the same type they passed in.
Container<? super T> write(Container<? super T> container)
B) Overly restrict the type parameter on the data structure to match the type of the value being written, and force callers to cast the input and output:
// Caller must accept as-is; cannot write values of type S (S super T) into the result.
Container<T> create()
// Caller must cast Container<S> (S super T) to Container<T> before calling, then cast the result back to Container<S>.
Container<T> write(Container<T> container)
C) Use a new type parameter and do our own unsafe casting internally:
// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> create()
// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> write(Container<S> container)
Pick your poison.
Hmm, ok - let's work with this. You define a method:
public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
What does that mean? It means that if I call your method, then I get back a list of some superclass of String. Maybe it returns a list of String. Maybe it returns a list of Object. I don't know.
Cool.
List<Object> l1 = createArrayListFullOfEmptyStrings(5);
According to you, that should compile. But that's not right! I can put an Integer into a list of Object - l1.add(3) . But if you are returning a list of String, then doing that should be illegal.
List<String> l3 = createArrayListFullOfEmptyStrings(5);
According to you, that should compile. But that's not right! l3.get(1) should always return a String ... but that method might have returned a list of Object, meaning that l3.get(1) could conceivably be an Integer.
The only thing that works is
List<? super String> l5 = createArrayListFullOfEmptyStrings(5);
All I know is that I can safely call l4.put("foo"), and I can safely get Object o = l4.get(2) .

Java: Casting Object to a generic type

In Java when casting from an Object to other types, why does the second line produce a warning related to the cast, but the first one doesn't?
void a(Object o) {
Integer i = (Integer) o;
List<Integer> list = (List<Integer>) o;
}
/*Type safety: Unchecked cast from Object to List<Integer>*/
It's because the object won't really be checked for being a List<Integer> at execution time due to type erasure. It'll really just be casting it to List. For example:
List<String> strings = new ArrayList<String>();
strings.add("x");
Object o = strings;
// Warning, but will succeeed at execution time
List<Integer> integers = (List<Integer>) o;
Integer i = integers.get(0); // Bang!
See Angelika Langer's Java Generics FAQ for more info, particularly the type erasure section.
Jon's answer is the right one, but occasionally you can't get around that warning (like when you're working with a legacy API). In those cases you can suppress the warning like so:
#SuppressWarnings("unchecked")
List<Integer> list = (List<Integer>) someApiThatReturnsNonGenericList();
For clarity, let me slightly rewrite the examples...
I would say, the cruxial difference between:
void a(Object o) {
Integer i = (Integer) o;
...
}
and
void a(Object o) {
List<Integer> list = (List<Integer>) o;
...
}
is that, given that there is a type-error, the first cast will always immediately throw a RuntimeException (specifically, a ClassCastException) when executed.
While the second one might not -- as long as the input parameter o is any kind of List<?>, execution will just proceed, in spite of an incorrect cast.
Whether the code will somewhere later throw an exception or not, depends upon what you do with the list.
But regardless, an Exception might not be thrown at the line where the cast was made, but somewhere else (which might be a difficult bug to trace down) or not at all.
That's what I understand, is the reason the compiler-designers considered a warning appropriate in the second case only.

Categories

Resources