Why does the following
public class ListBox {
private Random random = new Random();
private List<? extends Collection<Object>> box;
public ListBox() {
box = new ArrayList<>();
}
public void addTwoForks() {
int sizeOne = random.nextInt(1000);
int sizeTwo = random.nextInt(1000);
ArrayList<Object> one = new ArrayList<>(sizeOne);
ArrayList<Object> two = new ArrayList<>(sizeTwo);
box.add(one);
box.add(two);
}
public static void main(String[] args) {
new ListBox().addTwoForks();
}
}
Not work? Just toying around with generics for the purpose of learning and I expected that I would be able to insert anything that extends Collection in there but I get this error:
The method add(capture#2-of ? extends Collection<Object>) in the type List<capture#2-of ? extends Collection<Object>> is not applicable for the arguments (ArrayList<Object>)
The method add(capture#3-of ? extends Collection<Object>) in the type List<capture#3-of ? extends Collection<Object>> is not applicable for the arguments (ArrayList<Object>)
at ListBox.addTwoForks(ListBox.java:23)
at ListBox.main(ListBox.java:28)
You've declared box to be a List of something that extends Collection of Object. But according to the Java compiler, it could be anything that extends Collection, i.e. List<Vector<Object>>. So it must disallow add operations that take the generic type parameter for this reason. It can't let you add an ArrayList<Object> to a List that could be List<Vector<Object>>.
Try removing the wildcard:
private List<Collection<Object>> box;
This should work because you can certainly add an ArrayList<Object> to a List of Collection<Object>.
Related
I'm reading about generic types. I'm able to understand that generic types can be used to specify the type of content can be consumed by objects or collections to avoid runtime error and solve it while compile time only.
I want to know how can I create a generic class whose type variable(T = List) is List. For example in below example its String.
import java.util.ArrayList;
import java.util.List;
public class Test<T> {
T string;
public Test(T in) {
this.string = in;
}
public static void main(String args[]) {
Test<String> o = new Test<String>("Generic");
}
}
I want to know how can I create a generic class whose type variable(T
= List) is List.
Specify the List as parameterized type.
To have a generic Test of List<String> do for example :
List<String> myList = new ArrayList<>();
...
Test<List<String>> o = new Test<>(myList);
Just déclare your T like an extends of List
Change this:
public class Test<T> {
to this
public class Test<T extends List> {
In this case, you will not be able to write this:
Test<String> o = new Test<String>();
If you want your list strictly to be a list of strings. Then simply declare your list as List<String>
in this way obliges the developer to use a class that belongs to the Collection es (List, ArrayList, Vector, ...)
public class Test<T extends Collection>{ ... }
UPDATE
public class Test<E,T extends Collection<E>>{ ... }
public class Test {
static List<Object> listA = new ArrayList<>();
public static void main(final String[] args) {
final List<TestClass> listB = new ArrayList<>();
listB.add(new TestClass());
// not working
setListA(listB);
// working
setListA(listB.stream().collect(Collectors.toList()));
System.out.println();
}
private static void setListA(final List<Object> list) {
listA = list;
}
}
why does it work with streams and does not work for the simple set?
For the first case, it fails because List<TestClass> is not a subtype of List<Object>.1
For the second case, we have the following method declarations:
interface Stream<T> {
// ...
<R, A> R collect(Collector<? super T, A, R> collector)
}
and:
class Collectors {
// ...
public static <T> Collector<T, ?, List<T>> toList()
}
This allows Java to infer the generic type parameters from the context.2 In this case List<Object> is inferred for R, and Object for T.
Thus your code is equivalent to this:
Collector<Object, ?, List<Object>> tmpCollector = Collectors.toList();
List<Object> tmpList = listB.stream().collect(tmpCollector);
setListA(tmpList);
1. See e.g. here.
2. See e.g. here or here.
This line
setListA(listB);
doesn't work because List in Java is invariant, meaning List<TestClass> doesn't extends List<Object> when TestClass extends Object. More details here
This line
setListA(listB.stream().collect(Collectors.toList()));
works because Java infer Object for Collector's generic type from this method signature setListA(final List<Object> list) and so you actually pass List<Object> there
the type parameters of Java Generic is invariance which means it can't be inherited as type parameters class hierarchy. The common parent of List<TestClass> and List<Object> is List<?>.
you can see detailed answer about java generic wildcard from kotlin & java. for example:
List<String> strings = new ArrayList<String>();
List<CharSequence> sequences = strings; // can't work
List<? extends CharSequence> parent1 = strings; // works fine
List<?> parent2 = strings; // works fine
// ^--- is equaivlent to List<? extends Object>
the streams approach is transform a List<TestClass> to List<Object>. if you want it works without transform a List to another List by stream. your methods signature should be as below, and the Collection#addAll also does it in java:
List<?> listA = new ArrayList<>();
private static void setListA(List<?> list) {
listA = list;
}
In my Android Studio project I have simple structure of classes:
public class P{}
public class A extends P{}
public class B extends P{}
And in another class I have a List:
private List<? extends P> data;
private List<A> listA;
private List<B> listB;
But when I try to do that:
data = listA; //it's ok
data.addAll(listB); //it calls error
The second line is red in Android Studio and error is:
addAll(java.util.Collection<capture<? extends com.mydomain.P>>)
in List cannot be applied to (java.util.List<com.mydomain.subclass.B>)
How can I solve this problem?
When you declare private List<? extends P> data; You require a specific type that extends p for the content of your list, which is not necessary. You can simply use:
private List<P> data;
As any class that extends P (B and A alike) will be accepted.
(That prevent you from assigning data = listA, though)
When you use a parameterized type like List, the compiler does some type binding and type checking at compilation time.
So with a variable declared as
List<A> listA;
any use of listA will be with the type argument A.
With a variable declared as
List<?> data;
any use of data will be with the type argument ? which is the wildcard, but the actual type is unknown.
Given that it doesn't know the actual type, the compiler can't let you make use of it. The add(E) method of List depends on the type variable. So
List<A> listA = ...;
listA.add(someA);
would be fine since someA is of type A.
Now you may think
List<?> data = listA;
data.add(someA); // theoretically fine
should word, but like this
List<?> data = someMethod();
data.add(someA);
it doesn't. What if the referenced List isn't meant to hold A objects?
The compiler simply can't allow this. That is what type-checking all about with generics.
Try this one if you are using ArrayList only.
public class P {}
public class A extends P {}
public class B extends P {}
public class MyList<T extends P> extends ArrayList<P> {
private static final long serialVersionUID = 1L;
...
}
private static MyList<? extends P> data;
private static MyList<A> listA;
private static MyList<B> listB;
public static void main(String[] args) throws IOException {
data = listA; // it's ok
data.addAll(listB); // it's ok
}
I created the following Test class:
public class GenericTest {
public static class A implements Serializable {
}
public static class B implements Serializable {
}
public static void main(String[] args) {
// Error: Type mismatch: cannot convert from List<capture#1-of ? extends Serializable> to List<GenericTest.A>
//List<A> aList = getInfo().get("A");
//List<B> BList = getInfo().get("B");
// Warning: Type safety: Unchecked cast from List<capture#1-of ? extends Serializable> to List<GenericTest.A>
List<A> aList = (List<A>)getInfo().get("A");
List<B> BList = (List<B>)getInfo().get("B");
}
public static Map<String, List<? extends Serializable>> getInfo() {
Map<String, List<? extends Serializable>> infoMap = new HashMap<String, List<? extends Serializable>>();
List<A> aList = new ArrayList<A>();
List<B> bList = new ArrayList<B>();
try {
aList.add(new A());
infoMap.put("A", aList);
bList.add(new B());
infoMap.put("B", bList);
}
catch(Exception e) {
e.printStackTrace();
}
return infoMap;
}
}
Is there a better way to go about this to avoid casting and suppressing the unchecked warning? I have been told using casting almost defeats the purpose of using Generics in the first place. Is there a problem with this, or a "safer" way to go about doing it?
you can try doing this. You still need to suppress warnings in the getList method, but if you only add lists using the addToMap method ,the compiler can correctly check if the added list is the same type of class that was used in the first parameter. Also google for super type tokens.
public static void main(String[] args) {
List<A> aList = new ArrayList<A>();
aList.add(new A());
List<B> bList = new ArrayList<B>();
bList.add(new B());
addToMap(A.class,aList);
addToMap(B.class,bList);
List<A> aListFromMap = getList(A.class);
List<B> bListFromMap = getList(B.class);
}
private static Map<Class<?>,Object> infoMap = new HashMap<Class<?>,Object>();
public static <T extends Serializable> void addToMap(Class<T> key, List<T> value) {
infoMap.put(key,value);
}
public static <T extends Serializable> List<T> getList(Class<T> key) {
return (List<T>)(infoMap.get(key));
}
No, there is no way to help this situation. You have a single map with two kinds of values (this is called a heterogeneous map) and the type system cannot express that. You must downcast without type safety. Either that, or completely redesign to keep these two kinds of objects in two separate structures.
package pkg_2;
import java.util.*;
class shape{}
class Rect extends shape{}
class circle extends shape{}
class ShadeRect extends Rect{}
public class OnTheRun {
public static void main(String[] args) throws Throwable {
ShadeRect sr = new ShadeRect();
List<? extends shape> list = new LinkedList<ShadeRect>();
list.add(0,sr);
}
}
You cannot add anything to a List<? extends X>.
The add cannot be allowed because you do not know the component type. Consider the following case:
List<? extends Number> a = new LinkedList<Integer>();
a.add(1); // in this case it would be okay
a = new LinkedList<Double>();
a.add(1); // in this case it would not be okay
For List<? extends X> you can only get out objects, but not add them.
Conversely, for a List<? super X> you can only add objects, but not get them out (you can get them, but only as Object, not as X).
This restriction fixes the following problems with arrays (where you are allowed these "unsafe" assigns):
Number[] a = new Integer[1];
a[0] = 1; // okay
a = new Double[1];
a[0] = 1; // runtime error
As for your program, you probably just want to say List<shape>. You can put all subclasses of shape into that list.
ShadeRect sr = new ShadeRect();
List<shape> list = new LinkedList<shape>();
list.add(0,sr);