Search code examples
javagenericsgeneric-method

Java compiler is not agreed with safety of a generic method call


I'm working on some set of abstractions representing an SQL result set and have written this methods:

public interface Column<T>{
    //some methods
}

public class SqlRowExtractor{

    public void doExtraction(){
        Collection<Column<?>> cols = getColumns(); //Returns the collection of Column<T>.
                                                   //A particular result set may contain, 
                                                   //for instance 2 Column<Date>, 
                                                   //1 Column<Integer> and so fotrh

        for(Column<?> c : cols){
            put(c, get(c)); //1  <-------------------- HERE
                            //Compile-error  
        }
        //other staff
    }

    public <T> void put(Column<T> c, T o){
       //put the extracted value in another place
    }

    public <T> T get(Column<T> c) throws SQLException{
        //extract the value of the row for the Column<T> c
    }
}

I got compile-error at //1, although it's perfectly clear by the signature of put and get methods that there is no way to put inaproppriate value. How can I fix the error in a type-safe way? The error message:

The method put(Column<T>, T) is not applicable for the arguments 
(Column<capture#3-of ?>, capture#4-of ?)

Solution

  • This does not compile because the compiler does not understand that the use of the wild-card type for the get call is the same type as the use of a wild-card type in for the set call, even if the methods are used with the same object.

    You can solve it by introducing a util method with a type parameter. In that way, the wild-card type is used only once, and inside the method the type parameters will have a concrete type, which the compiler can understand that it is the same one that is used in multiple places.

    The concrete type which is assigned to a wild-card type in each separate place where it is used is called the capture of the wild-card type, and it is given a name like capture#3-of ? by the compiler.

    The following compiles:

    private <T> void getPut(Column<T> c) throws SQLException {
        // Compiles because the type's capture, which in non-wildcard, has been bound to T
        put(c, get(c));
    }
    
    public void doExtraction() throws SQLException {
        Collection<Column<?>> cols = getColumns(); 
        for(Column<?> c : cols) {
            // Compiles because wild-card type of c is only used once
            getPut(c);  
        }
    }
    

    The same technique is used in an example of capturing conversion in the JLS.