2

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 ?)
St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • Where does your for loop appear? (i.e. in which class and which method) – Eran Oct 31 '15 at 10:29
  • @Eran In a method of a class. The class has all the methods listed here. But how does it matter where the loops actually appears? – St.Antario Oct 31 '15 at 10:30
  • @Eran Fixed, couldn't you look at the update question? – St.Antario Oct 31 '15 at 10:32
  • Two `?`s are not equal to each other (hence `capture#3` vs `capture#4`). Overall, your approach is not going to work well because of type erasure. The `T` in `get` and `put` is gone by the compile time, so you need some other way of passing the type info to these methods. – Sergey Kalinichenko Oct 31 '15 at 10:33
  • @dasblinkenlight Understood, so probably creating a helper method `private static void doLoop(Column c){ //THE LOOP }` capturing the wildcard is the only idea here, right? – St.Antario Oct 31 '15 at 10:37
  • @St.Antario Unfortunately, it's worse than that: mixed bags of generics in Java are pretty much useless. You can make your `Column` carry the type information, for example, by providing and storing `Class` in the constructor, but once you make a collection of `Column`s with different types, the compile-time type info is lost for each element of the collection. – Sergey Kalinichenko Oct 31 '15 at 10:47
  • `put()` requires type `T`, however `?` may or may not be compatible with `T`. Suppose, using this code from another code resolves `T` = `String`, but as you said, `getColumns()` can even return `Date` type columns, so, `put()` cannot accept just any type. – S.D. Oct 31 '15 at 11:20

3 Answers3

1

I am not sure exactly what your trying to do but to me if you want to make everything type safe then you need to pass in the type of column. To make sure they are all using the same type.

interface Column<T>{
    //some methods
}

class SqlRowExtractor{

    public <T> Collection<Column<T>> getColumns(Class<T> clss) {
        return null;
    }
    public <T> void doExtraction(Class<T> clss) throws SQLException{
        // Type T needs to be specified before this so compiler 
        // can check it.
        Collection<Column<T>> cols = getColumns(clss); 
        for(Column<T> c : cols){
            put(c, get(c)); 
        }
        //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
        return null;
    }
}
WillShackleford
  • 6,918
  • 2
  • 17
  • 33
1

you have to redesign your code because in that form it cannot compile. Your code is not type safe for the compiler. You can write it in the following form

interface Column<T>{

    /**
     * extracts valu from the column
     * 
     * @return the value
     */
    T value() throws SQLException;
}

public class test{

    public void doExtraction() throws SQLException {
        Collection<Column<?>> cols = getColumns(); //Returns the collection of Column<T>.
        for(Column c : cols){
            put(c, c.value());
        }
    }

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

}

this code works and each column is responsible for the value extraction

burovmarley
  • 644
  • 3
  • 8
1

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.

Lii
  • 11,553
  • 8
  • 64
  • 88