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.
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
Below is the code,
import java.util.List;
import java.util.ArrayList;
public class Dummy {
public static void main(String[] args) {
List<String> lst = new ArrayList<String>();
lst.add("a string");
lst.add("another string");
String s = lst.get(0);
} //end main
}
when the constructor new ArrayList<String>(); is invoked, array of type Object is created.
..
lst holds Object[0] array.
So, If array of type Object gets created by constructor, How does javac does not see type casting issue in this statement String s = lst.get(0);, despite generic syntax is used while invoking constructor?
Here is some non-generic code:
public class MyList {
List myData = new ArrayList();
public void add(Object ob) {
myData.add(ob);
}
public Object getAtIndex(int ix) {
return myData.get(ix);
}
}
This code will allow you to store an object of any type into your MyList instances even if the contract of MyList specifies that the objects must all be of the same type. Moreover if you use a MyList instance to store String instances, you must manually cast them to String when you retrieve them.
String myString = (String) myList.get(1);
The above MyList class is not type safe. It is most definitely possible that the above assignment statement will fail with a ClassCastException if an object other than a String instance was stored into your MyList instance (which can happen at run-time without any complaint).
Here is a generified MyList class:
public class MyList<T> {
List<T> myData = new ArrayList<>();
public void add(T ob) {
myData.add(ob);
}
public T getAtIndex(int ix) {
return myData.get(ix);
}
}
Now, the compiler guarantees that only T instances can be added and retrieved from MyList instances. Because the compiler guarantees that T instances will always be returned, you can use syntax like this without any manual casting:
String myString = myList.get(1);
The generified MyList class is now type-safe. The compiler won't allow you to store anything but T instances into your MyList instances, which guarantees that no ClassCastExceptions will occur at run-time. If you examine the byte-code, you'll find that the compiler has placed a cast automatically.
Generics in Java are a compile-time only phenomenon. In the byte-code, all references to T in the MyList class above are replaced by Object. This process is referred to as "type erasure". It is important to remember that type safety in Java is provided by the compiler only. If your program compiles without any errors AND without any warnings, then your program, generics and all, is type safe. The compiled program, however, has retained almost no information about the generic parameters.
From farther down in the class:
#SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
That #SuppressWarnings tells the compiler that you're sure about the unchecked cast that you're performing there. Since all of the other operations, such as get(int), use the type parameter E, this restricts the unsafe type handling to specific locations where the implementer can make sure to handle the cast correctly.
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
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);
Here is my code:
Object[] data = GeneComparison.readData(files);
MyGenome genome = (MyGenome) data[0];
LinkedList<Species> breeds = (LinkedList<Species>) data[1];
It gives this warning for the LinkedList:
Type safety: Unchecked cast from Object to LinkedList<Species>
Why does it complain about the linked list and not MyGenome?
Java complains like that when you cast a non-parameterized type (Object) to a parameterized type (LinkedList). It's to tell you that it could be anything. It's really no different to the first cast except the first will generate a ClassCastException if it is not that type but the second won't.
It all comes down to type erasure. A LinkedList at runtime is really just a LinkedList. You can put anything in it and it won't generate a ClassCastException like the first example.
Often to get rid of this warning you have to do something like:
#SuppressWarning("unchecked")
public List<Something> getAll() {
return getSqlMapClient.queryForList("queryname");
}
where queryForList() returns a List (non-parameterized) where you know the contents will be of class Something.
The other aspect to this is that arrays in Java are covariant, meaning they retain runtime type information. For example:
Integer ints[] = new Integer[10];
Object objs[] = ints;
objs[3] = "hello";
will throw a exception. But:
List<Integer> ints = new ArrayList<Integer>(10);
List<Object> objs = (List<Object>)ints;
objs.add("hello");
is perfectly legal.
Because here:
MyGenome genome = (MyGenome) data[0];
You are not using generics
And here
LinkedList<Species> breeds = (LinkedList<Species>) data[1];
You are using them.
That's just a warning, you are mixing types in the data array. If you know what are you doing ( I mean, if the second element do contains a LinkedList ) you can ignore the warning.
But better would be to have an object like this:
class Anything {
private Object [] data;
public Anything( Object [] data ) {
this.data = data;
}
public Gnome getGnome() {
.....
}
public List<Species> getBreeds() {
......
}
}
And have to methods returning proper things, prior to a correct conversion so you end up with:
Anything anything = new Anything( GeneComparison.readData(files) );
MyGenome genome = anything.getGnome(); // similar to data[0]
LinkedList<Species> breeds = anything.getBreeds(); // similar to data[1];
Inside those methods you have to do proper transformations.