Can one avoid argument mismatch in Java when using "? extends" in generic declaration?

user1111929 :

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.)

davidxxx :

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 :

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
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=92265&siteId=1