I've read a few topics which cover certain questions about generics, such as their relationship with raw types. But I'd like an additional explanation on a certain line found in the Java SE tutorial on unbound generics .
According to a sentence :
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print List<Integer>, List<String>, List<Double>, and so on, because they are not subtypes of List<Object>.
If I understand well this sentence; the difference between List<?> and List<Object>, is that we can use the type argument List<String> or List<Integer> by implementing the former. While if we implement the later, we can only use the type argument List<Object>. As if List<?> is an upper bound to Object namely List<? extends Object>.
But then the following sentence confuses me, in the sense that according to what I previously understood, List<Object> should only contain instances of the class Object and not something else.
It's important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can only insert null into a List<?>.
There are two separate issues here. A List<Object> can in fact take any object as you say. A List<Number> can take at least Number objects, or of course any subclasses, like Integer.
However a method like this:
public void print(List<Number> list);
will actually only take a List which is exactly List<Number>. It will not take any list which is declared List<Integer>.
So the difference is List<?> will take any List with whatever declaration, but List<Object> will only take something that was declared as List<Object>, nothing else.
The last quote simply states, that List<?> is a list for which you literally don't know what type its items are. Because of that, you can not add anything to it other than null.
The sentence that is confusing you is trying to warn you that, while List<?> is the super-type of all generic lists, you cannot add anything to a List<?> collection.
Suppose you tried the following code:
private static void addObjectToList1(final List<?> aList, final Object o ) {
aList.add(o);
}
private static void addObjectToList2(final List<Object> aList, final Object o ) {
aList.add(o);
}
private static <T> void addObjectToList3(final List<T> aList, final T o ) {
aList.add(o);
}
public static void main(String[] args) {
List<String> testList = new ArrayList<String>();
String s = "Add me!";
addObjectToList1(testList, s);
addObjectToList2(testList, s);
addObjectToList3(testList, s);
}
addObjectToList1 doesn't compile, because you cannot add anything except null to a List<?>. (That's what the sentence is trying to tell you.)
addObjectToList2 compiles, but the call to it in main() doesn't compile, because List<Object> is not a super type of List<String>.
addObjectToList3 both compiles and the call works. This is the way to add elements to a generic list.
Related
I would like to create a generic method with a return type that contains a generic element sent in to the method.
This works fine as long as the input argument is of exact same type as the generic type. However I would like it to be possible to send an extension of the generic type.
Basically, I would like to create a method, createList, that would work for both of these calls:
List<Object> list = createList("foo");
List<String> list2 = createList("bar");
This generic method below works fine for the second call, but not for the first one.
private static <T> List<T> createList(T element) {
List<T> list = new ArrayList<>();
list.add(element);
return list;
}
For the second call I get a compilation error saying "Incompatible types. Required Object. Found String".
Below is the non-generic version which works fine for the first call, but not for the second:
private static List<Object> createList(Object element) {
List<Object> list = new ArrayList<>();
list.add(element);
return list;
}
Same compilation error here as on the other version but this time String is required, and Object was found.
Is it possible to create a method (preferrably using generics) in a way that both these calls would work? (using Java 7)
There were some type inference changes in Java 8 that fix this problem. So the straightforward solution is to update.
If you can't or don't want to update to Java 8, you can also provide the generic type explicitly:
List<Object> list = EnclosingClass.<Object>createList("foo");
Where EnclosingClass is the class that declares createList.
I'm doing something similiar, and solved the problem using a method like this.
Also this is not limited to any certain "Type" of Elements. It allows Every Object and/or primitive. IT also gives you a one-liner to fill a created list with some elements.
#SafeVarargs
private static <T extends Collection<S>, S> T makeList(T collection, S... objects) {
Collections.addAll(collection, objects);
return collection;
}
//example:
List<String> list1 = makeList(new LinkedList<String>(), "foo", "bar");
List<Integer> list2 = makeList(new ArrayList<Integer>(), 1,2,3,4);
List<? extends String> list = new Arraylist<String>();
list.add("foo");
Given piece of code gives me compile time error.i don't get it why i can't add string in list.
but the code means that we can add the String class object and it's derived class object in the list
still i am getting the error why
List<?> should only be used when you are not concerned with the data type of the items and interested in operations such as getting size of list etc.
For Example,
public int getSize(List<?> itemList) {
return itemList.size();
}
It is more of a Read Only list.
You should be using the following if you intend to make a new list of String items.
List<String> list = new Arraylist<>();
list.add("foo");
Alternatively, you can use this:
List<Object> list = new Arraylist<>();
list.add("foo");
This will work:
List<? super String> list = new ArrayList<String>();
list.add("foo");
Then your compiler will now, that the caller is to pass a list of objects that are String or a super type.
When you say <? extends String> it means it can be of any type which extends String. That means somebody can pass List and it will accept it.
Look also here:
Difference for <? super/extends String> in method and variable declaration
Essentially List<? extends String> is a List of an unknown type, all that is known is that this type extends String. It does not matter that the variable contains an instance of List<String>.
Now String is final, so there is no subclass... so let's consider:
List<? extends A> list = new ArrayList<A>();
where A is some class with a subclass B. When used, a variable of type List<? extends A> might be an instance of List<B>. We can not add A to it because there is the possibility that an A is not a B.
But you know it will be right? Well, the compiler doesn't, nor does it anticipate the logical connections between the unknown type and what you've instantiated, because in general you may change that variable at runtime.
A similar example exist in the tutorial optimistically named More Fun with Wildcards, where a method accepts (Set<T>, T) and an illegal call is preposed using (Set<?>, String) though the Set<?> variable contains an instance of Set<String>. The issue is the same here, despite the addition of extends.
For practicing purposes I am trying to write a general method to display the elements of an ArrayList by calling its .toString() method. Let's please assume .toString() does what I want it to do.
I came up with this solution below where my input ArrayList is of type Object:
public void printArralyList(ArrayList<Object> list){
for(Object o:list){
System.out.print(o.toString());
}
System.out.println();
}
However it would not work!
printArralyList(new ArrayList<Integer>(Arrays.asList(1,2,3,5,8,13,21)));
The compilation error I get is
The method printArralyList(ArrayList<Object>) is not applicable
for the arguments (ArrayList<Integer>
how can I address that?
An ArrayList<Integer> is not an ArrayList<Object>, even though an Integer is an Object.
You need a wildcard in your method's parameter, because you don't care what type the generic type parameter is.
public void printArralyList(ArrayList<?> list){
Incidentally, you can have your method take a List instead of an ArrayList, and there would be no need to wrap the return of Arrays.asList in an ArrayList:
public void printArralyList(List<?> list){
and
printArralyList(Arrays.asList(1,2,3,5,8,13,21));
would work.
You dont need Generics or wildcard. All you need is a simple method which does not specify any type for the argument so that you can pass in an ArrayList of anytime.
public void printArralyList(ArrayList list){
for (Object o:list){
System.out.print(o.toString());
}
System.out.println();
}
The reason for the error was explained by rgettman in https://stackoverflow.com/a/21996188/
However, if it is for practicing purposes, you should consider practicing polymorphism and programming to an interface
public static void printIterable(Iterable<?> iterable)
{
for (Object object : iterable)
{
System.out.print(object);
}
System.out.println();
}
This can be used with an ArrayList<Integer> parameter, as well as with an LinkedHashSet<JComponent>, or anything else that implements the Iterable interface....
Answering your question.
The declaration ArrayList does not equal to ArrayList. Even if an Integer is delivered from Object in type hierarchy.
Important thing to remember is that when you declare Collection<String> myStrings, you tell to compiler, that to variable myString can be assigned only instances of class that are created upon String type and are Collections.
Collection<Object> myObjects;
myObjects = new ArrayList<String>(); //Exception
myObjects = new HashSet<String>(); //Exception
myObjects = new HashSet<Object>(); //Valid
myObjects = new ArrayList<Object>(); //Valid
myObjects = new ArrayList<Integer>(); //Exception
To solve problems like that, java provide set of Wildcars that allow you to pass this.
There are three type of will card that support various variances.
<? extends T> - Covariance
<? super T> - Contravariance
<?> - Invariance/
The rule about them is called PECS
As we want to consume the list elements, to remove the compilations errors you should use Covariance.
Collection<? extends Object> myObjects;
myObjects = new ArrayList<String>(); //Valid
myObjects = new HashSet<String>(); //Valid
myObjects = new HashSet<Object>(); //Valid
myObjects = new ArrayList<Object>(); //Valid
myObjects = new ArrayList<Integer>(); //Valid
As in Java every class is delivered from object so <? extends> is functionally equal to <?>, and that is what rgettman has proposed as answer
The collection framework in Java is supported with generics. You should get familiar with them to fully benefit from the framework.
A different way to solve is is to benefit from generic methods like this:
public static <T> void printList<Iterable<T> iterable) {
for(T element : iterable){
System.out.printf("%s ",element);
}
System.out.println();
}
Why static ?
The call to static method are faster and this method is not related to any class member.
What is ?
This is declaration of an generic method. It allow you to define the value for generic parameter. And Java is so keen that it can extract it by itself is most cases in version 7.
If you call the method with Iterable<String> then the value of T will be String if Iterable then `Integer'.
Why Iterable ?
The Iterable is a simple interface that allows you to use for-each look. This mean you will be able to iterate through all object that classes definition implements it.
Why printf ?
The printf function use Formatter, the benefits from it are two
- In case when instance of element is assigned with null, you will not get null pointer exception that will occur if you call it o.toString().
What is missing ?
In that implementation is still missing two things
- The input validation
- The proper output format that will separate the elements with a coma.
The error:
The method add(capture#1-of ?) in the type List<capture#1-of ?> is not
applicable for the arguments (String)
Code:
List<?> to = new ArrayList<Object>();
to.add(new String("here"));
Since List<?> is a generic type List and therefore can be of any type, then why it is not accepting String in add method?
A List<?> is a list of some type, which is unknown. So you can't add anything to it except null without breaking the type-safety of the list:
List<Integer> intList = new ArrayList<>();
List<?> unknownTypeList = intList;
unknownTypeList.add("hello"); // doesn't compile, now you should see why
should a String not be acceptable ?
No. <?> means the type is unknown and the compiler cannot be sure that any type is acceptable to add (this include String)
You can specify a lower bound:
List<? super Object> to = new ArrayList<Object>();
to.add(new String("here")); // This compiles
Now the compiler is sure that the list can contain any Object
According to wildcards, the question mark (?), called the wildcard, represents an unknown type and not a generic type, since its an unknown type the compiler cannot accept String in your case.
You can consider any list defined using wildcards to be read only. Still, there are some non-read operations that you can do.
From the docs:
You can add null.
You can invoke clear.
You can get the iterator and invoke remove.
You can capture the wildcard and write elements that you've read from the list.
List<?> could be rewritten as List<? extends Object> and ? extends Object means anything is subclass of Object.
For clarity, assuming we only have two subclass of Object here like String and Integer.
List<? extends Object> means this list can store a list of String such as List<String> or a list of Integer such as List<Integer>, but not a list of mixed String and Integer.
Back to your example, the list to can be a list of String or a list of Integer. When you try to add String into the list, it means this list can only store a list of String. However, the complier know that this list can also be a list of Integer and complier doesn't know which one is the case and an error is showed.
List<?> to = new ArrayList<Object>();
to.add(new String("here"));
Consider this code:
public <T> List<T> meth(List<?> type)
{
System.out.println(type); // 1
return new ArrayList<String>(); // 2
}
It does not compile at line 2, saying that List is required.
Now, if it's changed to:
public <T> List<?> meth(List<T> type)
{
System.out.println(type); // 1
return new ArrayList<String>(); // 2
}
It does compile. Why? I thought the difference between declaring a generic type with T and using the wildcard was that, when using the wildcard, one cannot add new elements to a collection. Why would <?> allow a subtype of List to be returned? I'm missing something here, what's the explicit rule and how it's being applied?
The difference is in the return type declaration. List<String> is not a subtype of List<T>, but it is a subtype of List<?>.
List<?> makes no assumptions regarding its type variable, so the following statements are valid:
List<?> l0 = new ArrayList<String>();
List<?> l1 = new ArrayList<Object>();
List<? extends Number> ltemp = null;
List<?> l2 = ltemp;
List<T> assumes that the type argument will be resolved in the context of client (e.g. type use), when you declared it as List<String> or List<Object>. Within the method body, you cannot make any assumptions about it either.
In the first case, T is not necessarily a superclass of String. If you choose a T like Integer and call the method, it'll fail; so it won't compile. However, the second will compile as surely, any ArrayList<String> is a valid List of something.
As said earlier, String isn't a subtype of T, so it's why it does not work. However, this code works :
public <T> List<T> meth(List<?> type)
{
System.out.println(type); // 1
return new ArrayList<T>(); // 2
}
and is more in the idea of what you want, I think.