This question already has answers here:
Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
(19 answers)
Closed 8 years ago.
Consider below method doSomething(List<Object>) which accepts List<Object> as parameter.
private void doSomething(List<Object> list) {
// do something
}
Now consider below code snippet which tries to call doSomething() where I try to pass List<String> to doSomething()
List<Object> objectList;
List<String> stringList;
doSomething(stringList); // compilation error incompatible types
doSomething(objectList); // works fine
Even below code throws compilation error
objectList = stringList; // compilation error incompatible types
My question is why List<String> can not be passed to a method which accepts List<Object>?
Because while String extends Object, List<String> does not extend List<Object>
Update:
In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>.
This is because collections do change. In your case, If List<String> was a subtype of List<Object>, then types other than String can be added to it when the list is referenced using its supertype, as follows:
List<String> stringList = new ArrayList<String>;
List<Object> objectList = stringList;// this does compile only if List<String> where subtypes of List<Object>
objectList.add(new Object());
String s = stringList.get(0);// attempt to assign an Object to a String :O
and the Java compiler has to prevent these cases.
More elaboration on this Java Tutorial page.
You could put an object of a wrong type into the list IF this worked:
private void doSomething(List<Object> list) {
list.add(new Integer(123)); // Should be fine, it's an object
}
List<String> stringList = new ArrayList<String>();
doSomething(stringList); // If this worked....
String s = stringList.get(0); // ... you'd receive a ClassCastException here
This generic question in Java may look confusing to any one who is not very familiar with Generics as in first glance it looks like String is object so List<String> can be used where List<Object> is required but this is not true. It will result in compilation error.
It does make sense if you go one step further because List<Object> can store anything including String, Integer etc but List<String> can only store Strings.
Also have a look at: Why not inherit from List<T>?
The reason for these limitations have to do with variance considerations.
Take the following code:
public void doSomething(List<Object> objects)
{
objects.add(new Object());
}
Expanding your example, you could try to do the following:
List<String> strings = new ArrayList<String>();
string.add("S1");
doSomething(strings);
for (String s : strings)
{
System.out.println(s.length);
}
Hopefully it's obvious why this would break if the compiler allowed this code to be compiled (which it doesn't) - a ClassCastException would occur for the second item in the list when trying to cast the Object to a String.
To be able to pass generalized collection types, you need to do this:
public void doSomething(List<?> objects)
{
for (Object obj : objects)
{
System.out.println(obj.toString);
}
}
Again, the compiler is watching your back and were you to replace the System.out with objects.add(new Object()) the compiler wouldn't allow this because objects could have been created as List<String>.
For more background on Variance see the Wikipedia artical Covariance and contravariance
From Java Tutorials of Generics:
Let's test your understanding of generics. Is the following code snippet legal?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
Source : Generics and Subtyping
It is sometimes expected that a List<Object> would be a supertype of a List<String> , because Object is a supertype of String .
This expectation stems from the fact that such a type relationship exists for arrays:
Object[] is a supertype of String[] , because Object is a supertype of String . (This type relationship is known as covariance .)
The super-subtype-relationship of the component types extends into the corresponding array types.
No such a type relationship exists for instantiations of generic types. (Parameterized types are not covariant.)
Check here for more details
If you are not sure what datatype it will take in you can make use of Generics in Java as follows
public static void doSomething(List<?> data) {
}
public static void main(String [] args) {
List<Object> objectList = new ArrayList<Object>();
List<String> stringList = new ArrayList<String>();
doSomething(objectList);
doSomething(stringList);
}
But while using the data, you will be required to specify proper data type as a Type Cast
Related
I have method as below
void meth(List<?> list) {
List<Integer> integers = (List<Integer>)list; //how do I make sure am casting correctly to List < Integer >? what if list passed as List< String > , then this will throw some RunTime Exceptions, how to avoid this?
}
In above snippet, for meth(), am not sure which type of Arraylist will be passed, it could be List or List etc, based on type of list type, I have to assign it to another list correctly, how can I achieve this?
Basically ... you can't. Since you could call meth (as you have written it) with a List<String> parameter, there can always be runtime exceptions.
Solutions:
Declare meth as public void meth(List<Integer> list) so that you can't call it like this meth(someStringList). That avoids the unchecked type cast and eliminates the possibility of a class cast exception.
Use list like this in meth.
void meth(List<?> list) {
for (Object o: list) {
Integer i = (Integer) o;
// ...
}
}
We can still get the class cast exception, but at least we get rid of the compiler error about unchecked type casts.
Use a #SuppressWarning annotation to suppress the compiler warning about the unchecked type cast. Once again, you could get the exceptions.
Unfortunately, given Java's generic type parameter erasure, there is no way that the body of meth (as written) can find out what kind of list it has been called with at runtime. And it won't work with a named type parameter either.
I verified that it does not throw exception. With type erasure, all generics are convert to Object. Generics are for compiler to enforce type during compile time.
static List<Integer> meth(List<?> list){
return (List<Integer>) list;
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("world");
strings.add("hello");
List<Integer> integers = meth(strings);
System.out.println(integers);
}
Console:
[world, hello]
You can try the code here: https://onlinegdb.com/z7DmGAJUI
class A {
private int a;
}
public static <T> List<T> listStrToListT(String str) {
String[] idStrs = str.replace(" ", "").split(",");
List<T> uids = new ArrayList<>();
for (String idStr : idStrs) {
uids.add((T) idStr);
}
return uids;
}
public static void main(String[] args) {
List<A> lst = listStrToListT("1,2,3");
System.err.println(lst);
}
This program don't have any error.But when I debug (in the below picture): lst is a List<String>.Why I directly assign List<String>(right side) to List<A>(left side) ?
Remember that generics in Java are only a compile-time thing. At runtime, all generic parameters are erased to non-generic types.
From the compiler's point of view, listStrToListT can return any kind of List the caller wants, not just List<String>. You convinced the compiler of this non-fact by (1) making listStrToListT generic and (2) casting idStr to T. You're saying "I'm sure this cast will work when this runs. Don't worry, Compiler!" This cast certainly smells fishy, doesn't it? What if T is A...
Anyway, now List<A> lst = listStrToListT("1,2,3"); compiles, as listStrToListT "can return any type of List" as mentioned before. You'd imagine that T is inferred to be A, and your cast in listStrToListT would fail at runtime, but that's not what happens.
Now it's runtime, all generic types get erased, making your code look like this:
public static List listStrToListT(String str) {
String[] idStrs = str.replace(" ", "").split(",");
List uids = new ArrayList();
for (String idStr : idStrs) {
uids.add((Object)idStr);
}
return uids;
}
// main method:
List lst = listStrToListT("1,2,3");
System.out.println(lst);
Note that the cast to T becomes a cast to Object, which really is just redundant here.
Printing out the list just involves calling toString on each of the Objects, so no casting is done there.
Note that what "smelled fishy" at compile time, is completely valid at compile time. The fishy cast became a perfectly valid (and redundant) cast to Object! Where'd the cast go?
Casts will only be inserted where necessary. This is just how generics work in Java. So let's create such a situation. Let's say in A you have a getter for the field a, and instead of printing the whole list, you print the a of the first element:
// main method:
List<A> lst = listStrToListT("1,2,3");
System.out.println(lst.get(0).getA());
Well, to be able to access getA, a cast needs to be inserted:
List lst = listStrToListT("1,2,3");
System.out.println(((A)lst.get(0)).getA());
otherwise lst.get(0) would be of type Object, and Objects don't have a getA method.
It is at this time that your program will crash.
This question already has answers here:
Java generics type erasure: when and what happens?
(7 answers)
Closed 6 years ago.
There two programs
Why is the first code code working?I expected it to throw a Run time Exception while accessing the elements as String is added instead of Integer
Similarly..
The second code is throwing Run time Exception while accessing the element though it is able to add Integer in the arrayList comfortably despite declaring it to hold String.
In both the codes,We are successful in adding the different Data types,but the problems seems to appear while accessing elements
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
Test.addToList(arrayList);
System.out.println(arrayList.get(0));
}
public static void addToList(ArrayList arrayList) {
arrayList.add("i");
}
}
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
Test.addToList(arrayList);
System.out.println(arrayList.get(0));
}
public static void addToList(ArrayList arrayList) {
arrayList.add(1);
}
}
You can add elements in both cases due to type erasure. At run time, the class doesn't know it was declared as new ArrayList<String>(), just that it was declared as new ArrayList().
In the println case, the method's compile-time overload resolution comes into play. System.out is a PrintStream, which has several overloads for println. Among these are:
...
void println(Object x);
void println(String x);
The Java compiler will pick whichever of those is most specific. In the ArrayList<Integer> case it's the first one, and in the ArrayList<String> case it's the second. Once it does that, as part of the type erasure handling, it will cast the Object result of the raw ArrayList::get(int) call to the required type, but only if that cast is necessary.
In the case of the ArrayList<Integer> call, the cast is not necessary (ArrayList::get(int) returns an Object, which is exactly what the method expects), so javac omits it. In the case of the ArrayList<String> call, it is necessary, so javac adds it. What you see as:
System.out.println(arrayList.get(0));
is actually compiled to:
System.out.println((String) arrayList.get(0));
But the element isn't a String, and that's what results in the ClassCastException.
Generics only exist during compilation. They are removed from the binarycode after compiling, this is called 'type erasure'. It is done mostly for backwards compatibility reasons.
If you compile without warnings and without manual casting, misuse of the generics is not possible, as it will lead to compiler errors and some warnings.
When you state you expect an ArrayList for your function, without any indication of generics. You disable all the compiletime checks. You should have gotten a warning for this. Any place where a generic parameter is used in that case, will just accept Object.
However when you access the objects using get(), something hiddens happens, a cast.
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);
The last line is rewritten to:
String s = (String) list.get(0);
This fails if the list does not return something of type String.
When you only use generics on the list, it will only have Strings (or your code wouldn't compile). But as you have allowed a non-String object into the list, the type check of a cast will fail.
While I was going through some generics question I came across this example. Will you please explain why list.add("foo") and list = new ArrayList<Object>() contain compailation issues?
In my understanding List of ? extends String means "List of Something which extends String", but String is final ? can only be String. In list.add() we are adding "foo" which is a string. Then why this compilation issue?
public class Generics {
public static void main(String[] args) {
}
public static void takelist(List<? extends String> list){
list.add("foo"); //-- > error
/*
* The method add(capture#1-of ? extends String) in the
* type List<capture#1-of ? extends String> is not applicable
* for the arguments (String)
*/
list = new ArrayList<Object>();
/*
* Type mismatch: cannot convert from ArrayList<Object> to List<? extends String>
*/
list = new ArrayList<String>();
Object o = list;
}
}
For starters, the java.lang.String class is final, meaning nothing can extend it. So there is no class which could satisfy the generic requirement ? extends String.
I believe this problem will cause all of the compiler errors/warnings which you are seeing.
list.add("foo"); // String "foo" does not extend String
list = new ArrayList<Object>(); // list cannot hold Object which does not extend String
It is true what you say. String is final. And so you can reason that List<? extends String> can only be list of string.
But the compiler isn't going to make that kind of analysis. That is to say, the compiler will not assess the final or non-final nature of String (see comments). The compiler will only let you put null into your list.
You can pull stuff out though.
String s = list.get(0);
While String is final, this information is not used.
And in fact, with Java 9, it may no longer be final (rumor has it that Java 9 may finally get different more efficient String types).
Without knowing it is final, List<? extends String> could be e.g. a List<EmptyString> of strings that must be empty.
void appendTo(List<? extends String> l) {
l.append("nonempty");
}
appendTo(new ArrayList<EmptyStrings>());
would yield a violation of the generic type.
As a rule of thumb always use:
? extends Type for input collections (get is safe)
? super Type for output collections (put is safe)
Type (or maybe a <T>) for input and output collections (get and put are safe, but the least permissive).
I.e. this is fine:
void appendTo(List<? super String> l) {
l.append("nonempty");
}
appendTo(new ArrayList<Object>());
You have already mentioned that String is a final type and therefore there is no point in repeating this fact. The point that is important to note is that none of the following Lists allows adding an element:
List<?> which is a List of anything.
List<? extends SomeType> which is a List of anything that extends SomeType.
Let's understand it with an example.
The List<? extends Number> could be List<Number> or List<Integer> or List<Double> etc. or even a List of some other type that hasn't been defined yet. Since you can not add any type of Number to a List<Integer> or any type of Number to a List<Double> etc., Java does not allow it.
Just for the sake of completeness, let's talk about List<? super Integer> which is List of anything that is a super/parent type of Integer. Will the following compile?
Object obj = 10.5;
list.add(obj);
As you can guess, of course NOT.
What about the following?
Object obj = 10.5;
list.add((Integer) obj);
Again, as you can guess, indeed it will compile but it will throw ClassCastException at runtime. The question is: why did not Java stop us in the first place by failing the compilation itself? The answer is Trust. When you cast something, the compiler trusts that you already understand the cast.
So, the following compiles and runs successfully:
Object obj = 10;
list.add((Integer) obj);
list.add(20);
I recently finished an online course in AP Computer Science (Java) and on the final exam there was a question that went something like this:
Which of these needs a String cast to use String methods on it:
I. ArrayList a = new ArrayList();
II. ArrayList<Object> b = new ArrayList<Object>();
III. ArrayList<String> c = new ArrayList<String>();
Something about this confused me: can option I ever even be able to be casted? It has no generic definition so, unless the Java compiler defauted to ArrayList<Object>, what class is E then?
This is my test code (the suppress comments are needed because this is an "unchecked" operation):
ArrayList a = new ArrayList();
#SuppressWarnings("unchecked")
a.add(new Object());
#SuppressWarnings("unchecked")
a.add(new String("test"));
#SuppressWarnings("unchecked")
a.add(null);
System.out.println((String)(a.get(0)));
No matter what is in the arguments for the add() method, it always gives the compiler error:
test.java:14: error: <identifier> expected
a.add(new Object());
^
If I try to add an identifier anywhere on the code (e.g.: a<Object>.add(new Object())) it gives the exact same error as before.
The question is what is actually happening when no parameter is passed to the generics parameter and can anything be added to this list in the first place, let alone cast into another object? Thanks in advance!
You have a simple syntax error. #SuppressWarnings can't be used on aribrary statements like a.add(x), only class declarations, method declarations and variable declarations. That's why it asks for an identifier, the #SuppressWarnings is expecting to see one of those three, not a simple statement.
public static void main(String[] args) {
ArrayList a = new ArrayList();
a.add(new Object());
a.add(new String("test" );
a.add(null);
System.out.println((String) (a.get(0)));
}
The code compiles fine like this.
So what happens when you use a raw type? Nothing. No type checking is done. The above code produces a java.lang.ClassCastException error on the println statement. It doesn't catch that you put an Object in when you meant to put a String, and it doesn't know until runtime that your cast on the println will be bad.
When it is not specified, it defaults to the bound of the generic, and if that is not specified it is by default object.
This means that :
List lst = new ArrayList();
Is totally equivalent to :
List<Object> lst = new ArrayList<Object>();
On such a list, everything can be added or retrieved, because everything in Java extends Object, except primitives (but then autoboxing comes into play).
So, you can correctly write :
lst.add("String");
lst.add(new Date());
lst.add(new Integer(1));
lst.add(1); // It is converted to an Integer by auto-boxing
And you can correctly write :
String str = (String)lst.get(0);
But you have to be aware of possible runtime cast exceptions :
String str2 = (String)lst.get(1); // Will give error, cause element 1 is a date.
If you have a specific ArrayList subclass that defines a generic bound, like for example :
public class MyList<E extends MyClass> extends ArrayList<E> {
The you can still use it without generics :
MyList lst = new MyList();
But it will accept only MyClass or subclasses of MyClass :
lst.add(new MyClass());
lst.add(new MySubClass());
MyClass a = lst.get(0);
MyClass b = lst.get(1);
MySubClass c = (MySubClass)lst.get(1);
But nothing else :
lst.add("String"); // Will give compile time error