15

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.

avismara
  • 5,141
  • 2
  • 32
  • 56
  • I assume `this` implements `Callback`? – njzk2 Apr 08 '16 at 18:07
  • related: http://stackoverflow.com/questions/27000227/cannot-select-parameterized-type – njzk2 Apr 08 '16 at 18:09
  • @njk2 -- Yes. You are right. `this` implements Callback. And it isn't related at all. The question is, how does my service layer get compiled if you cannot just call it? – avismara Apr 08 '16 at 18:12
  • I am not sure you have the best approach, or even retrofit is the right tool to use here. You apparently have a single GET endpoint that returns different things based on various things. Possibly you can define all the possible types returned by your endpoint, if you know then. Or use something else entirely, a little more low-level. – njzk2 Apr 08 '16 at 18:36
  • Yes, we are not using Retrofit with the best of our judgements. But still this language quirk got me intrigued and hence the question -- which I restate -- is about the compiler letting me do things that are undoable. I am more than convinced that this is not the approach. – avismara Apr 08 '16 at 19:08
  • it is indeed quite surprising, as all generics are basically erased at runtime, so the signature of the method should match the call. weird. – njzk2 Apr 08 '16 at 19:59

4 Answers4

3

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

Community
  • 1
  • 1
JMSilla
  • 1,326
  • 10
  • 17
1

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.

user902383
  • 8,420
  • 8
  • 43
  • 63
0

Did some tests and found some solution, hopefully it will help.

Since this code returns true (where A is some generic class that do nothing):

A<String> a = new A<String>();
System.out.println(A.class == a.getClass());

It means that you don't need to get class of 'Get< T>' but just 'Get' for your Retrofit

What you need is to send T as the other argument, for function to know what is this T, for example:

public <T> void performGet(String url, final Class<Get> clazz, final Class<T> t, com.example.spark.testapp.services.utils.Callback<T> callback) {
    Retrofit retrofit = new Retrofit.Builder().baseUrl("").build();
    Get<T> service = retrofit.create(clazz);
    ...
Paweł.Ch
  • 44
  • 4
  • We tried this solution before, but we don't want the `T`'s type information to be lost within `retrofit.create(clazz)`. What it returns is something of type `Get` not `Get`. – avismara Apr 06 '16 at 09:32
  • By type you mean class? If yes first example shows that `Get` and `Get` are of the same class. If not could you tell what kind of error u get after setting it by `Get = ...`? – Paweł.Ch Apr 06 '16 at 09:58
  • By type, I mean type. I do not get an error, but it gives me a warning, naturally. The type of `Get` and `Get` are not same. Even if we do this the way you suggested, it will compile, but it will definitely fail to do what I want it to do. :) Plus, this is not the answer to my question anyway. – avismara Apr 06 '16 at 10:59
-1

If you want to keep the generic class parameter (T) info reachable in a method, you must provide the class information. Example:

public class SomeClass<T, X, C> {
   Class<X> xClass;
   Class<T> tClass;
   Class<C> cClass;

   public SomeClass<T, X, C>(Class<T> a, Class<X> b, Class<C> c){
      tClass = a;
      xClass = b;
      cClass = c;
   }
}

That way, you could use the class info inside the "consumer" class, either for accessing common signatures, serialization/deserialization processes, object creation, calling generic methods, etc.

Now, if you just want to use a generic class parameter's info within a method, you should:

public <T> void performGet(String url, final Class<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);
}

This allows you the same than saving the generic class info into the class containing the method; you can access to it within your method and use it as a parameter for other generic methods and everything else.

If you need more examples, take a look at this. It's a wrapper too, but around around Spring's RestTemplate-Android, so maybe you can get some ideas from it. (Disclaimer - I'm teh dev)

https://github.com/fcopardo/EasyRest/blob/master/src/main/java/com/grizzly/rest/GenericRestCall.java

Fco P.
  • 2,486
  • 17
  • 20
  • Please take a look at the edit. I am NOT looking for a solution to get this to work. I am asking why does the compiler let this code compile --- a code where a method signature is un-callable" --- when it shouldn't. This answer, as informative as it is, isn't really what I am looking for – avismara Apr 13 '16 at 19:11