I am reading on Generics in Java atm, and things go a little slow would love some help. I read from Oracles own database:
https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html
At the bottom we can see List<Integer> is a subtype of List<? extends Number>
I also came across this stackoverflow question:
Java Generic type : difference between List <? extends Number> and List <T extends Number>
Which says in one answer: this is true:
((List<Integer>)list).add((int) s);
I have verified it, so that's ok. But I don't understand It completely.
What if the Wildcard is the Short class and I add a number higher than 2^15-1(=32767) Shouldn't it give error?
I even tried something like this and it works fine:
import java.util.*;
class CastingWildcard{
public static void main(String[] args){
List<? extends Number> list = new ArrayList<Short>();
int s=32770;
((List<Integer>)list).add((int) s);
System.out.println(list.get(0));
}
}
To sum up: Why Can I cast List<? extends Number> to List<Integer> when the wildcard could be Short, and even Byte, which also extends Number?
The cast makes the compiler ignore the fact, that the types may not be assignable.
At runtime the type parameters are unimportant, see type erasure.
The ArrayList internally stores the content in a Object[] array, which means you can add any reference type to the list object, if you "abuse" casting.
You may get a exception when you retrieve a Object though, since there's a cast hidden in the get statement.
Example:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
List<String> list2 = (List) list;
list2.add("Hello World");
Integer i = list.get(0); // works
String s = list2.get(3); // works
s = list2.get(1); // ClassCastException
i = list.get(3); // ClassCastException
You can cast an object to anything you want, but it might fail at runtime. However since generics information isn't present during runtime, your code becomes essentially ((List)list).add(s);. At that point list will take any object, not just a Number. Generics can help you avoid casts and keep type safety during compile time, but during runtime they don't matter anymore.
Related
I have a class:
class Generic<T> {
List<List<T>> getList() {
return null;
}
}
When I declare a Generic with wildcard and call getList method, the following assignment is illegal.
Generic<? extends Number> tt = null;
List<List<? extends Number>> list = tt.getList(); // this line gives compile error
This seems odd to me because according to the declaration of Generic, it's natural to create a Generic<T> and get a List<List<T>> when call getList.
In fact, it require me to write assignment like this:
List<? extends List<? extends Number>> list = tt.getList(); // this one is correct
I want to know why the first one is illegal and why the second one is legal.
The example I give is just some sample code to illustrate the problem, you don't have to care about their meaning.
The error message:
Incompatable types:
required : List<java.util.List<? extends java.lang.Number>>
found: List<java.util.List<capture<? extends java.lang.Number>>>
This is a tricky but interesting thing about wildcard types that you have run into! It is tricky but really logical when you understand it.
The error has to do with the fact that the wildcard ? extends Number does not refer to one single concrete type, but to some unknown type. Thus two occurrences of ? extend Number don't necessarily refer to the same type, so the compiler can't allow the assignment.
Detailed explanation
The right-hand-side in the assignment, tt.getList(), does not get the type List<List<? extends Number>>. Instead each use of it is assigned by the compiler a unique generated capture type, for exampled called List<List<capture#1 extends Number>>.
The capture type List<capture#1 extends Number> is a subtype of List<? extends Number>, but it is not type same type! (This is to avoid mixing different unknown types together.)
The type of the left-hand-side in the assignment is List<List<? extends Number>>. This type does not allow subtypes of List<? extends Number> to be the element type of the outer list, thus the return type of getList can't be used as the element type.
The type List<? extends List<? extends Number>> on the other hand does allow subtypes of List<? extends Number> as the element type of the outer list. So that is the right fix for the problem.
Motivation
The following example code demonstrates why the assignment is illegal. Through a sequence of steps we end up with a List<Integer> which actually contains Floats!
class Generic<T> {
private List<List<T>> list = new ArrayList<>();
public List<List<T>> getList() {
return list;
}
}
// Start with a concrete type, which will get corrupted later on
Generic<Integer> genInt = new Generic<>();
// Add a List<Integer> to genInt.list. This is not necessary for the
// main example but migh make things a little clearer.
List<Integer> ints = List.of(1);
genInt.getList().add(ints);
// Assign to a wildcard type as in the question
Generic<? extends Number> genWild = genInt;
// The illegal assignment. This doesn't compile normally, but we force it
// using an unchecked cast to see what would happen IF it did compile.
List<List<? extends Number>> list =
(List<List<? extends Number>>) (Object) genWild.getList();
// This is the crucial step:
// It is legal to add a List<Float> to List<List<? extends Number>>.
// list refers to genInt.list, which has type List<List<Integer>>.
// Heap pollution occurs!
List<Float> floats = List.of(1.0f);
list.add(floats);
// notInts in reality is the same list as floats!
List<Integer> notInts = genInt.getList().get(1);
// This statement reads a Float from a List<Integer>. A ClassCastException
// is thrown. The compiler must not allow us to end up here without any
// previous type errors or unchecked cast warnings.
Integer i = notInts.get(0);
The fix that you discovered was to use the following type for list:
List<? extends List<? extends Number>> list = tt.getList();
This new type shifts the type error from the assignment of list to the call to list.add(...).
The above illustrates the whole point of wildcard types: To keep track of where it is safe to read and write values without mixing up types and getting unexpected ClassCastExceptions.
General rule of thumb
There is a general rule of thumb for situations like this, when you have nested type arguments with wildcards:
If the inner types have wildcards in them, then the outer types often need wildcards also.
Otherwise the inner wildcard can't "take effect", in the way you have seen.
References
The Java Tutorial contains some information about capture types.
This question has answers with general information about wildcards:
What is PECS (Producer Extends Consumer Super)?
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);
Trying to fully understand Generics in Java, I ran into a problem I couldn't really get around, nor find a specific solution for in the web.
I have this method:
public <T extends City> void someMethod(List<T[]> objectsList) {
List<City[]> myList = (List<City[]>) objectsList;
}
Just writing this into the compiler with provide me with an error I can ignore - however during compilation the compiler fails saying
Error:(97, 46) error: incompatible types: List<T[]> cannot be converted to List<City[]> where T is a type-variable: T extends City declared in method <T>someMethod(List<T[]>)
A simple solution is to simple run the entire List and Arrays within the received 'objectsList' and typecast them one at a time, which would work (I tried that), however I'm not really sure that's the right way to do it...
This isn't something you're going to want to do often (since generics and arrays don't mix well), but there are some important things to make note of:
T holds the type information for the method. You may not be passing in a City at all times, and in the off case you don't, Java can't help you out.
The cast is not going to be safe, due to the fact that generics are invariant; you cannot say that a List<City[]> is equivalent to a List<T[]>, even if T extends City.
With that, what you can do to fix this is use a wildcard bound on the generic types. Use them in both the declaration of what you accept to the method, and what you're storing. You then avoid the cast, and everything is type-safe.
public <T extends City> void someMethod(List<? extends T[]> objectsList) {
List<? extends T[]> myList = objectsList;
}
Because, in Java, an array is like a type. Ergo, the type City[] is completely different to T extends City I'm not sure what you're getting at, but that List<T[]> looks extremely dodgy.
How To Use Generics
Let's say you want to return an ArrayList in the reverse order to one parametrised. I don't know why, and I expect there's a perfectly good library function to do so, but let's roll with it.
public ArrayList<E> getReversedList(ArrayList<E> list) {
ArrayList<E> newList = new ArrayList(list.size());
for (int i = 0; i < list.size(); i++)
newList.add(list.size() - i, (E) list.get(i));
return newList;
}
There is no need to cast - just work with arrays of type T[]. For example, this compiles:
public <T extends City> void someMethod(List<T[]> objectsList) {
for (T[] cities : objectsList) {
for (City city : cities) {
// do something with the city object
}
}
}
The code in the method doesn't know which exact subclass of City is in the arrays, but you may assign them to a variable of type City and treat them as City objects.
The reason your cast doesn't work is due to the fact that List<SubType> is not a subtype of List<Type>.
I have written below code
class Student {}
class Student1 extends Student {}
class Student2 extends Student {}
List<? extends Student> emp = new ArrayList<>();
emp.add(new Student()); // I do not want this to happen. at compile time it should give me error
emp.add(new Student1()); // this should happen
emp.add(new Student2()); // this should happen
But in above code its not working throwing compile error in all 3 adds.
Any pointers?
No, there is no such option in Java to constrain the type parameter to just subtypes of a given type. It looks like you have taken the meaning of the syntax ? extends Type a bit too literally. Type there is the upper bound of the wildcard, and the bounds are always inclusive.
If your idea was the way it really worked, there would be no type by which you could refer to a general item of your list: the narrowest such type is Student.
In java Generics are not co-variant. So List<T> is not a sub-type of List<S> if T is a sub-type of S. This was done to ensure static, compile time type safety. If it were allowed you could do things like following:
List<Integer> intList = new ArrayList<>();
List<Number> numberList = intList;
numberList.add(1.0); // Breaks the contract that intList will contain only Integers
// (or Objects of classes derived from Integer class)
Now to allow functions which could work on Lists containing anything which extends some base class, Bounded wildcards were introduced. So for example, if you wish to write a generic add method which returns the sum of all elements in a List (irrespective of whether the list if of Type Integer, Double, Float), you can write the following code
double add(List<? extends Number> numberList) {
double ans = 0;
for (Number num : numberList) {
ans += num.doubleValue();
}
return ans;
}
The argument can be List of any Object which extends Number such as List<Double>, List<Float>, List<Short>, List<Integer>.
Now coming to your question, when you say a List is of Type <? extends Student> and add Student1() to it, the compiler is not able to verify whether the type is correct or not and it breaks the compile time safety. (Because ? is an unknown type). Normally you cannot add anything to a List having a bounded wildcard as its type (except null and except if you follow a rather complicated process). So either you can declare your list as of type Student but that will mean that your first add statement would not throw an error.
In most practical scenarios, this kind of case is handled by making Student abstract and implementing all the common functionality in the abstract class while declaring your list as of type Student.
Also if you ever want to add to a bounded wildcard type, you can write a helper function like this and call it in your method (This also accepts Student as valid type. There's no way to force a Type to extend something because type resolution is always inclusive in Java):
private <Student, S extends Student> void addd(List<Student> l, S element) {
l.add(element);
}
and call it as:
addd(emp, new Student());
addd(emp, new Student1());
addd(emp, new Student3());
Use "List<? extends Student>" when you want to access the existing elements of List.
Use "List<? super Student>" when you want to set the elements to the List.
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);