Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoke method whose parameter is bounded by an intersection type

Tags:

java

generics

In the context of static methods, i'd like to narrow a type reference and invoke a more specific method for an object like this :

public static <T, L extends List<? extends T> & RandomAccess> void do(L list) {
    // Do some stuff
}

public static <T> void do(Iterable<? extends T> iterable) {
    if(iterable instanceof List && iterable instanceof RandomAccess)
        // invoke do(List&RandomAccess) method
    else
        // do something else
}


So i'd like to know if there's a syntax allowing to call do(Iterable) rather that using some hack like this one :

private static <L extends List<? extends T> & Comparable> L cast(Iterable<? extends T> iterable) {
    return (L) iterable;
}

public static <T> void do(Iterable<? extends T> iterable) {
    if(iterable instanceof List && iterable instanceof RandomAccess)
        do(cast(iterable));
    else
        // do something else


NOTE : I know it isn't possible to cast my iterable this way

do((List<? extends T> & RandomAccess) iterable);

and it seams to me that the erasure of L in

L extends List<? extends T> & Comparable

is

List<? extends T>


So why can't i invoke the method this way ?

do((List<? extends T>) iterable); // Which results in invoking do(Iterable<? extends T)
like image 606
Antoine Marques Avatar asked Jul 23 '13 13:07

Antoine Marques


3 Answers

Your assumption is correct: The erasure of intersections is the first type - ie List (for purposes of reflection etc).

You can get your code to compile without the "hack" by providing the intersection type to which to cast as a method type:

public static <T, L extends List<? extends T> & RandomAccess> void method(L list) {
    // do whatever
}

@SuppressWarnings("unchecked") // needed to suppress unsafe cast warning 
public static <T, L extends List<? extends T> & RandomAccess> void method(Iterable<? extends T> iterable) {
    if(iterable instanceof List && iterable instanceof RandomAccess)
        method((L)iterable); // calls the other method
    else
        return; // do whatever
}

This code compiles and the second method calls the first method, as desired.

There is no way to cast to an intersection without this technique.


Note that your code does not compile because do is a java keyword and thus is not a valid method name. I used method() instead to get a compilable example.

Also I think you meant RandomAccess where you coded Comparable.

like image 192
Bohemian Avatar answered Oct 31 '22 18:10

Bohemian


AFAIK the erasure of L extends List<? extends T> & RandomAccess would be List, but even if I'm wrong, you couldn't use (List<? extends T>) iterable since that would not meet the requirements (a List is not necessarily RandomAccess). Thus, the only method that would match would be the Iterable version.

Edit: a quick reflection printout confirms my assumption. I named the methods method (creative, isn't it?) and get the following output:

... method(java.util.List)
... method(java.lang.Iterable)

You might now think that casting to List would be sufficient (and it might be, if you'd disable generics or call the method via reflection), but the compiler doesn't suffer from type erasure and thus knows that only method(java.lang.Iterable) matches.

like image 28
Thomas Avatar answered Oct 31 '22 17:10

Thomas


I don't think there's a way to invoke erased version of a static method (in a clean build)

public static <L extends List<?> & RandomAccess> void do1(L list) {}

private static <L extends List<?> & RandomAccess> L cast(Iterable<?> iterable) 
{
    return (L) iterable;
}

public static void do2(Iterable<?> iterable)
{
    do1(cast(iterable));

    // try to invoke the erased version
    //do1((List)iterable);   // does not compile
}

We can invoke erased version of instance methods through raw type

static class Helper<T>
{
    <T, L extends List<? extends T> & RandomAccess> void do3(L list)
    {
        do1(list);
    }
}

public static void do2(Iterable<?> iterable)
{
    Helper helper = new Helper(); // raw type
    helper.do3((List) iterable);  // erased method
}

Back to static methods. Suppose we have this pre-Java5 code

#1 static void foo(List list){}

#2 foo(new ArrayList());

now after Java5, #1 is generified as

#1 static void foo(List<?> list){}

but #2 is kept as is, with raw type. How come #2 still compiles? The spec (15.12.2.2) makes it possible by allowing an unchecked conversion. The unchecked conversion can convert a raw Foo to Foo<..> (but no more sophistication like Foo -> Foo<..>&Bar).

The spec only contains enough tricks to make sure legacy code can survive typical generification. Your case is certainly not typical, and the tricks don't apply.

like image 26
ZhongYu Avatar answered Oct 31 '22 18:10

ZhongYu