Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling constructor of generic type?

I am trying to make my own repository for my database to learn, so I am trying something like this:

@Override
public <T extends DatabaseObject> List<T> getList() {
    Cursor cursor = getCursor(somehowGetClassOfT(), null, null); //how to do this?
    //excess code removed, rest of function not relevant to question
    return list;
}

protected <T extends DatabaseObject> Cursor getCursor(Class<T> clazz, String selection, String[] selectionArgs) {
    DatabaseObject databaseObject = instantiateFromT(clazz); //how to do this?
    String tableName = databaseObject.getTableName();
    String[] projection = databaseObject.getProjection();
    String sortOrder = databaseObject.getDefaultOrderColumn() + " " + DEFAULT_SORT_ORDER;
    Cursor cursor = database.query(
            tableName,                            
            projection,               
            selection,                             
            selectionArgs,                            
            null,                                     
            null,                                     
            sortOrder                                 
    );
    return cursor;
}

In other words I have many classes that extend DatabaseObject, and I want to be able to construct a Cursor for them dynamically.

I've defined basic methods in a DatabaseObject such as getting the table name, String array of column names, etc, but since I cannot override static methods via interface (for things like table name), I have to instantiate a blank object so I can get the table name with a getter.

However, I am not sure how to implement:

  1. somehowGetClassOfT(). Whatever T is, I want to pass the class for it into the Cursor function.

  2. instantiateFromT(clazz). Given some class, call the constructor so I can get access to that object's table/projection/sort fields.

Or is all of this possible by using "reflection" that I've been hearing about?

like image 697
user7085962 Avatar asked Oct 29 '16 19:10

user7085962


1 Answers

Generics are not reified at run-time. Since the information is not present, you have two solutions :

  • it can be stored at compile-time by introducing a private field with the class
  • you can use reflection to resolve generic types at runtime from an instance.

Here an example with a private field T and a constructor, by starting from your code :

public abstract class MyRepository<T extends DatabaseObject> {

    private Class<T> type;

    public MyRepository(Class<T> type) {
      this.type = type;
    }

    public <T> List<T> getList() {
      Cursor cursor = getCursor(null, null); // how to do this?
      // excess code removed, rest of function not relevant to question
      return null;
    }

    protected <T extends DatabaseObject> Cursor getCursor(String selection, String[] selectionArgs) {
      DatabaseObject databaseObject = instantiateFromType(); // how to do this?
      String tableName = databaseObject.getTableName();
      String[] projection = databaseObject.getProjection();
      String sortOrder = databaseObject.getDefaultOrderColumn() + " " + DEFAULT_SORT_ORDER;
      Cursor cursor = database.query(
        tableName,
        projection,
        selection,
        selectionArgs,
        null,
        null,
        sortOrder);
      return cursor;
    }

    private DatabaseObject instantiateFromType() {
      try {
          T interfaceType = (T) type.newInstance();
          return interfaceType;
      }
      catch (Exception e) {
          // TODO to handle
       }         
    }
}

Here your DogRepository :

public class DogRepository extends MyRepository<Dog> {

    public DogRepository() {        
      super(Dog.class);
    }

}

with a Dog which implements DatabaseObject:

public class Dog implements DatabaseObject{
   //todo
}

To answer to your comment.

The line DogRepository(Class type), is it necessary to pass in the class as an argument?

Not is is not needed (sorry I bad read your comment)).
And to go further, as you may notice, constructors for concrete repositories are boiler plate code.
If you use reflection to resolve the class behind the generic used in DogRepository, you don't need to define a custom constructor any longer for it.

For example to retrieve the type used in the repository, Spring does the job with its own library using JDK reflection mechanisms with something like that :

Map<TypeVariable, Type> source = GenericTypeResolver.getTypeVariableMap(DogRepository.class);

Spring has its library because it uses much reflection.
But in your case, you can also do the job without using a lib but with your own utility class. Here a sample to resolve the generic type from a repository instance.

DogRepository dogRepository = new DogRepository();
Class<?> typeClass = (Class<?>) ((ParameterizedType)   dogRepository.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
System.out.println("typeClass=" + typeClass );

With reflection to resolve the effective generic type in MyRepository constructor :

public abstract class MyRepository<T extends DatabaseObject> {

    private Class<T> type;

    public MyRepository() {
       type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
   ...
 }

repository concrete classes don't need custom constructor any longer :

public class DogRepository extends MyRepository<Dog> {

}
like image 75
davidxxx Avatar answered Nov 15 '22 11:11

davidxxx