Java generics are invariant so it's impossible to do such cast:
List<Object> li = (List<Object>)new ArrayList<Integer>();
But in the following code in line 4 I can cast from List<Integer> to List<T>, where T could be any type. Why is that type of cast allowed?
I know it generates warning about unchecked cast, but the point is that this cast is possible inside parametrized method, but not in normal code. Keeping in mind that generics are invariant why is it allowed? In normal code when having List<Integer> I can only cast it to List<Integer> which doesn't make sense and other casts are illegal. So what is the point of allowing such cast as in line 4?
I know that generic types are removed at compile time and it ends with List xlist = (List)list, but before removing those types it is obvious that this cast should not be allowed unless it is accepted only for the case when someone passes Integer as el which doesn't make much sense.
class Test {
public static <T> void t(List<Integer> list, T el) {
List<T> xlist = (List<T>)list; //OK
xlist.add(el);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
t(list, "a");
t(list, "b");
//prints [a, b] even if List type is Integer
System.out.println(list);
}
}
In Java, it is a compile error to do an explicit cast that is known at compile time to be always incorrect or always correct. A cast from List<Integer> to List<Object> is known at compile time to be always incorrect, therefore it is not allowed. A cast from List<Integer> to List<T> is not known at compile time to be always incorrect -- it would be correct if T were Integer, and T is not known at compile time.
I guess the answer is that this cast is possible only because Integer can be passed as argument el and in that case that cast will be correct. In all other cases of types other than Integer it will be illegal cast. But as I see in generics, if there is at least one type which would give correct cast (Integer) then the compiler doesn't give an error, but warning, even if all other cases are invalid.
Object is a super class of the class Integer. List<Object> is not a super class of the class List<Integer> , but furthermore if you would like a List<Object> to reference to a List<Integer> you can use joker sign (?).
List<?> list1 = new List<Object>();
List<?> list2 = new List<Integer>();
list1 = list2;
Related
I ask this question because of a discussion about one answer here on Stack. The statement is the following:
Given the following code:
List<?> list =new ArrayList<Integer>();
Why can't we do:
Integer e = 2;
list.add(e);
This throws a compiler error, despite the fact that we instantiated the list as an ArrayList<Integer>.
Why is that ?
Because a List<?> could be any sort of List (List<String> for example). And the compiler should not permit adding the wrong type to a list.
However, if you know the actual class then you can do a class cast at runtime:
((List<Integer>)list).add(e);
Code like this should be avoided since it can generate a ClassCastException if an unexpected type is encountered at runtime. To make matters worse (as noted by luk2302), our ClassCastException might only occur in an entirely different area of the code-- namely, when we are retrieving something from the list.
A better approach
If you know that the list will be of a specific type or a superclass of that type, then you could define the variable using a bounded wildcard:
List<? super Integer> list;
Integer e = 2;
list = new ArrayList<Integer>();
list.add(e);
list = new ArrayList<Number>();
list.add(e);
list = new ArrayList<Object>();
list.add(e);
This approach, as noted by M. Prokhorov, allows us to avoid the need for an inadvisable cast.
Just create an Arraylist of and they will let you add all, because Integer, String and Boolean are child or in other words Object class is their parent.
I've read a few topics which cover certain questions about generics, such as their relationship with raw types. But I'd like an additional explanation on a certain line found in the Java SE tutorial on unbound generics .
According to a sentence :
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print List<Integer>, List<String>, List<Double>, and so on, because they are not subtypes of List<Object>.
If I understand well this sentence; the difference between List<?> and List<Object>, is that we can use the type argument List<String> or List<Integer> by implementing the former. While if we implement the later, we can only use the type argument List<Object>. As if List<?> is an upper bound to Object namely List<? extends Object>.
But then the following sentence confuses me, in the sense that according to what I previously understood, List<Object> should only contain instances of the class Object and not something else.
It's important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can only insert null into a List<?>.
There are two separate issues here. A List<Object> can in fact take any object as you say. A List<Number> can take at least Number objects, or of course any subclasses, like Integer.
However a method like this:
public void print(List<Number> list);
will actually only take a List which is exactly List<Number>. It will not take any list which is declared List<Integer>.
So the difference is List<?> will take any List with whatever declaration, but List<Object> will only take something that was declared as List<Object>, nothing else.
The last quote simply states, that List<?> is a list for which you literally don't know what type its items are. Because of that, you can not add anything to it other than null.
The sentence that is confusing you is trying to warn you that, while List<?> is the super-type of all generic lists, you cannot add anything to a List<?> collection.
Suppose you tried the following code:
private static void addObjectToList1(final List<?> aList, final Object o ) {
aList.add(o);
}
private static void addObjectToList2(final List<Object> aList, final Object o ) {
aList.add(o);
}
private static <T> void addObjectToList3(final List<T> aList, final T o ) {
aList.add(o);
}
public static void main(String[] args) {
List<String> testList = new ArrayList<String>();
String s = "Add me!";
addObjectToList1(testList, s);
addObjectToList2(testList, s);
addObjectToList3(testList, s);
}
addObjectToList1 doesn't compile, because you cannot add anything except null to a List<?>. (That's what the sentence is trying to tell you.)
addObjectToList2 compiles, but the call to it in main() doesn't compile, because List<Object> is not a super type of List<String>.
addObjectToList3 both compiles and the call works. This is the way to add elements to a generic list.
I created list
private final List<Double> prices
and I have method:
public Double getPrice(int i)
{
i--;
if (i >= this.prices.size())
{
return Double.NEGATIVE_INFINITY;
}
return this.prices.get(i);
}
And looks good... but that always throws:
ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
After some tests I found:
I creating this list from config, but in config I using numbers without dots so code creating list with Integers not Doubles (ArrayList with Integers inside)
But why I can cast this list to List without any error?
Example:
Integer a = 1;
Integer b = 1;
Integer c = 1;
Integer d = 1;
List<?> listI = new ArrayList<>(Arrays.asList(a, b, c, d));
List<Double> listD = (List<Double>) listI;
for (Object o : listD)
{
System.out.println(o.getClass().getSimpleName()); // Integer
}
System.out.println("done");
This code works without any errors, but printing Integer from Double list, why that don't throw ClassCastException in List<Double> listD = (List<Double>) listI;?
So if you try add double num = listD.get(0); then throw ClassCastException
The generic type parameter that Java infers from the call to Arrays.asList is the common type of the arguments to asList -- Integer, so the new ArrayList is an ArrayList<Integer>, not an ArrayList<Double>.
Java won't throw a ClassCastException at List<Double> listD = (List<Double>) listI;, because at runtime, type erasure has already happened, and to the JVM, the code looks like this:
List listD = (List) listI;
That is perfectly fine to the JVM.
The ClassCastException comes later, from attempting to cast the Integer that is really coming from casting the list to a Double. With generics, the Java compiler will insert an implicit cast to the generic type parameter, in this case, Double, when an item is retrieved. However, it's really an Integer.
There should have been a warning, on the cast when you compiled this code, about an "unchecked cast" when casting the List<?> to a List<Double>. It warns you about the very possibility that you've demonstrated - a potential ClassCastException later.
why that don't throw ClassCastException in List<Double> listD = (List<Double>) listI;?
At run-time all the type information is lost. You have mixed raw type with parametrized type that's why you are getting into trouble at runtime.
Try this one
List<Integer> listI = new ArrayList<Integer>(Arrays.asList(a, b, c, d));
List<Double> listD = (List<Double>) listI; // compilation error
// Cannot cast from List<Integer> to List<Double>
Sample code :
// Here's a basic parameterized list.
List<Integer> li = new ArrayList<Integer>();
// It is legal to assign a parameterized type to a nonparameterized variable
List l = li;
// This line is a bug, but it compiles and runs.
// The Java 5.0 compiler will issue an unchecked warning about it.
// If it appeared as part of a legacy class compiled with Java 1.4, however,
// then we'd never even get the warning.
l.add("hello");
// This line compiles without warning but throws ClassCastException at runtime.
// Note that the failure can occur far away from the actual bug.
Integer i = li.get(0);
For more sample code have a look at Generic Types, Part 1 - O'Reilly Media
1)I wonder why generic methods and classes don't take super keyword with them although extends is accepted?
2)Due to type erasure we can't use instance of operator with generics but then why is typecasting allowed?
eg:
if (objecta instanceof Object){} //doesn't work
Collection collection = new ArrayList();
Collection<Integer> d1 = (Collection<Integer>)collection; //works fine.
As per my understanding it should not because we are trying to cast it to Collection and there is nothing as Collection at runtime.
3)I have read that static variables can't be a member of generics classes but I am not very clear why. Why the following behaviour is shown then?
public class NoGenericss
{ static List<Integer> list; //WORKS FINE
static List<T> list1; //COMPILATION ERROR
public class Genericss<T>
{ static List<Integer> list; //WORKS FINE
static List<T> list1; //COMPILATION ERROR
static void meth(T t){} //COMPILATION ERROR
static <S> void meth(S t){} //WORKS FINE
Why is there such a variable behaviour shown?
For some reference for Java generics see the Sun tutorial.
2)
The instanceof operator is dynamic, meaning it checks type of an object during the runtime. Because of type erasure, the generic type of an object (such as a collection) is not available at runtime (a List<Integer>, List<String>, List<URI> become all just Lists of some objects).
However, a cast is static. The type parameters of your variables are checked by the Java compiler. If you do this:
List<Integer> ints = Arrays.asList(1, 2, 3);
List<String> strings = (List<String>) ints;
... the compiler displays an unchecked warning, because this operation is possible, but not safe. Because now when you do:
String s = ints.get(0);
You get a ClassCastException at runtime, because the VM will try to cast an Integer to a String.
This happens because the compiler compiles the above source code with generics into an equivalent of the following code. The type erasure is implemented in such a way, so that all the generic parameters go away, making all collections only collections of Objects and therefore, casts are inserted in the code wherever necessary.
List ints = Arrays.asList(1, 2, 3);
List strings = ints;
String s = (String) ints.get(0);
3)
The <T> type parameter is visible only to member fields and member methods. Each instance of a generic class can have a different T, for example if you have a class:
class List<T> { // this is not java.util.List, just some custom class
public List<T> subList(int start, int end) { ... }
...
}
... then you can do:
List<String> strings = ...
List<String> stringsSub = strings.subList(1, 2);
... but also:
List<Integer> ints = ...
List<Integer> intsSub = ints.subList(1, 2);
As you can see, the first instance has the T set to String, the second to Integer. So the member method subList always returns a List of the same T.
Now, suppose you added a static field:
class List<T> {
public static List<T> CONSTANT_LIST = ...
...
}
... what would the T be in the case of the CONSTANT_LIST? String, Integer, something else? Well, it's impossible to define, because statics exist independently of any of the class instances. That's why the T is invisible to them.
On the other hand, there is no problem to add a static with a concrete generic type:
class List<T> {
public static List<Integer> CONSTANT_LIST = new List(1, 2, 3);
...
}
I think the title should explain it all but just in case...
I want to know what risks and potential issues relating to casting can arise from the following snippet of Java code:
List<? extends MyObject> wildcardList = someAPI.getList();
List<MyObject> typedList = (List<MyObject>) wildcardList;
My thoughts are that all objects in the wildcardList should be an instance of MyObject (exact type or subclass) and so whenever objects are retrieved from typedList then there should never be a ClassCastException. Is this correct? If so why does the compiler generate a warning?
There should be no problem as long as just you retrieve objects from the list. But it could result in runtime exception if you invoke some other methods on it like the following code demonstrate:
List<Integer> intList = new ArrayList<Integer>();
intList.add(2);
List<? extends Number> numList = intList;
List<Number> strictNumList = (List<Number>) numList;
strictNumList.add(3.5f);
int num = intList.get(1); //java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer
You are correct about retrieving objects from typedList, this should work.
The problem is when you later add more objects to typedList. If, for instance, MyObject has two subclasses A and B, and wildcardList was of type A, then you can't add a B to it. But since typedList is of the parent type MyObject, this error will only be caught at runtime.
Consider you do something like:
List<ChildClassOne> childClassList = new ArrayList<ChildClassOne>();
childClassList.add(childClassOneInstanceOne);
childClassList.add(childClassOneInstanceTwo);
List<? extends MyObject> wildcardList = childClasslist; // works fine - imagine that you get this from a method that only returns List<ChildClassOne>
List<MyObject> typedList = (List<MyObject>) wildcardList; // warning
typedList.add(childClassTwoInstanceOne); // oops my childClassList now contains a childClassTwo instance
ChildClassOne a = childClassList.get(2); // ClassCastException - can't cast ChildClassTwo to ChildClassOne
This is the only major problem. But if you only read from your list it should be ok.
This is similar to
List<Object> obj = (List<Object>) new ArrayList<String>();
I hope the problem is evident. List of subtypes can't be cast to Lists of supertypes.
In addition to the answers already posted, take a look at the following
what-is-the-meaning-of-the-type-safety-warning-in-certain-java-generics-casts
confused-by-java-generics-requiring-a-cast
The compiler generates a warning incase an element from the type is accessed later in your code and that is not the generic type defined previously. Also the type information at runtime is not available.