Generics (List) typing question - java

I am trying to use a common technique to create objects from Xml. (Xml is legacy, so although there are already libraries to do this, it seemed faster to write this myself.)
I don't understand the compiler's complaint about the generic usage. Code sample:
public void createObjects() {
List<Object1> objectOnes = new ArrayList<Object1>();
List<Object2> objectTwos = new ArrayList<Object2>();
parseObjectsToList("XmlElement1", objectOnes);
parseObjectsToList("XmlElement2", objectTwos);
}
private void parseObjectsToList(String xmlTag, List<? extends Object> targetList) {
// read Xml and create object using reflection
Object newObj = createObjectFromXml(xmlTag);
targetList.add(newObj)
/* compiler complains: "The method add(capture#2-of ? extends Object) in the type List<capture#2-of ? extends Object> is not applicable for the arguments (Object)"
*/
/* If I change method signature to parseObjectsToList(String xmlTag, List targetList)
it works fine, but generates compiler warning about raw type */
}
Thanks for any enlightenment on the subject!

The problem you are running into is that, with the bounded wildcard that you have defined, you will be unable to add any element to the collection. From this tutorial:
List<? extends Shape > is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. However, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.
There is, as usual, a price to be paid for the flexibility of using wildcards. That price is that it is now illegal to write into shapes in the body of the method

All a wildcard type means is that the actual type parameter T of the List that you pass as the second argument to parseObjectsToList is going to be a subtype of Object. It does NOT mean that the same List will be parameterized with different types.
So now you have a List<T> (called targetList) and you are trying to call targetList.add(Object). This is illegal because Object is not necessarily a subtype of T.
Because you are adding to the List rather than extracting elements from it, use List<Object> and make sure that's exactly what you pass in.

Using a List<Object> will work, but you might want keep your more precisely typed List<Object1> and List<Object2> for type-safety elsewhere. In that case, you'll need to check the type of each object before adding it to the List.
private void parseObjectsToList(String tag, List<T> list, Class<? extends T> c) {
// read Xml and create object using reflection
Object newObj = createObjectFromXml(tag);
list.add(c.cast(newObj)) ;
}
The cast() operation is a reflective equivalent to the static cast operator: (T) newObj
Using the altered method would look something like this:
parseObjectsToList("XmlElement1", objectOnes, Object1.class);

Think about what you are asking the compiler to do:
Given a list of "something that is a subtype of Object
Let me insert an Object into it
This doesn't make sense. Suppose your list is a list of Integer. Suppose that createObjectFromXml returns a String. It wouldn't make sense to allow inserting a String into a list typed for Integers.
So, your options are either to make your List a List<Object> or to find some way to make createObjectFromXml return a specific type, that you can then tie to the type of your list.

Related

Java Generics with class object as generic type

I want to create a Java Object for a class that is defined using generics.
Specifically, i want to create a List of Objects of class that is determined at runtime.
I would want something like
Class clazz = Class.forName("MyClass");
List<clazz> myList = new ArrayList<>(); // This isn't allowed
Defining an array of object would allow me to store a list of MyClass type objects, but that would lead to casting the objects every-time the object is fetched from the list, i would like to avoid such a scenario.
Is there a way to achieve something like the above code using java.
Well, since you know that class, you could (with a warning) cast the List itself; but you would still need to know the class name and some checks for that, like for example:
if(clazz.getName().equals("java.lang.String")) {
// warning here
yourList = (List<String>) yourList;
}
As I understand your question, you want to know if is possible for the compiler to know the runtime type while it is limited to the compile type.
You can't. And your hypothesis is also wrong:
Defining an array of object would allow me to store a list of MyClass
type objects, but that would lead to casting the objects every-time
the object is fetched from the list, i would like to avoid such a
scenario.
In Java, generics does not remove the cast: it is still there in the form of type erasure and (hidden) cast. When you do List<String>, you merely ask the compiler to hide the cast in operation such as T get(int): there will be a cast to String.
If you want to use the compile time information, than that would mean you already have/know the type MyClass available at compile time and you would not use Class::forName but MyClass.class which would return a Class<MyClass>.
What you can do is either:
Use an interface if you have a common ground for theses classes (like JDBC Driver).
Cast the raw list into a known type, for example using Class::isAssignableFrom.
No, you can't. Generics in Java is just a compile-time type checking mechanism. If you don't know the type until runtime, then, obviously, it cannot be used for compile-time type checking. The compiler can't determine at compile time what types to allow you to put into or get out of a List<clazz>, so it's no more meaningful than just a raw type List.
There is a little trick that can be used to work with a specific unknown type: Declare a type parameter that is only used for the unknown type:
public <T> void worksWithSomeUnknownClass() throws ReflectiveOperationException {
#SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) Class.forName("MyClass");
T obj = clazz.getConstructor().newInstance();
List<T> myList = new ArrayList<>();
myList.add(obj);
}
This solution is very limited though. It makes sure that you don't mix it up with other unknown types or Object, but you can not really do anything with T. And you have to declare a type parameter on every method that uses it.

Why generic type is not applicable for argument extends super class for both?

Here is the problem that I have been being tried to find the solution.
We have two class definitions. One of two extends other one.
class T{}
class TT extends T{}
The requirement is that there should be a list keeps object extends T
List<? extends T> list = new ArrayList<>();
But the problem occures when I try to put a TT object ( barely seems it is a subclass of T )
into the list.
list.add(new TT());
Compilation Error Message
The method add(capture#2-of ? extends Cell) in the type List is not applicable for the arguments (Cell)
You can create a List<T> list = new ArrayList<T>(); directly, this can allow all subtypes of T into the list. This is actually little difficult to understand. when you declare it as
List<? extends T> list = ...
It means that it can allow any unknown subtypes of T into the list. But, from that declaration we cannot ensure which is the exact sub-type of T. so, we can only add null into it
List<? extends T> indicates that anything can comes out of it can be cast to T, so the true list could be any of the following:
List<T>
List<T2>
List<TT>
etc
You can see that even a new T cannot safely be added to such a collection because it could be a List<T2> which T cannot be put into. As such, such List cannot have non null entries added to them.
In this case you may simply want List<T>
So why would you ever use this?!
This contravariance can be useful for method parameters or returns, in which a collection will be read, rather than added to. A use for this could be to create a method that accepts any collection that holds items that are T, or extend T.
public static void processList(Collection<? extends Vector3d> list){
for(Vector3d vector:list){
//do something
}
}
This method could accept any collection of objects that extends Vector3d, so ArrayList<MyExtendedVector3d> would be acceptable.
Equally a method could return such a collection. An example of a use case is described in Returning a Collection<ChildType> from a method that specifies that it returns Collection<ParentType>.
The requirement is that there should be a list keeps object extends T
If you just want a List where you can store objects of any class that extend from T, then just create a List like this:
List<T> list = new ArrayList<T>();
The way you've created a list currently, will not allow you to add anything except null to it.
There are boundary rules defined for Java Generics when using WildCards
**extends Wildcard Boundary**
List means a List of objects that are instances of the class T, or subclasses of T (e.g. TT). This means a Read is fine , but insertion would fail as you dont know whether the class is Typed to T
**super Wildcard Boundary**
When you know that the list is typed to either T, or a superclass of T, it is safe to insert instances of T or subclasses of T (e.g.TT ) into the list.
In your example , you should use "super"
An addition to the other answers posted here, I would simply add that I only use wild cards for method parameters and return types. They're intended for method signatures, not implementations. When I put a wildcard into a variable declaration, I always get into trouble.

Generic method vs wildcard - compilation error

I had a issue where (to simplify):
public void method(List<List<?>> list){...}
gave me a compilation error when called with:
method(new ArrayList<List<String>>()); // This line gives the error
After reading a similar thread, I understood that it would work if I were to rewrite the method signature as:
public void method(List<? extends List<?>> list){...}
Now, my question is, why does the following work then?
public <T> void method(List<List<T>> list){...}
Confusions do come when you deal with multi-level wildcard syntax. Let's understand what those types exactly mean in there:
List<List<?>> is a concrete parameterized type. It is a heterogenous collection of different types of List<E>. Since List<?> represent a family of all the instantiation of List, you can't really pass an ArrayList<List<String>> to List<List<?>>. Because, nothing stops you from adding a List<Integer> to it inside the method, and that will crash at runtime, had compiler allowed it.
List<? extends List<?>> is a wildcard parameterized type. It represents a family of different types of List<E>. Basically, it might be a List<ArrayList<String>>, List<LinkedList<Date>>, so on. It can be a list of any type that extend from a List<?>. So, it will be safe to pass a ArrayList<List<String>> to it, the reason being, you won't be allowed to add anything, but null to the list. Adding anything to the list will be a compile time error.
As for List<List<T>>, it is again a concrete parameterized type. And since you're dealing with a generic method now, the type parameter will be inferred to be the type that is passed for it. So, for an ArrayList<List<String>>, type T will be inferred as T. A generic method deals with the types that are declared with it. So, there is only a single type T here. All the lists you get out of List<List<T>> will certainly be a List<T> for any type T. So, it's a homogenous collection of that type of List. Inside the method, you can not add any arbitrary List<E> to the List<List<T>>, because the compiler doesn't know whether that type E is compatible with T or not. So, it is safe invocation.
Related:
Multiple wildcards on a generic methods makes Java compiler (and me!) very confused
Java HashMap nested generics with wildcards
What are multi-level wild cards? Confusion in syntax
When to use generic methods and when to use wild-card?
I think I found the answer in Angelika Langer's generics FAQ, "Case Study #3":
If a method signature uses multi-level wildcard types then there is always a difference between the generic method signature and the wildcard version of it. Here is an example. Assume there is a generic type Box and we need to declare a method that takes a list of boxes.
Example (of a method with a type parameter):
public static <T> void print1( List <Box<T>> list) {
for (Box<T> box : list) {
System.out.println(box);
}
}
Example (of method with wildcards):
public static void print2( List <Box<?>> list) {
for (Box<?> box : list) {
System.out.println(box);
}
}
Both methods are perfectly well behaved methods, but they are not equivalent. The generic version requires a homogenous list of boxes of the same type. The wildcard version accepts a heterogenous list of boxes of different type. This becomes visible when the two print methods are invoked.
The basic reason is that List<List<?>> is not a superclass of List<List<String>>.
A List<List<?>> could contain a List<Integer> and a List<String> for example.
The generic types must match exactly, otherwise you could get erroneous assignments made.

Determine generic arguments of List<>

I'm using reflection to walk the field members of a class and I need to know for List<> subclasses, what the generic type parameters are.
Given a field that has a type that is a subclass of List, how can I tell in a generic way what the type parameters of List<> are?
For example:
class X<T> {
List<String> x1; // String
ArrayList<String> x2; // String
SubclassOfArrayListString x3; // String
List<?> x4; // error
List<T> x5; // error
}
class SubclassOfArrayListString extends ArrayList<String> {
// ...
}
NOTE: I added <T> to X above to illustrate that there might be cases where there isn't a correct answer - it has nothing to do with the problem, except being something to consider when answering.
You can't because generic type information is lost on compilation (*). That's also the reason you cannot create an array of some generic type T at runtime.
At runtime, every List<T>is again a raw type List -- you could even add an Integer to something declared as List<String>, generics won't and can't prevent that [Edit: using unchecked casts or a widening cast to a raw type; this will result in (suppressable) compiler warnings but no errors].
(*) Edit: I learned some new and stand corrected, certain type parameters (implementors of GenericDeclaration like Class, Constructor, Field and the return, parameter and exception types of Method) will be retained in the byte code and can be accessed at runtime using Field.getGenericType() and similar accessors.
public class X<T extends List> {
T field;
}
Is that what you need?
Read this tutorial about Generics : Generics Tutorial. This should clear things up.
This pdf has also good examples at the end of the wildcard usage as well as the extends ans super keywords. PDF Doc
Basically, Use Container<? extends A> when you want to read from the container
Use Container<? super A> when you want to write to the container.

Why can't add Strings in List?

As we know know that we can add strings and integers in list if we declare generic type for list. but the code seems not adding up the strings. what is the problem i can't understand..
class GHJ{String s;GHJ(String s){this.s=s;}}
class DFR{
public static void main(String[] g){
List<? extends Object> list=new ArrayList();
list.add((new String("ffg")));// compiler error
list.add("df");// compiler error
list.add("sdd");// compiler error
list.add(new GHJ("dff"));// compiler error
String s= list.get(0);// compiler error
System.out.println(list+" "+s);
}
}
The proper way is to use generics.
Create your list like:
List<String> list = new ArrayList<String>();
Then you don't need to cast.
Edited:
As dpb noted there're multiple types of objects. From my point of view, mixing different type of objects in a list is asking for problems. The only way of solving it is:
Don't use generics, everything will be an Object in the list
When you need to retrieve something is needed to do something like:
if (list.get(0) instanceof String) {
} else if (list.get(0) instanceof GHJ) {
} .....
In your case, there's no need for you to make your list generic. Just use
List list = new ArrayList();
This will create a list of Object types.
Just to supplement Carlos's answer:
Suggested reading from Java language guide -> http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html
and for more comprehensive coverage:
http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
You have told the compiler that the type in your list is something that extends object. then you add things to the list and try and get one out. All the compiler can know that what you get out will extend object, not that it will definitely be a string. You need to tell the compiler that you are getting a string out of the list.
if you said
Object a= new String("blah");
String s;
you would need to cast a to a string to assign it to s
s= (String) a;
what happens in your example if you do
String s = list.get(3);
where you have put your new GHJ("dff")?
how is the compiler to know what type of object is in each position?
You can, but only by either using an untyped (get rid of
<? super String>
entirely, so:
List x = new List(); x.add(new Object()); x.add("blah!");
or by finding a common superclass (in this case, only Object) for the generic argument
List<Object> x = new List<Object>(); x.add(new Object()); x.add("blah!");
You can pull tricks to stuff elements into lists, bypassing generic typechecks, but this is Very Bad Juju.
list is of type List<? super String> so if you want to add something to it, then it needs to be either a String or something that's a superclass of String. The only thing that's a superclass of String is Object. GHJ is not a superclass of String so you can't add an instance of GHJ to that list.
if you add different types to the list, you have to tell the compiler which type you are getting out of the list, you can't get around that.
could you not keep separate lists for each type of object and maybe keep a map which contains lists of individual types? Then you can get each list by type and all the elements in the list will be the same type?
Yes, you can add a list and an object in the list, but the only way to do that is be to have an untyped List. In that case you loose the type information and have to manually cast the objects your are getting out of the list back to their original type.
You could use instanceof to check into which type you can cast the objects, but that's particularly ugly...
Question is : do you really need to put both Strings and Objects in the same list ? that looks like a code smell to me.
You need to cast your String from Object, like this:
String s = (String)list.get(0);
Why would it add up the string?
It doesn't compile because needs a cast :
String str = (String)list.get(0);
The wild card will mess you up here because it's not doing what you intend it to do. Wild cards are traditionally used when you need to refer to some set of generic generics.
For example, if you wanted to let the user give you a collection that contained any type of Object then your method could take a Collection<? extends Object> as its parameter. You would want to do this because if you just used Collection<Object> as the parameter then they couldn't pass a Collection<String>.
So, the problem is that List<? extends Object> has some limitations because the compiler cannot know what specific type that List takes. For example, it could be a List<String>... the compiler has no idea. For that reason, you could never call any method on List that takes a <? extends Object> as its parameter. In other words, you cannot call add().
You could call get()... because the compiler knows what type the receiving variable is and can check that it's ok. Object foo = myList.get(0) is fine.
I think in this case you really want List<Object> and to make sure to declare the type parameter on the ArrayList as well.
List<Object> list = new ArrayList<Object>();
And if you want strings then you will have to specifically convert the Objects to string. I suggest String.valueOf() personally.
Incidentally, the above is documented pretty well in the wildcards section of the Generics chapter in Core Java volume 1 (well, at least my ancient JDK 1.5 edition).

Categories