Issue using Generics and Parameterized List - java

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.

Related

If B extends A, one cannot cast List<A> to List<B> (makes sense), but then why can one cast List<? extends A> to List<B>?

If a class B extends class A, one cannot cast List<A> to List<B> (cast 1 below). This makes sense, because if it were allowed, then one could read a B from the list even if it contained none.
However, in the code below, cast 2 from List<? extends A> to List<B> causes a warning. Shouldn't it generate an error for the same reason as before? In the code, the list only contains one object that is an A but not a B, and yet it is in a list deemed List<B>.
package test;
import java.util.LinkedList;
import java.util.List;
public class TypeLowerBoundCasting {
static class A {}
static class B extends A {}
static void method1() {
List<A> listOfAs = new LinkedList<A>();
listOfAs.add(new A());
// List<B> listOfAsAsListOfBs = (List<B>) listOfAs ; // cast 1: compiler error
// B b = listOfAsAsListOfBs.get(0); // that would be bad
}
static void method2() {
LinkedList<A> listOfAs = new LinkedList<A>();
listOfAs.add(new A());
List<? extends A> listOfAExtensions = listOfAs;
List<B> listOfAsAsListOfBs = (List<B>) listOfAExtensions; // cast 2: warning, but compiles
B b = listOfAsAsListOfBs.get(0); // that IS bad; causes a run-time error.
}
public static void main(String[] args) {
method2();
}
}
A List<? extends A> might be a List<B>, so you can cast:
List<? extends A> list = new ArrayList<B>(); // lose type information
List<B> result = (List<B>) list; // regain type information
Similar to how you can do:
Object o = "Hello World!"; // lose type information
String s = (String) o; // regain type information
Unfortunately, the first cast is unchecked. But a cast in that place is still a valid option, as you can see.
But a List<A> can never actually be a List<B> (unless you abuse raw types), because List<A> is not assignable from a List<B> (i.e. List<B> does not 'extend' List<A>) so you can't cast:
List<A> list = new ArrayList<B>(); // Only works with: (List<A>) (List) new ArrayList<B>();

Java add list of specific class to list of java.lang.Object works with java 8 streams - why?

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;
}

Method to take `T extends Iterable` and return T<E>?

How do I write a method that takes a parameter of some type T which is an instance of Iterable, as well as a parameter of Class<E>, and return T<E>?
public static <...> ... checkedCast(T iterable, Class<E> clazz) {
// Check elements and throw ClassCastException if invalid
#SupressWarning("checked")
... cast = (...)iterable;
return cast;
}
I want to use it like this:
// This should compile
ArrayList<?> a = ...;
ArrayList<String> b = checkedCast(a, String.class);
// So should this
HashSet<Number> c = ...;
Set<Integer> d = checkedCast(c, Integer.class);
// This shouldn't compile
b = checkedCast(a, Integer.class);
// This shouldn't compile
b = checkedCast(c, Integer.class);
// This should throw ClassCastException
checkedCast(a, Integer.class);
I know I can do this using overrides, but this requires me to write an override for every type:
public static <T> Iterable<T> checkedCast(Iterable<?> iterable, Class<T> clazz) {...}
public static <T> List<T> checkedCast(List<?> list, Class<T> clazz) {...}
public static <T> ArrayList<T> checkedCast(ArrayList<?> list, Class<T> clazz) {...}
public static <T> Set<T> checkedCast(Set<?> set, Class<T> clazz) {...}
One of the weaknesses of the Java type system's Generics extension is that how we think about types in the singular doesn't scale to how we think of types in the plural.
In short, Collections of a generic type cannot be safely cast, ever. Build a new list, pull out each type and check it individually, and the return the new list. If you disregard this warning, I'll direct someone to do something like
List<Customer> customers = new ArrayList<>();
customers.add(new Customer(...));
List<Object> customerObjects = checkCast(customers, Object.class);
customerObjects.add(new Order(...));
You have been warned.
See if this works for you. But, people can help you better if you can describe in more detail why you need such a method.
public static
<InputElement, OutputElement extends InputElement,
InputContainer extends Iterable<InputElement>,
OutputContainer extends Iterable<OutputElement>>
OutputContainer checkedCast(InputContainer iterable,
Class<OutputElement> clazz) {
#SuppressWarnings("unchecked")
OutputContainer output = (OutputContainer) iterable;
return output;
}
This works/matches your requirements - except for throwing a ClassCastException (if you really want that behaviour, you can include it in the checkedCast method yourself):
import java.util.*;
public class CheckedCast {
public static <GenB, GenA extends GenB, CollA extends List<GenA>> List<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
return (List<GenB>)iterable;
}
public static <GenB, GenA extends GenB, CollA extends Set<GenA>> Set<GenB> checkedCast(CollA iterable, Class<GenB> clazz){
return (Set<GenB>)iterable;
}
static class One {}
static class Two extends One {}
static class Three {}
public static void main(String[] args) {
ArrayList<Two> test1 = new ArrayList<Two>();
List<One> test2 = checkedCast(test1, One.class);
// Shouldn't compile...
ArrayList<One> aa = checkedCast(test2, One.class); // output is ArrayList
List<Two> bb = checkedCast(test2, Three.class); // Three is not superClass of Two
ArrayList cc = checkedCast(new HashSet(), Integer.class); // Set cannot become List
ArrayList<One> dd = checkedCast(new LinkedList<One>(), One.class); // ArrayList is not superClass of List
}
}
Updated to match new requirement: ArrayList xs = checkedCast(new HashSet(), Integer.class) - shouldn't compile
Update: updated to assert returned Collection generic type extends input Collection's generic type.

Giving List the same type as the class which contains it

Let's assume I have a class A that can be extended. Within that Class A I have a List List<A>. So this class will contain a list with elements A. Now If I subclass this class B extends A, I want class B to have the same member List<B>, ie the same list but this type containing items of type B. Is this possible using generics ? I can see something like A <T extends A>, while declaring List<T>, but I don't like as the information about the class type are already there. Is there another better solution ? Example below:
public class A {
List<A> list = new ArrayList<A>();
}
public class B extends A {
}
I want list to have the generic type of B in class B.
If you want to put the behaviour in the super class, then you're going to have to tell the super class what type of class the subclass is. This can be done by adding a generic type to the super.
public class A<E> {
protected List<E> items;
public A() {
this.items = new ArrayList<E>();
}
}
public class B extends A<B> {
public static void main(String[] args) {
B b = new B();
b.items.add(b);
}
}
You can use extends keyword in generic.
For example:
public class A {
protected List<? extends A> list;
public A() {
list = new ArrayList<A>();
}
public <T extends A> List<T> getList() {
return (List<T>) list;
}
public void setList(List<A> list) {
this.list = list;
}
}
public class B extends A {
public B() {
list = new ArrayList<B>();
}
public static void main(String[] args) {
A a = new A();
a.getList().add(new A());
B b = new B();
b.getList().add(new B());
}
}

Generics: how to enforce restrictions between keys and values in a Map

The problem: I've a Function Object interface defined in a class:
public static interface FunctionObject<T> {
void process(T object);
}
I need it generic because I'd like to use T methods in the process implementations.
Then, in other generic class, I've a Map where I have classes as keys and function objects as values:
Map<Class<T>, FunctionObject<T>> map;
But I also want the map to accept subtype classes and function objects of supertypes OF THE KEY TYPE, so I did this:
Map<Class<? extends T>, FunctionObject<? super T>> map; //not what I need
The basic idea is to be able to use the map as follows:
//if T were Number, this should be legal
map.put(Class<Integer>, new FunctionObject<Integer>(){...});
map.put(Class<Float>, new FunctionObject<Number>(){...});
map.put(Class<Double>, new FunctionObject<Object>(){...});
As I want to enforce the FunctionObject has the type of the class key or a supertype, what I really would like to define is this:
Map<Class<E extends T>, FunctionObject<? super E>>> map;
How can I achieve the desired effect? Is a typesafe heterogenous container the only option? What would the Map generic types look like to allow populating it from a reference?
Parametrized container, seems to work just fine:
public class MyMap<T>
{
interface FunctionObject<X> {}
private Map<Class<? extends T>, FunctionObject<Object>> map = new HashMap<>();
#SuppressWarnings("unchecked")
public <E extends T> void put(Class<E> c, FunctionObject<? super E> f)
{
map.put(c, (FunctionObject<Object>) f);
}
public <E extends T> FunctionObject<Object> get(Class<E> c)
{
return map.get(c);
}
public static void Main(String[] args)
{
MyMap<Number> map = new MyMap<>();
map.put(Integer.class, new FunctionObject<Integer>() {});
map.put(Float.class, new FunctionObject<Number>() {});
map.put(Double.class, new FunctionObject<Object>() {});
}
}
Edited to comply to the question. Sadly there is no way to avoid the downcasting to object.
Edit added get().
You can do this with encapsulation, assuming you only use the map through the method which check this on a per entry basis.
The following add method avoids the need to double up on the type as well.
public class Main {
interface FunctionObject<T> { }
private final Map<Class, FunctionObject> map = new LinkedHashMap<Class, FunctionObject>();
public <T> void add(FunctionObject<T> functionObject) {
Class<T> tClass = null;
for (Type iType : functionObject.getClass().getGenericInterfaces()) {
ParameterizedType pt = (ParameterizedType) iType;
if (!pt.getRawType().equals(FunctionObject.class)) continue;
Type t = pt.getActualTypeArguments()[0];
tClass = (Class<T>) t;
break;
}
map.put(tClass, functionObject);
}
public <T> void put(Class<T> tClass, FunctionObject<T> functionObject) {
map.put(tClass, functionObject);
}
public <T> FunctionObject<T> get(Class<T> tClass) {
return map.get(tClass);
}
public static void main(String... args) throws IOException {
Main m = new Main();
m.add(new FunctionObject<Integer>() {
});
FunctionObject<Integer> foi = m.get(Integer.class);
System.out.println(foi.getClass().getGenericInterfaces()[0]);
}
}
prints
Main.Main$FunctionObject<java.lang.Integer>
You can use #SuppressWarnings("unchecked") if you want to disable the warning.
The point is; there is no way to describe the constraint you have in the field declaration, you can achieve the same result if you use accessor methods which do the check on a per entry basis. You can add runtime checks as well if you need to ensure raw types are correct.

Categories