Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implements a method of a generic interface?

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?

like image 965
user4789408 Avatar asked Mar 28 '18 18:03

user4789408


People also ask

How is generic method implemented in java?

Generic MethodsAll generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( < E > in the next example). Each type parameter section contains one or more type parameters separated by commas.

Can a generic class implement an interface?

We can subtype a generic class or interface by extending or implementing it.

Can you implement interface methods?

Yes, it is mandatory to implement all the methods in a class that implements an interface until and unless that class is declared as an abstract class. Implement every method defined by the interface.

How do you implement an interface and call its methods?

In order to call an interface method from a java program, the program must instantiate the interface implementation program. A method can then be called using the implementation object.


2 Answers

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();}
   }
like image 154
NoDataFound Avatar answered Oct 22 '22 17:10

NoDataFound


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(); });
like image 20
Justin Albano Avatar answered Oct 22 '22 18:10

Justin Albano