I'm trying to make a function that processes sorted lists of (comparable) elements. I'm therefore using the generic <T extends List<? extends Comparable>>
, which works will as long as I don't need any list-specific operations that require <? extends Comparable>
as input. But in the code snippet below (simplest example: computing the intersection of two sorted lists) the line C.add((Comparable)(A.get(posA)));
is rejected by the compiler, claiming that add
needs argument ? extends Comparable
, which Comparable
apparently is not.
public static <T extends List<? extends Comparable>> T intersect (T A, T B) {
T C = (T) A.getClass().newInstance();
int posA = 0;
int posB = 0;
while(posA<A.size()&&posB<B.size()) {
if (A.get(posA).compareTo(B.get(posB))>0) posB++;
else if (A.get(posA).compareTo(B.get(posB))<0) posA++;
else if (A.get(posA).equals(B.get(posB))) {
C.add((Comparable)(A.get(posA)));
posA++; posB++;
}
}
return C;
}
How should I tell the compiler that A.get(posA)
is of valid type ? extends Comparable
? Apparently casting doesn't work, and I would like the routine to accept and return lists of arbitrary comparables (Integer, String, custom objects, etc.)
Don't you notice all the unsafe type statements in your code, which multiple unsafe casts ?
You have really many of them. It often means that the overall approach is not the correct one.
In fact things are not so complicated if you learn how generics work in Java.
That could help you :
- https://docs.oracle.com/javase/tutorial/java/generics/upperBounded.html
- https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html
Here are the main things that you should consider according to your actual code : 1) Don't use raw type such as List<? extends Comparable>>
. Comparable
is a generic class.
2) You cannot add anything but null
in a List declared List<? extends Foo>
, that is using a upper bounded wildcard. The last one allows to make the List covariant : accepting Foo and any subclass but with the previous limitation. So you don't want to use that.
3) You can instantiate a generic ArrayList
without the need to declare the generic method type T
for the ArrayList
. Using T
for the Comparable
type will do things really simpler.
4) You want to avoid reflection as much as possible.
By following these ideas you could write a code that could look like :
public static <T extends Comparable<T>> List<T> intersect (List<T> A, List<T> B) {
List<T> list = new ArrayList<>();
int posA = 0;
int posB = 0;
while(posA<A.size()&&posB<B.size()) {
if (A.get(posA).compareTo(B.get(posB))>0) posB++;
else if (A.get(posA).compareTo(B.get(posB))<0) posA++;
else if (A.get(posA).equals(B.get(posB))) {
list.add(A.get(posA));
posA++; posB++;
}
}
return list;
}
That was my original approach, but the problem here is that not every that the intersection of two non-ArrayList Lists will be an ArrayList here.
The type of the List will not be known at compile time if you declare List
for the parameter. So you will unavoidably finish with unsafe casts.
For example :
@SuppressWarnings("unchecked")
public static <T extends Comparable<T>, L extends List<T>> L intersect(L A, L B) {
if (A.getClass() != B.getClass()) {
throw new IllegalArgumentException("not same type between ...");
}
List<T> list = A.getClass()
.newInstance(); // uncheck
int posA = 0;
int posB = 0;
while (posA < A.size() && posB < B.size()) {
if (A.get(posA)
.compareTo(B.get(posB)) > 0)
posB++;
else if (A.get(posA)
.compareTo(B.get(posB)) < 0)
posA++;
else if (A.get(posA)
.equals(B.get(posB))) {
list.add(A.get(posA));
posA++;
posB++;
}
}
return (L) list; // uncheck
}