Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing nested Class<MyInterface<T>> as a parameter in Android

I am trying to create a wrapper over Retrofit to abstract my service implementation. I have gotten the compiler to compile successfully so far:

package com.example.spark.testapp.services;

import com.example.spark.testapp.services.apis.Get;
import com.example.spark.testapp.services.apis.Post;
import com.example.spark.testapp.services.utils.*;
import com.example.spark.testapp.services.utils.Error;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;




public class ServiceLayer {
    public <T> void performGet(String url, final Class<Get<T>> clazz, com.example.spark.testapp.services.utils.Callback<T> callback) {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
        Get<T> service = retrofit.create(clazz);
        //Pass authentication token here
        Call<T> t = service.get(url, "");
        executeCallback(callback,t);
    }

    public <T> void performPost(String url, final Class<Post<T>> clazz,com.example.spark.testapp.services.utils.Callback<T> callback) {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
        Post<T> service = retrofit.create(clazz);

        //Pass authentication token here
        Call<T> t = service.post(url, "");
        executeCallback(callback,t);
    }

    public <T> void executeCallback( final com.example.spark.testapp.services.utils.Callback<T> callback , Call<T> call) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                callback.onSuccess(response.body());
            }


            @Override
            public void onFailure(Call<T> call, Throwable t) {
                ///Find out what exactly went wrong. Populate Error. and then...
                com.example.spark.testapp.services.utils.Error e = new Error();
                callback.onFailure(e);
            }
        });
    }
}

While this compiles, the problem is at the point of calling the method:

private void getString() {

        ServiceLayer s = new ServiceLayer();
        s.performGet("",Get<String>.class,this); //Cannot select from parameterised type

    }

I Googled around this a bit and found out that this is not possible due to type erasure. Fine.

But my question is, shouldn't the compiler raise an error here? At this line? :

public <T> void performGet(String url, final Class<Get<T>> clazz, com.example.spark.testapp.services.utils.Callback<T> callback) 

How did my service layer get compiled?

EDIT

The question seems to be misunderstood. I am not looking for a way to get this design to work. I understand the flaw in it and we have found a better way to layer our services. The question is about the interesting/weird behaviour of the language itself.

like image 927
avismara Avatar asked Apr 06 '16 06:04

avismara


2 Answers

But my question is, shouldn't the compiler raise an error here?

The method signature is perfectly correct in Java. And generic method signatures are controlled by the same rules as normal methods.

In a generic method, the actions that you can do in the code are dependent on the parameter types. For example, if you have this method:

public static <T> void test(List<T> list, Class<T> clazz) 
        throws InstantiationException, IllegalAccessException 
{
    list.add(clazz.newInstance());
}

It compiles, but if we add the next line:

list.add(new Integer(1));

it won't because in compile time list only accepts instances of T. Therefore the generic method is well defined.

When you try to call your generic method, the compiler cannot infer T from the parameters. The main problem is obviously the Class<Get<T>> construct, that is valid in the method signature but not recommended. Although, you can do an unsafe and weird cast to make the method call compile and work:

s.performGet("",(Class<Get<String>>)(Class) Get.class,this);

Doing this cast chain the compiler can now infer T, because the generic types are only checked in compile time. In runtime, Class<Get<T>> will always be Get.class.

Some related questions about this topic:

Passing the Class<T> in java of a generic list?

Generic type as parameter in Java Method

like image 191
JMSilla Avatar answered Oct 05 '22 23:10

JMSilla


First, your syntax is valid, hence compiler does not dhows error in given line. Error which you have is probably related to that Class is special class which is created by VM, and compiler during checking for error treats it as normal class.

Have a look at this example:

 class Get<T> {
    }

class Callback<T> {
    }
class Clazz<T> {
    }

static class ServiceLayer  {

        public <T> void performGet(String url, final Clazz<Get<T>> clazz,
                Callback<T> callback) {
        }
    }

now if you try to call

    ServiceLayer a = new ServiceLayer();
    Clazz<Get<String>> clazz = new Clazz<Hello.Get<String>>(); 
    Callback<String> callback = new Callback<String>();
    a.performGet("someurl", clazz, callback);

you want have any problems. So as you see there is no problem with that syntax, but with special object.

like image 24
user902383 Avatar answered Oct 06 '22 01:10

user902383