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
Related
I am trying to create a generic method that returns a generic list with a given data type.
I have passed Integer as data type and I filled with String with no issue at runtime.
My question why I didn't receive ClassCastException during the Runtime?
public static void main(String[] args) {
List<Integer> list = (List<Integer>) generic(Integer.class);
System.out.println(list);
}
private static <T> List<?> generic(T clazz) {
List<T> list = new ArrayList<>();
list.add((T) Arrays.asList("create", "generic"));
list.add((T) Arrays.asList("create", "generic2"));
return list;
}
, output
[[create, generic], [create, generic2]]
Due to type erasure, not every violation of the generic type system is identified at runtime. This situation is also called heap pollution. But you should have received “unchecked” warnings from the compiler.
But your assumption that you create a List<Integer> is wrong anyway. You are passing Integer.class as argument for the T clazz parameter, hence, T is not Integer but Class<Integer>.
A correct generic declaration would be:
public static void main(String[] args) {
List<Integer> list = generic(Integer.class);
System.out.println(list);
}
private static <T> List<T> generic(Class<T> clazz) {
List<T> list = new ArrayList<>();
return list;
}
Now that the program is free of “unchecked” warnings, you can be sure that no heap pollution will occur at runtime. If you want more guarantees, you can use
public static void main(String[] args) {
List<Integer> list = generic(Integer.class);
System.out.println(list);
}
private static <T> List<T> generic(Class<T> clazz) {
List<T> list = Collections.checkedList(new ArrayList<>(), clazz);
list.add((T) Arrays.asList("create", "generic")); // will fail at runtime
list.add((T) Arrays.asList("create", "generic2"));
return list;
}
However, when you listen and respond to the compiler warnings, you don’t need the additional runtime checks.
As you have declared T as <T> the runtime type-checking of (T) is against Object. Therefore, the cast from a List is fine.
List is declared as List<E>, so there can be no additional internal runtime type-checking from List.add. Likewise for List.toString.
You will, however, get warnings (so long as the lint option is enabled in your compiler). Don't ignore the warnings.
You can use clazz.cast in place of T, though T cannot usefully be a parameterised type.
Generics provide type checks at compile time and are erased by the compiler during the Type Erasure. So after the type erasure your method will look something like this:
private static List generic(Object clazz) {
List list = new ArrayList();
list.add((Object) Arrays.asList("create", "generic"));
list.add((Object) Arrays.asList("create", "generic2"));
return list;
}
And your main method will look as follows:
List list = (List) generic(Integer.class);
System.out.println(list);
So there's no reason for a ClassCastException to be thrown, because you are not trying to do any Integer specific actions to the elements of your list. But you will get it as soon as you do, for example:
list.forEach(integer -> System.out.println(integer.intValue()));
because here it will try to cast ArrayList to Integer to call intValue().
Conclusion: using generics along with unchecked casting is quite useless.
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:
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
Consider the following test of Java's ArrayList#toArray method. Note that I borrowed the code from this helpful answer.
public class GenericTest {
public static void main(String [] args) {
ArrayList<Integer> foo = new ArrayList<Integer>();
foo.add(1);
foo.add(2);
foo.add(3);
foo.add(4);
foo.add(5);
Integer[] bar = foo.toArray(new Integer[10]);
System.out.println("bar.length: " + bar.length);
for(Integer b : bar) { System.out.println(b); }
String[] baz = foo.toArray(new String[10]); // ArrayStoreException
System.out.println("baz.length: " + baz.length);
}
}
But, notice that there will be a ArrayStoreException when trying to put an Integer into a String[].
output:
$>javac GenericTest.java && java -cp . GenericTest
bar.length: 10
1
2
3
4
5
null
null
null
null
null
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.ArrayList.toArray(Unknown Source)
at GenericTest.main(GenericTest.java:16)
Can this error be prevented through Java generics at compile-time?
ArrayStoreException exists precisely because Java's type system cannot handle this situation properly (IIRC, by the time Generics came along, it was too late to retrofit arrays in the same manner as the collections framework).
So you can't prevent this problem in general at compile time.
You can of course create internal APIs to wrap such operations, to reduce the likelihood of accidentally getting the types wrong.
See also:
Dealing with an ArrayStoreException
Why are arrays covariant but generics are invariant?
List#toArray(T[]) is a generic method declared as
<T> T[] toArray(T[] a);
So the type argument is either inferred from the type of the given array or with the <Type> notation prefixing the method invocation.
So you could do
String[] baz = foo.<Integer>toArray(new String[10]); // doesn't compile
But I think that's the best you could do.
But in that sense, you can clearly see that Integer doesn't match String (or vice-versa).
Note that this is a documented exception
ArrayStoreException - if the runtime type of the specified array is
not a supertype of the runtime type of every element in this list
So I don't think you should be trying to find it at compile time.
The method Collection.toArray cannot be changed for compatibility reasons.
However for your own code you can create a (more) type-safe helper method which protects you from the ArrayStoreException if you use your method consequently:
public static <T> T[] toArray(List<? extends T> list, T[] t) {
return list.toArray(t);
}
This method will reject String[] s=toArray(new ArrayList<Integer>(), new String[0]); which matches the example case of your question, but beware of the array subtyping rule: it will not reject
Object[] s=toArray(new ArrayList<Integer>(), new String[0]);
because of the pre-Generics “String[] is a subclass of Object[]” rule. This can’t be solved with the existing Java language.
The ArrayStoreException is runtime exception not compile time and thrown at run time and it indicates that different type of object is being stored in the array.
Object x[] = new String[3];
x[0] = new Integer(0);
The only way you can find it at compile time is by using <Integer> type as below
foo.<Integer>toArray(new String[10]);
The above will throw compile time error as
The parameterized method <Integer>toArray(Integer[]) of type List<Integer> is not applicable for the arguments (String[]).
I don't think I really understand Java generics. What's the difference between these two methods? And why does the second not compile, with the error shown below.
Thanks
static List<Integer> add2 (List<Integer> lst) throws Exception {
List<Integer> res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
.
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
required: T
found: capture#1 of ? extends java.util.List
For the second method to compile, you have to cast the result of newInstace() to T:
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = (T) lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Regarding the difference between the two methods, let's forget about the implementation, and consider only the signature.
After the code is compiled, both methods will have exactly the same signature (so the compiler would give an error if the have the same name). This happens because of what is called type erasure.
In Java, all the type parameters disappear after compilation. They are replaced by the most generic possible raw type. In this case, both methods will be compiled as List add2(List).
Now, this will show the difference between the two methods:
class Main {
static <T extends List<Integer>> T add1(T lst) { ... }
static List<Integer> add2(List<Integer> lst) { ... }
public static void main(String[] args) {
ArrayList<Integer> l = new ArrayList<Integer>();
ArrayList<Integer> l1 = add1(l);
ArrayList<Integer> l2 = add2(l); // ERROR!
}
}
The line marked as // ERROR! won't compile.
In the first method, add1, the compiler knows that it can assign the result to a variable of type ArrayList<Integer>, because the signature states that the return type of the method is exactly the same as that of the parameter. Since the parameter is of type ArrayList<Integer>, the compiler will infer T to be ArrayList<Integer>, which will allow you to assign the result to an ArrayList<Integer>.
In the second method, all the compiler knows is that it will return an instance of List<Integer>. It cannot be sure that it will be an ArrayList<Integer>, so you have to make an explicit cast, ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);. Note that this won't solve the problem: you are simply telling the compiler to stop whining and compile the code. You will still get an warning (unchecked cast), which can be silenced by annotating the method with #SuppressWarnings("unchecked"). Now the compiler will be quiet, but you might still get a ClassCastException at runtime!
The first one is specified to accept a List<Integer> and return a List<Integer>. List being an interface, the implication is that an instance of some concrete class that implements List is being passed as a parameter and an instance of some other concrete class that implements List is returned as a result, without any further relationship between these two classes other than that they both implement List.
The second one tightens that up: it is specified to accept some class that implements List<Integer> as a parameter, and return an instance of exactly that same class or a descendant class as the result.
So for example you could call the second one like so:
ArrayList list; // initialization etc not shown
ArrayList result = x.add2(list);
but not the first, unless you added a typecast.
What use that is is another question. ;-)
#Bruno Reis has explained the compile error.
And why does the second not compile, with the error shown below.
The error shown is actually reporting that you have tried to run code that failed to compile. It is a better idea to configure your IDE to not run code with compilation errors. Or if you insist on letting that happen, at least report the actual compilation error together with the line number, etc.
"I don't think I really understand Java generics."
Nobody does...
The issue is related to the interesting return type of getClass(). See its javadoc. And this recent thread.
In both of your examples, lst.getClass() returns Class<? extends List>, consequently, newInstance() returns ? extends List - or more formally, a new type parameter W introduced by javac where W extends List
In your first example, we need to assign W to List<Integer>. This is allowed by assignment conversion. First, W can be converted to List because List is a super type of W. Then since List is raw type, the optional unchecked conversion is allowed, which converts List to List<Integer>, with a mandatory compiler warning.
In the 2nd example, we need to assign W to T. We are out of luck here, there's no path to convert from W to T. It makes sense because as far as javac knows at this point, W and T could be two unrelated subclass of List.
Of course, we know W is T, the assignment would have been safe if allowed. The root problem here, is that getClass() loses type information. If x.getClass() returns Class<? extends X> without erasure, both of your examples will compile without even warning. They indeed are type safe.
Generics are a way to guarantee type safety.
Eg:
int[] arr = new int[4];
arr[0] = 4; //ok
arr[1] = 5; //ok
arr[2] = 9; //ok
arr[3] = "Hello world"; // you will get an exception saying incompatible
types.
By default arrays in Java are typeSafe. An integer array is only meant to
contain integer and nothing else.
Now:
ArrayList arr2 =new ArrayList();
arr2.add(4); //ok
arr2.add(5); //ok
arr2.(9); //ok
int a = arr2.get(0);
int b = arr2.get(1);
int c = arr3.get(2);
You willa gain get an exception like what it is not possible to cast Object
instance to integer.
The reason is that ArrayList stores object and not primitive like the
above array.
The correct way would be to explicitly cast to an integer.You have to do this
because type safety is not yet guaranteed.
eg:
int a = (int)arr2.get(0);
To employ type safety for collections, you simply specify the type of objects that your collection contains.
eg:
ArrayList<Integer> a = new ArrayList<Integer>();
After insertion into the data structure, you can simply retrieve it like you
would do with an array.
eg:
int a = arr2.get(0);