10

I have this interface:

public interface ParsableDTO<T> {
    public <T> T parse(ResultSet rs) throws SQLException;
}

Implemented in some kind of dto classes and this method in another class:

public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, 
                                                          Class<T> dto_class) {
    List<T> rtn_lst = new ArrayList<T>();
    ResultSet rs = doQueryWithReturn(StringQueryComposer
            .createLikeSelectQuery(table, null, null, null, true));

    try {
        while(rs.next()) {
            rtn_lst.add(T.parse(rs)); //WRONG, CAN'T ACCESS TO parse(...) OF ParsableDTO<T>
        }
        rs.close();
    } catch (SQLException e) {
        System.err.println("Can't parse DTO from " 
                + table + " at " + dateformat.format(new Date()));
        System.err.println("\nError on " + e.getClass().getName() 
                + ": " + e.getMessage());
        e.printStackTrace();
    }

    return rtn_lst;
}

How can I access the method parse(ResultSet rs) of the interface that can parse a specific T? Is there a working, different and/or better method to do that?

Navin Gelot
  • 1,264
  • 3
  • 13
  • 32
user4789408
  • 1,196
  • 3
  • 14
  • 31

5 Answers5

9

You are trying to call a non static method on a generic, which is erased when compiled. Even if the method was static, there is no way the compiler would allow that (because T is ParseableDTO in that case, and never the concrete implementation).

Instead, assuming you are in Java 8, I would do:

@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs) throws SQLException;
}

And then:

public <T> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
    try (ResultSet rs = doQueryWithReturn(StringQueryComposer
            .createLikeSelectQuery(table, null, null, null, true))) {
        List<T> rtn_lst = new ArrayList<T>();
        while(rs.next()) {
            rtn_lst.add(mapper.mapRow(rs));
        }
        return rtn_lst;
    } catch (SQLException e) {
        // ...
    }

    return rtn_lst;
}

The interface RowMapper is derived from existing framework, such as JDBC Template.

The idea is to separate concerns: the DTO is not polluted by JDBC related method (eg: mapping or parsing, but I suggest you to avoid the parse name as you are not really parsing a SQL ResultSet here), and you may even leave the mapping in the DAO (lambda make it easier to implements).

Polluting the DTO with JDBC may be problematic because the client/caller will probably not have a valid ResultSet to pass to the parse. Worse: in newer JDK (9++), the ResultSet interface is in the java.sql module which may be unavailable (if you think about a web service, the client does not need JDBC at all).

On a side note, from Java 7 onward, you may use try-with-resource with the ResultSet to automatically close it in a safer way: in your implementation, you are only closing the ResultSet if there was no errors.

If you are stuck with Java 6, you should use the following idiom:

   ResultSet rs = null;
   try {
     rs = ...; // obtain rs
     // do whatever
   } finally {
     if (null != rs) {rs.close();}
   }
NoDataFound
  • 11,381
  • 33
  • 59
1

The inability to call a static method on the generic type T is a side-effect of type erasure. Type erasure means that generic type information is removed--or erased--from Java bytecode after compilation. This process is performed in order to maintain backward compatibility written with code prior to Java 5 (in which generics were introduced). Originally, many of the generic types we use in Java 5 and higher were simple classes. For example, a List was just a normal class that held Object instances and required explicit casting to ensure type-safety:

List myList = new List();
myList.add(new Foo());
Foo foo = (Foo) myList.get(0);

Once generics were introduced in Java 5, many of these classes were upgraded to generic classes. For example, a List now became List<T>, where T is the type of the elements in the list. This allowed the compiler to perform static (compile-time) type checking and removed the need to perform explicit casting. For example, the above snippet is reduced to the following using generics:

List<Foo> myList = new List<Foo>();
myList.add(new Foo());
Foo foo = myList.get(0);

There are two major benefits to this generic approach: (1) tedious and unruly casting is removed and (2) the compiler can ensure at compile-time that we do not mix types or perform unsafe operations. For example, the following would be illegal and would cause an error during compilation:

List<Foo> myList = new List<Foo>();
myList.add(new Bar());  // Illegal: cannot use Bar where Foo is expected

Although generics help a great deal with type safety, their inclusion into Java risked breaking existing code. For example, it should still be valid to create a List object without any generic type information (this is called using it as a raw type). Therefore, compiled generic Java code must still be equivalent to non-generic code. Stated another way, the introduction of generics should not affect the bytecode generated by the compiler since this would break existing, non-generic code.

Thus, the decision was made to only deal with generics at and before compile time. This means that the compiler uses generic type information to ensure type safety, but once the Java source code is compiled, this generic type information is removed. This can be verified if we look at the generated bytecode of the method in your question. For example, suppose we put that method in a class called Parser and simplify that method to the following:

public class Parser {

    public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Class<T> clazz) {
        T dto = null;
        List<T> list = new ArrayList<>();
        list.add(dto);
        return list;
    }
}

If we compile this class and inspect its bytecode using javap -c Parser.class, we see the following:

Compiled from "Parser.java"
public class var.Parser {
  public var.Parser();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public <T extends var.ParsableDTO<T>> java.util.List<T> getParsableDTOs(java.lang.String, java.lang.Class<T>);
    Code:
       0: aconst_null
       1: astore_3
       2: new           #18                 // class java/util/ArrayList
       5: dup
       6: invokespecial #20                 // Method java/util/ArrayList."<init>":()V
       9: astore        4
      11: aload         4
      13: aload_3
      14: invokeinterface #21,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      19: pop
      20: aload         4
      22: areturn
}

The line 14: invokeinterface #21, 2 denotes that we have called add on List using an Object argument even though the actual type of the argument in our source code is T. Since generics cannot affect the bytecode generated by the compiler, the compiler replaces generic types with Object (this makes the generic type T non-reifiable) and then, if needed, performs a cast back to the expected type of the object. For example, if we compile the following:

public class Parser {

    public void doSomething() {
        List<Foo> foos = new ArrayList<>();
        foos.add(new Foo());
        Foo myFoo = foos.get(0);
    }
}

we get the following bytecode:

public class var.Parser {
  public var.Parser();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public void doSomething();
    Code:
       0: new           #15                 // class java/util/ArrayList
       3: dup
       4: invokespecial #17                 // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: new           #18                 // class var/Foo
      12: dup
      13: invokespecial #20                 // Method Foo."<init>":()V
      16: invokeinterface #21,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      21: pop
      22: aload_1
      23: iconst_0
      24: invokeinterface #27,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      29: checkcast     #18                 // class Foo
      32: astore_2
      33: return
}

The line 29: checkcast #18 shows that the compiler added an instruction to check that the Object that we received from the List (using get(0)) can be cast to Foo. In other words, that the Object we received from the List is actually a Foo at runtime.

So how does this factor into your question? Making a call such as T.parse(rs) is invalid in Java because the compiler has no way of knowing at runtime on what class to call the static method parse since the generic type information is lost at runtime. This also restricts us from creating objects of type T (i.e. new T();) as well.

This conundrum is so common that it is actually found in the Java libraries themselves. For example, every Collection object has two methods to convert a Collection into an array: Object[] toArray() and <T> T[] toArray(T[] a). The latter allows the client to supply an array of the expected type. This provides the Collection with enough type information at runtime to create and return an array of the expected (same) type T. For example, if we look at the JDK 9 source code for AbstractCollection

public <T> T[] toArray(T[] a) {
    // ...
    T[] r = a.length >= size ? a :
              (T[])java.lang.reflect.Array
              .newInstance(a.getClass().getComponentType(), size);
    // ...
}

we see that the method is able to create a new array of type T using reflection, but this requires using the object a. In essence, a is supplied so that the method can ascertain the actual type of T at runtime (the object a is asked, "What type are you?"). If we cannot provide a T[] argument, the Object[] toArray() method must be used, which is only able to create an Object[] (again from the AbstractCollection source code):

public Object[] toArray() {
    Object[] r = new Object[size()];
    // ...
}

The solution used by toArray(T[]) is a plausible one for your situation, but there are some very important differences that make it a poor solution. Using reflection is acceptable in the toArray(T[]) case because the creation of an array is a standardized process in Java (since arrays are not user-defined classes, but rather, standardized classes, much like String). Therefore, the construction process (such as which arguments to supply) is known a priori and standardized. In the case of calling a static method on a type, we do not know that the static method will, in fact, be present for the supplied type (i.e. there is no equivalent of an implementing an interface to ensure a method is present for static methods).

Instead, the most common convention is to supply a function that can be used to map the requested argument (ResultSet in this case) to a T object. For example, the signature for your getParsableDTOs method would become:

public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Function<ResultSet, T> mapper) {
    /* ... */
}

The mapper argument is simply a Function<ResultSet, T>, which means that it consumes a ResultSet and produces a T. This is the most generalized manner, since any Function that accepts ResultSet objects and produces T objects can be used. We could also create a specific interface for this purpose as well:

@FunctionalInterface
public interface RowMapper<T> {
    public T mapRow(ResultSet rs);
}

and change the method signature to the following:

public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
    /* ... */
}

Thus, taking your code and replacing the illegal call (the static call to T) with the mapper function, we end up with:

public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
    List<T> rtn_lst = new ArrayList<T>();
    ResultSet rs = doQueryWithReturn(StringQueryComposer
            .createLikeSelectQuery(table, null, null, null, true));

    try {
        while(rs.next()) {
            rtn_lst.add(mapper.mapRow(rs)); // <--- Map value using our mapper function
        }
        rs.close();
    } catch (SQLException e) {
        System.err.println("Can't parse DTO from " 
                + table + " at " + dateformat.format(new Date()));
        System.err.println("\nError on " + e.getClass().getName() 
                + ": " + e.getMessage());
        e.printStackTrace();
    }

    return rtn_lst;
}

Additionally, because we used a @FunctionalInterface as a parameter to getParsableDTOs, we can use a lambda function to map the ResultSet to a T, as in:

Parser parser = new Parser();
parser.getParsableDTOs("FOO_TABLE", rs -> { return new Foo(); });
Justin Albano
  • 3,809
  • 2
  • 24
  • 51
0

Remove <T> from the parse() method. It's hiding the T declared by the interface.

Dean Xu
  • 4,438
  • 1
  • 17
  • 44
  • 1
    Do not resolve the problem, for some reason the IDE tell me that can't reach the method in the ParsableDTO in T in the while. Because for use the method the T must be instantiate, but i can't instantiete the object, i want that the method in the interface create one object of the same type... and if i do that method static, i can't call the method of "ParsableDTO" in the generic type, i must call it from "ParsableDTO", wrong again because isn't the method specific of T but is the method in the interface that do nothing (PS: the is here because i try to use that method in static way) – user4789408 Mar 30 '18 at 17:16
  • There is no need to declare a new functional interface for this. You can use ```Function``` in `java.util.function.*` – Valentin Ruano Apr 01 '18 at 04:40
  • @user4789408 what you are trying to do here... get the factory/constructor method declared in the interface simply does not work in Java or is not the Java way. Perhaps it does in other languages but not in this one. The solution proposed in this reply is about the best or java best-practices to address this situation. – Valentin Ruano Apr 01 '18 at 04:45
0

As it stands, parse() is an instance method on the ParsableDTO so you will need an instance of type T (e.g. of the dto_class) to access the method. For example:

T t = dto_class.newInstance();
rtn_lst.add(t.parse(rs));

I think it is right as an instance method too - you wouldn't be able to call different versions of the method on the subclasses of ParsableDTO if they were static.


Also, possibly as an aside, this looks curious: <T extends ParsableDTO<T>>.

This is suggesting that parse() will be returning instances that extend ParsableDTO. If that's not intentional, it may be best to have two generic types there:

public <T, P extends ParsableDTO<T>> List<T> getParsableDTOs(String table,
        Class<P> dto_class) {
    ...
    P p = dto_class.newInstance();
    rtn_lst.add(p.parse(rs));

And agree with the earlier comments about there being two <T> declarations on the interface and its method. It compiles fine, but hints that the type returned by parse() could be different from the T declared in ParsableDTO<T>.

df778899
  • 10,703
  • 1
  • 24
  • 36
0

You just need to change the method signature of getParsableDTOs to use ParsableDTO<T> instead of Class<T>. Inside your while loop do

rtn_lst.add(dto_class.parse(rs));

nitnamby
  • 404
  • 2
  • 8