Understanding use of wild card in java generics? - java

I have asked a similar question and this is a more specific version in the hope of getting a clear answer.
While trying to understand java generic types and usage of wild card "?", I tried the following:
List<Integer> li4 = new ArrayList<Integer>();
li4.add(new Integer(5));
Integer myInt4 = li4.get(0);
Now I replace with the more generic type '
List<? extends Integer> li = new ArrayList<Integer>();
Integer myInt = li.get(0);
The above compiles fine.
It seems that 'li' above is being treated as a list where each element is an Integer (look at the li.get(0) call).
I can also do the following:
li = li4;
The above compiles and runs fine.
But when I try:
li.add(new Integer(5));
I get following compilation error (using Oracle JDeveloper as IDE):
Error(24,9): cannot find method add(java.lang.Integer)
'? extends Integer' should allow any types that extend Integer. It behaves like that for the 'get' method where it returns an Integer. Similarly it does not complain when ArrayList is assigned to it. So for example 'li = new ArrayList<String>()' does not compile. So why am I allowed to assign another ArrayList<Integer>(), get an Integer back but not add an Integer?

? extends Integer does not mean "should allow any types that extend Integer". It means that this is a List<T> for some specific T that extends Integer.
So anything you get out of a List<? extends Integer> will extend Integer, but you can't just put anything in -- we don't know that the type you're putting in matches T.
To give a concrete example, Integer extends Number. So you could write
List<? extends Number> list1 = new ArrayList<Integer>();
List<? extends Number> list2 = new ArrayList<Double>();
But you shouldn't be allowed to write list2.add(new MyNumber()), or list2.add(new Integer(3)), because list2 is actually a List<Double>.

Following code helped me understand the answer:
List<Exception> exL = new ArrayList<Exception>();
exL.add(new Exception());
Exception ex = exL.get(0);
Throwable th = exL.get(0);
exL.add(new NullPointerException());
/** Error(153,7): cannot find method add(java.lang.Exception)
exL.add(new Throwable());
**/
So when we declare List, the elements must at least have behavior of Exception which means Exception, RuntimeException and its sub-classes. It is ok to assign an element returned from such a list to either Exception or any of its subclasses. "Exception e = exL.get(0)" Or "Throwable th = exL.get(0)". It is also ok to add an instance of Exception or say NullPointerException to List.
List genExcepList, can be a List, List or List as "? extends Exception" represents Exception and all its sub-classes. In fact, List is a super class of List, List etc. For any of these list types, the returned element from 'get' call is guaranteed to be of type Exception. So it is ok to do: Exception e = genExcepList.get(0). But it is not ok to call genExcepList.add(exception) as List must satisfy the restrictions on every type it allows which are List, List, List etc. The only thing that works is genExcepList.add(null) as 'null' would work for every type.
On the other hand if we have something like:
List rtgs = new ArrayList();
"? super Exception" represents Exception and any its super-classes. So List represents List, List and List. On any of these list types, one can call "add(new Exception());" so it is ok to call rtgs.add(new Exception()) as an Exception is also a Throwable and Object. However, it is not ok to call "Exception ex = rtgs.get(0)" as the element returned from List can be any of Exception, Throwable and Object. So the only thing that would safely work is "Object ex = rtgs.get(0)".

Related

Cannot add an object to List<?> instantiated as ArrayList<Object>

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.

Issue with using 'extends' and 'super' in java generics with generic methods

There is a method :
public static <T> void addandDisp(Collection<T> cs, T t)
which is being called in the following way :
List<? extends Object> ls2 = new LinkedList<Number>();
addandDisp(ls2,new Object());
This gives a compile time error. On the other hand, if we had only one parameter, then the call is successful. Why is that ?
Moreover, this is successful :
List<? super String> ls1 = new LinkedList<String>();
addandDisp(ls1,new String());
while this is not :
List<? super String> ls1 = new LinkedList<Object>();
addandDisp(ls1,new Object());
What is the underlying logic ?
I would assume that addandDisp means add and display.
When you have a collection, defined as:
List<? extends Object> ls2 = new LinkedList<Number>();
This means that the compiler will allow you to assign the colletion to all the possible unknown subtypes of Object. Since you have the operation add, the compiler denies to give you green light, because it doesn't know if the provided object's type meets the restriction to be of the unknown subtype of Object. This is called covariance.
Similary, when you have a definition like this:
List<? super String> ls1 = new LinkedList<String>();
The compiler allows you to assing ls1 to:
LinkedList<String>(); //you can add Strings to the list
LinkedList<Object>(); //you can add Strings and Objects to the list
In this case, the compiler will be completely aware if the object you're trying to pass meets the condition to be subtype of the generic type of the collection. This is called contravariance.
More info:
What is PECS?
Here is another approach, but others gave a quite detailed response.
The first example:
List<? extends Object> ls0 = new LinkedList<Number>();
addandDisp(ls0, new Object());
Here ls0 may be of any type that is a subclass of Object, like a Number as your example shows.
Now what if this could work? It would mean that if you can put an Object into any of the collections.
Imagine I want to iterate your previous list of Number objects:
for (Number n : numbers) {
...
}
If I could put an Object here, then Plaff! I would get a class cast exception immediately.
The second example
List<? super String> ls1 = ...;
addandDisp(ls1,new String());
Let's play a bit with the object hierarchy: String is a subclass of CharSequence which is a subclass of Object.
You get ls1 here which is an unknown supertype of String (e.g., a CharSequence). Is it all right to put a String into a list of character sequences? Yup, it's okay, since they share the same interface for sure! You can handle them through it in the same way.
The third example
List<? super String> ls1 = ...;
addandDisp(ls1,new Object());
Let's suppose the previous situation: you assign ls1 a list of CharSequences. Can you add an object to a List<CharSequence>? Nope, for the same reason as the first example fails.
Hope that helps clarifying your issues a bit :-)
On the other hand, if we had only one parameter, then the call is successful. Why is that ?
Your method has a single type parameter. When you invoke a method, you either provide a type argument explicitly or have one be inferred implicitly.
When you invoke it like
addandDisp(ls2, new Object());
the compiler needs to extract a type to bind to T from both method arguments, since both method parameters rely on the method's type parameter, T.
The issue here is that ? extends Object and Object do not produce a single type that can be bound to T safely. Imagine you had
public static <T> void addandDisp(Collection<T> cs, T t) {
cs.add(t);
}
...
List<? extends Object> ls2 = new LinkedList<Number>();
addandDisp(ls2, new Object());
This add should not be allowed since an Object should not be used where a Number would have been expected. Type safety would break.
If you have a single parameter
public static <T> void addandDisp(Collection<T> cs) {
Then the compiler uses the single argument to infer the type argument. There is then no ambiguity about what to use.
1 example: Object is not ? extends Object (for example, Number is ? extends Object, but Object is not Number). Thus, it is not type safe.
2 example: String is ? super String. It is type safe.
3 example: Object is not ? super String. Not safe again.

ArrayList with generics declaration in Java

The following code gives me an error for the line l.add
List<? extends Number> l = new ArrayList<Integer>();
l.add(1);
and forces me to write it as l.add(1, null);
Why is it so?
With a wildcard on the variable l, the compiler doesn't know (or care) which subclass of Number (or Number itself) the type parameter really is. It could be an ArrayList<Double> or an ArrayList<BigInteger>. It can't guarantee the type safety of what's passed in to add, and because of type erasure, the JVM can't catch a type mismatch either. So the compiler preserves type safety by disallowing such calls to add unless the value is null, which can be any type.
To get add to compile, you must declare l as:
List<? super Integer> l = new ArrayList<Integer>();
or you can remove the wildcard:
List<Integer> l = new ArrayList<Integer>();
It has to be:
List<? super Integer> l = new ArrayList<Integer>();
l.add(1);
Note the <? super Integer> declaration. It's called an upper-bound wildcard.
What does it do?
It restricts the Runtime type of the elements of the ArrayList to be one of the super classes of Integer, e.g. Integer, Number or Object, which means that you will be able to assign l to:
new ArrayList<Integer>
new ArrayList<Number>
new ArrayList<Object>
In the three cases, the statement l.add(1) is perfectly valid, so there's no compile-time error.
More info:
Upper-bounded wildcards in Java
What is PESC?
List<? extends Number> is different from List<T extends Number>. For List<T extends Number>, the l.add(new Integer(1)) would work.
If you use ? extends Number then you can't refer to the type, but you can still use ((List<Integer>)list).add((int) s).
You can write:
List<? extends Number> l = new ArrayList<>();
((List<Integer>)l).add((int) 1);
instead.
Generics is all about "Type Safety and invariance".
There are 2 scenario associated with your question.
Holding reference type.
Making modification to contents of that reference.
Your first statement is about holding the reference.
List<? extends Number> l = new ArrayList<Integer>();
Here, compiler ensures that l can hold the reference of Type List of objects which extends Number.
eg: List<Integer>, List<BigInteger>, List<Double> etc.
In our case it is List<Integer>. So far so good.
Now what am I allowed to add to l? Users could want to add anything (which extends Number, eg. Double, BigInteger or Integer). This would break the type safety. So to overcome this, the compiler ensures that the user is not allowed to add to such kind of references.
This kind of references are used mainly when we are only iterating through a type of list.
eg:
// Here numbers can take reference of List type of objects which extends Number.
public void printValue(List<? extends Number> numbers){
// Iterate through all the numbers.
for(Number number: numbers){
System.out.println(number.intValue());
}
}
Now coming to the 2nd scenario of this discussion, that is modifying the list.
For doing this, the compiler wants user to ensure type safety before making modification.
This can be done through <? super T>.
List<? super Number> n = new ArrayList<Number>();
Here the compiler allows to add subtypes of Number to n.
BigInteger b = new BigInteger("4");
Double d = new Double(2);
n.add(b); // valid
n.add(d); // valid
//Compiler won't allow here.
n.add(new CustomNumber()); // CustomNumber is not a subtype of Number. It addition in "n" is Invalid.
In simple words, List<? extends Number> declares a List of elements of unspecified type. You only specify that the type of the elements extends the class Number. l could be a List<Integer> or a List<Double>.
The compiler will allow to assign an element of the list to a Number, because it knows that anything in l is a Number or a subclass thereof.
But it will not allow to add any element to the list because the variable l could contain a List<Integer> that accepts only Integers, or a List<Double> that accepts only Doubles. No type is guaranteed to be accepted by the list. In some sense, List<? extends Number> is a read-only list.
If you need l to be a List of Numbers, maybe you pass it to a procedure that requires a List<Number>, then you must initialize it as an ArrayList<Number>().
But if you will always use Integers with that list, you should declare it as List<Integer> and initialize it with ArrayList<Integer>. Remember that Integer is a final class, it allows the compiler to optimize the code much more than with Numbers.

Why can't a method take a Collection<subClass> when the method's signature is defined as Collection<class>

I have a method that takes a list of SResource objects
public static List<STriple> listTriples(List<SResource> subjects){
//... do stuff
}
Why can't I do this
List<IndexResource> resultsAsList = new ArrayList<IndexResource>();
resultsAsList.addAll(allResults.keySet()); // I could possible not use lists and just use sets and therefore get rid of this line, but that is a different issue
List<STriple> triples = new ArrayList<STriple>();
triples = TriplesDao.listTriples(resultsAsList);
(The compiler tells me I have to make triples use SResource objects.)
When IndexResource is a subclass of SResource
public class IndexResource extends SResource{
// .... class code here
}
I would have thought this has to be possible, so maybe I am doing something else wrong. I can post more code if you suggest it.
You can do it, using wildcards:
public static List<STriple> listTriples(List<? extends SResource> subjects){
//... do stuff
}
The new declaration uses a bounded wildcard, which says that the generic parameter will be either an SResource, or a type that extends it.
In exchange for accepting the List<> this way, "do stuff" can't include inserting into subjects. If you're just reading from the subjects in the method, then this change should get you the results you want.
EDIT: To see why wildcards are needed, consider this (illegal in Java) code:
List<String> strings = new ArrayList<String>();
List<Object> objList = string; // Not actually legal, even though string "is an" object
objList.add(new Integer(3)); // Oh no! We've put an Integer into an ArrayList<String>!
That's obviously not typesafe. With wilcards, though, you can do this:
List<String> strings = new ArrayList<String>();
string.add("Hello");
List<? extends Object> objList = strings; // Works!
objList.add(new Integer(3)); // Compile-time error due to the wildcard restriction
You can't do this because generics are not "covariant" i.e. a List<Integer> is not a sub-class of the List<Number> though Integer is a sub-class of Number.
For those who are unable to add wildcards, this should work.
List<Integer> list = new ArrayList<Integer>();
new ArrayList<Number>(list);

What are the risks of explicitly casting from a list of type List<? extends MyObject> to a list of type List<MyObject> in Java?

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.

Categories

Resources